Add FormatToSpan

This commit is contained in:
Alexander Shabalin
2021-08-02 16:07:50 +00:00
committed by Space
parent 2c068b8c5a
commit 4c3fb8697b
5 changed files with 436 additions and 9 deletions
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "Format.h"
#include "Porting.h"
using namespace kotlin;
std_support::span<char> kotlin::FormatToSpan(std_support::span<char> buffer, const char* format, ...) noexcept {
std::va_list args;
va_start(args, format);
auto result = VFormatToSpan(buffer, format, args);
va_end(args);
return result;
}
std_support::span<char> kotlin::VFormatToSpan(std_support::span<char> buffer, const char* format, std::va_list args) noexcept {
if (buffer.empty()) return buffer;
if (buffer.size() == 1) {
buffer.front() = '\0';
return buffer;
}
int written = konan::vsnprintf(buffer.data(), buffer.size(), format, args);
// Consider this a failure, nothing has been written. TODO: Should this be an exception/RuntimeAssert?
if (written < 0) return buffer;
// If `written` is larger than the buffer size, just pretend we filled the entire buffer (ignoring the trailing \0).
size_t writtenSize = std::min(static_cast<size_t>(written), buffer.size() - 1);
return buffer.subspan(writtenSize);
}
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#ifndef RUNTIME_FORMAT_H
#define RUNTIME_FORMAT_H
#include <cstdarg>
#include "cpp_support/Span.hpp"
namespace kotlin {
/*
* `FormatToSpan` is a `snprintf`-like API for formatting text.
*
* It formats text into a `span` buffer (it uses `snprintf` under the hood and so needs a contiguous memory buffer to write into)
* and returns the unused portion of `span`.
* When input `span` is empty it does nothing.
* If `snprintf` fails it returns the buffer unmodified.
* If the formatted text cannot fit into the buffer, it'll be truncated enough to fit.
* If any character was written into the buffer, there will always be a null character appended, and the returned span will point to it.
*
* ```
* auto output = FormatToSpan(input, "string");
* ```
* If `input` was a buffer filled with `'\1'` of size 10, by the end the memory would look something like:
* ```
* s t r i n g \0 \1 \1 \1
* ^ ^
* | |
* input output
* ```
* and the output will be of size 4.
* If `input` was of size 5 instead:
* ```
* s t r i \0
* ^ ^
* | |
* | output
* input
* ```
* and the output will be of size 1.
*
* This allows for a composable behaviour:
* ```
* auto buffer = ...;
* buffer = FormatToSpan(buffer, "str");
* buffer = FormatToSpan(buffer, "%d", 42);
* buffer = FormatToSpan(buffer, "%s", "again");
* ```
* If `buffer` was of sufficient size, the result will be `str42again\0`.
*/
// Format snprintf-style message into a `buffer` and return the unused portion of a buffer.
std_support::span<char> FormatToSpan(std_support::span<char> buffer, const char* format, ...) noexcept
__attribute__((format(printf, 2, 3)));
// Format vsnprintf-style message into a `buffer` and return the unused portion of a buffer.
std_support::span<char> VFormatToSpan(std_support::span<char> buffer, const char* format, std::va_list args) noexcept;
} // namespace kotlin
#endif // RUNTIME_FORMAT_H
@@ -0,0 +1,327 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "Format.h"
#include <array>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "cpp_support/Span.hpp"
using namespace kotlin;
TEST(FormatTest, FormatToSpan_String) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(buffer, "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', 'b', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), buffer.size() - 2);
}
TEST(FormatTest, FormatToSpan_StringFormat) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(buffer, "%s", "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', 'b', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), buffer.size() - 2);
}
TEST(FormatTest, FormatToSpan_IntFormat) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(buffer, "%d", 42);
EXPECT_THAT(buffer, testing::ElementsAre('4', '2', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), buffer.size() - 2);
}
TEST(FormatTest, FormatToSpan_String_Size0) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(0), "ab");
EXPECT_THAT(buffer, testing::ElementsAre('\1', '\1', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 0);
}
TEST(FormatTest, FormatToSpan_StringFormat_Size0) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(0), "%s", "ab");
EXPECT_THAT(buffer, testing::ElementsAre('\1', '\1', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 0);
}
TEST(FormatTest, FormatToSpan_IntFormat_Size0) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(0), "%d", 42);
EXPECT_THAT(buffer, testing::ElementsAre('\1', '\1', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 0);
}
TEST(FormatTest, FormatToSpan_String_Size1) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(1), "ab");
EXPECT_THAT(buffer, testing::ElementsAre('\0', '\1', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_StringFormat_Size1) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(1), "%s", "ab");
EXPECT_THAT(buffer, testing::ElementsAre('\0', '\1', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_IntFormat_Size1) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(1), "%d", 42);
EXPECT_THAT(buffer, testing::ElementsAre('\0', '\1', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_String_Size2) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(2), "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', '\0', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 1);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_StringFormat_Size2) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(2), "%s", "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', '\0', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 1);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_IntFormat_Size2) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(2), "%d", 42);
EXPECT_THAT(buffer, testing::ElementsAre('4', '\0', '\1', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 1);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_String_Size3) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(3), "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', 'b', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_StringFormat_Size3) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(3), "%s", "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', 'b', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_IntFormat_Size3) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(3), "%d", 42);
EXPECT_THAT(buffer, testing::ElementsAre('4', '2', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_String_Size4) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(4), "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', 'b', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 2);
}
TEST(FormatTest, FormatToSpan_StringFormat_Size4) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(4), "%s", "ab");
EXPECT_THAT(buffer, testing::ElementsAre('a', 'b', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 2);
}
TEST(FormatTest, FormatToSpan_IntFormat_Size4) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatToSpan(std_support::span(buffer).first(4), "%d", 42);
EXPECT_THAT(buffer, testing::ElementsAre('4', '2', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 2);
}
TEST(FormatTest, FormatToSpan_Sequence) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
std_support::span<char> result(buffer);
result = FormatToSpan(result, "a");
result = FormatToSpan(result, "%s", "b");
result = FormatToSpan(result, "%d", 4);
EXPECT_THAT(buffer, testing::ElementsAre('a', 'b', '4', '\0', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 3);
EXPECT_THAT(result.size(), 2);
}
namespace {
std_support::span<char> FormatNesting(std_support::span<char> buffer) {
buffer = FormatToSpan(buffer, "(");
auto nested = buffer.first(buffer.size() - 1); // Leave a space for `)`
nested = FormatToSpan(nested, "a ");
nested = FormatToSpan(nested, "b ");
buffer = buffer.subspan(nested.data() - buffer.data());
return FormatToSpan(buffer, ")");
}
} // namespace
TEST(FormatTest, FormatToSpan_Nesting_Size1) {
std::array buffer{'\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('\0'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_Nesting_Size2) {
std::array buffer{'\1', '\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 1);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_Nesting_Size3) {
std::array buffer{'\1', '\1', '\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_Nesting_Size4) {
std::array buffer{'\1', '\1', '\1', '\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 3);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_Nesting_Size5) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 4);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_Nesting_Size6) {
std::array buffer{'\1', '\1', '\1', '\1', '\1', '\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', 'b', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 5);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_Nesting_Size7) {
std::array buffer{'\1', '\1', '\1', '\1', '\1', '\1', '\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', 'b', ' ', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 6);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_Nesting_Size8) {
std::array buffer{'\1', '\1', '\1', '\1', '\1', '\1', '\1', '\1'};
auto result = FormatNesting(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', 'b', ' ', ')', '\0', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 6);
EXPECT_THAT(result.size(), 2);
}
namespace {
std_support::span<char> FormatNestingWithBacktrack(std_support::span<char> buffer) {
buffer = FormatToSpan(buffer, "(");
auto nested = buffer;
nested = FormatToSpan(nested, "a ");
nested = FormatToSpan(nested, "b ");
if (nested.data() != buffer.data()) {
// If we managed to format something in nested, replace the last formatted character with ')'
buffer = buffer.subspan(nested.data() - buffer.data() - 1);
}
return FormatToSpan(buffer, ")");
}
} // namespace
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size1) {
std::array buffer{'\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('\0'));
EXPECT_THAT(result.data(), buffer.data());
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size2) {
std::array buffer{'\1', '\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 1);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size3) {
std::array buffer{'\1', '\1', '\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 2);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size4) {
std::array buffer{'\1', '\1', '\1', '\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 3);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size5) {
std::array buffer{'\1', '\1', '\1', '\1', '\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 4);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size6) {
std::array buffer{'\1', '\1', '\1', '\1', '\1', '\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', 'b', ')', '\0'));
EXPECT_THAT(result.data(), buffer.data() + 5);
EXPECT_THAT(result.size(), 1);
}
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size7) {
std::array buffer{'\1', '\1', '\1', '\1', '\1', '\1', '\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', 'b', ')', '\0', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 5);
EXPECT_THAT(result.size(), 2);
}
TEST(FormatTest, FormatToSpan_NestingWithBacktrack_Size8) {
std::array buffer{'\1', '\1', '\1', '\1', '\1', '\1', '\1', '\1'};
auto result = FormatNestingWithBacktrack(buffer);
EXPECT_THAT(buffer, testing::ElementsAre('(', 'a', ' ', 'b', ')', '\0', '\1', '\1'));
EXPECT_THAT(result.data(), buffer.data() + 5);
EXPECT_THAT(result.size(), 3);
}
+11 -9
View File
@@ -3,9 +3,13 @@
* that can be found in the LICENSE file.
*/
#include "KAssert.h"
#include <array>
#include <cstdarg>
#include "Porting.h"
#include "cpp_support/Span.hpp"
#include "Format.h"
#include "StackTrace.hpp"
using namespace kotlin;
@@ -13,22 +17,20 @@ using namespace kotlin;
namespace {
void PrintAssert(bool allowStacktrace, const char* location, const char* format, std::va_list args) noexcept {
char buf[1024];
int written = -1;
std::array<char, 1024> bufferStorage;
std_support::span<char> buffer(bufferStorage);
// Write the title with a source location.
if (location != nullptr) {
written = konan::snprintf(buf, sizeof(buf), "%s: runtime assert: ", location);
buffer = FormatToSpan(buffer, "%s: runtime assert: ", location);
} else {
written = konan::snprintf(buf, sizeof(buf), "runtime assert: ");
buffer = FormatToSpan(buffer, "runtime assert: ");
}
// Write the message.
if (written >= 0 && static_cast<size_t>(written) < sizeof(buf)) {
konan::vsnprintf(buf + written, sizeof(buf) - written, format, args);
}
buffer = VFormatToSpan(buffer, format, args);
konan::consoleErrorUtf8(buf, konan::strnlen(buf, sizeof(buf)));
konan::consoleErrorUtf8(bufferStorage.data(), bufferStorage.size() - buffer.size());
konan::consoleErrorf("\n");
if (allowStacktrace) {
kotlin::PrintStackTraceStderr();
@@ -10,6 +10,7 @@
extern "C" OBJ_GETTER(Kotlin_Throwable_getStackTrace, KRef throwable);
// TODO: Consider refactoring to `FormatToSpan`.
static void writeStackTraceToBuffer(KRef throwable, char* buffer, unsigned long bufferSize) {
if (bufferSize < 2) return;