#include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include using GuestContextCompare = int (APS5_VABI*)(const void*, const void*, void*); extern "C" { void* APS5_VABI set_constraint_handler_s_nid_postfix(void*); void* APS5_VABI bsearch_s_nid_postfix(const void*, const void*, std::size_t, std::size_t, GuestContextCompare, void*); std::uint16_t* APS5_VABI wcstok_s_nid_postfix(std::uint16_t*, std::size_t*, const std::uint16_t*, std::uint16_t**); int APS5_VABI wctomb_s_nid_postfix(int*, char*, std::size_t, std::uint16_t); int APS5_VABI wcrtomb_s_nid_postfix(std::size_t*, char*, std::size_t, std::uint16_t, void*); int APS5_VABI mbstowcs_s_nid_postfix(std::size_t*, std::uint16_t*, std::size_t, const char*, std::size_t); int APS5_VABI mbsrtowcs_s_nid_postfix(std::size_t*, std::uint16_t*, std::size_t, const char**, std::size_t, void*); char* APS5_VABI gets_s_nid_postfix(char*, std::size_t); FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); int APS5_VABI fclose_nid_postfix(FileStream*); extern FileStream* __stdinp_nid_postfix; } namespace { constexpr std::size_t failed = static_cast(-1); constexpr std::size_t tooLarge = std::size_t{1} << 61; int constraintCalls = 0; int constraintError = 0; std::string constraintMessage; void APS5_VABI RecordConstraint(const char* message, void* pointer, int error) { ++constraintCalls; constraintMessage = message; constraintError = error; if (pointer != nullptr) { std::fprintf(stderr, "constraint handler received a non-null pointer\n"); std::abort(); } } void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "%s\n", message); std::abort(); } } void RequireViolation(int before, const char* message, int error) { if (constraintCalls != before + 1 || constraintMessage != message || constraintError != error) { std::fprintf(stderr, "expected one \"%s\" violation, got %d calls (last \"%s\")\n", message, constraintCalls - before, constraintMessage.c_str()); std::abort(); } } int APS5_VABI CompareInts(const void* left, const void* right, void* context) { ++*static_cast(context); const int a = *static_cast(left); const int b = *static_cast(right); return (a > b) - (a < b); } std::basic_string Wide(const char* text) { std::basic_string wide; while (*text) wide.push_back(static_cast(*text++)); return wide; } void CheckBsearch() { const int values[] = {1, 3, 3, 3, 5, 7, 9}; int calls = 0; const int key = 3; Require(bsearch_s_nid_postfix(&key, values, 7, sizeof(int), CompareInts, &calls) == &values[3] && calls == 1, "bsearch_s did not probe the middle element first"); const int missing = 4; calls = 0; Require(bsearch_s_nid_postfix(&missing, values, 7, sizeof(int), CompareInts, &calls) == nullptr && calls == 3, "bsearch_s found a missing key"); const int last = 9; Require(bsearch_s_nid_postfix(&last, values, 7, sizeof(int), CompareInts, &calls) == &values[6], "bsearch_s missed the last element"); int before = constraintCalls; Require(bsearch_s_nid_postfix(nullptr, nullptr, 0, sizeof(int), nullptr, nullptr) == nullptr && constraintCalls == before, "bsearch_s reported an empty array"); Require(bsearch_s_nid_postfix(nullptr, values, 7, sizeof(int), CompareInts, &calls) == nullptr, "bsearch_s accepted a null key"); RequireViolation(before, "bsearch_s: bad argument", 22); before = constraintCalls; Require(bsearch_s_nid_postfix(&key, values, 0, tooLarge, CompareInts, &calls) == nullptr, "bsearch_s accepted an oversized element"); RequireViolation(before, "bsearch_s: bad argument", 22); } void CheckWcstok() { auto text = Wide(",,ab;c,,;"); const auto delimiters = Wide(",;"); std::size_t remaining = text.size() + 1; std::uint16_t* context = nullptr; std::uint16_t* token = wcstok_s_nid_postfix(text.data(), &remaining, delimiters.c_str(), &context); Require(token == text.data() + 2 && token[0] == 'a' && token[1] == 'b' && token[2] == 0 && remaining == 5 && context == text.data() + 5, "wcstok_s did not return the first token"); token = wcstok_s_nid_postfix(nullptr, &remaining, delimiters.c_str(), &context); Require(token == text.data() + 5 && token[1] == 0 && remaining == 3 && context == text.data() + 7, "wcstok_s did not return the second token"); token = wcstok_s_nid_postfix(nullptr, &remaining, delimiters.c_str(), &context); Require(token == nullptr && remaining == 1 && context == text.data() + 9, "wcstok_s did not stop at the end of the string"); int before = constraintCalls; auto unterminated = Wide("abc"); remaining = 2; Require(wcstok_s_nid_postfix(unterminated.data(), &remaining, delimiters.c_str(), &context) == nullptr, "wcstok_s read past s1max"); RequireViolation(before, "wcstok_s: missing delimiter", 22); before = constraintCalls; auto separators = Wide(",,,x"); remaining = 3; Require(wcstok_s_nid_postfix(separators.data(), &remaining, delimiters.c_str(), &context) == nullptr, "wcstok_s skipped past s1max"); RequireViolation(before, "wcstok_s: missing token", 22); before = constraintCalls; remaining = 0; Require(wcstok_s_nid_postfix(separators.data(), &remaining, delimiters.c_str(), &context) == nullptr, "wcstok_s accepted a zero s1max"); RequireViolation(before, "wcstok_s: missing token", 22); before = constraintCalls; context = nullptr; remaining = 4; Require(wcstok_s_nid_postfix(nullptr, &remaining, delimiters.c_str(), &context) == nullptr, "wcstok_s accepted a null context string"); RequireViolation(before, "wcstok_s: bad argument", 22); } void CheckWideToMultibyte() { int status = 7; char bytes[4] = {'x', 'x', 'x', 'x'}; Require(wctomb_s_nid_postfix(&status, bytes, sizeof(bytes), 'A') == 0 && status == 1 && bytes[0] == 'A', "wctomb_s did not convert"); Require(wctomb_s_nid_postfix(&status, bytes, sizeof(bytes), 0x263a) == 86 && status == -1, "wctomb_s converted an unrepresentable character"); Require(wctomb_s_nid_postfix(&status, nullptr, 0, 'A') == 0 && status == 0, "wctomb_s reported state dependence"); status = 7; int before = constraintCalls; Require(wctomb_s_nid_postfix(&status, bytes, 0, 'A') == 34 && status == 7, "wctomb_s wrote into a zero-size buffer"); RequireViolation(before, "wctomb_s: buffer too small", 34); before = constraintCalls; Require(wctomb_s_nid_postfix(nullptr, bytes, sizeof(bytes), 'A') == 22, "wctomb_s accepted a null status"); RequireViolation(before, "wctomb_s: bad argument", 22); unsigned char state[128]{}; std::size_t result = 7; Require(wcrtomb_s_nid_postfix(&result, bytes, sizeof(bytes), 'B', state) == 0 && result == 1 && bytes[0] == 'B', "wcrtomb_s did not convert"); Require(wcrtomb_s_nid_postfix(&result, nullptr, 0, 'B', state) == 0 && result == 1, "wcrtomb_s did not measure the reset sequence"); Require(wcrtomb_s_nid_postfix(&result, bytes, sizeof(bytes), 0x263a, state) == 86 && result == failed, "wcrtomb_s converted an unrepresentable character"); before = constraintCalls; bytes[0] = 'x'; Require(wcrtomb_s_nid_postfix(&result, bytes, sizeof(bytes), 'B', nullptr) == 22 && result == failed && bytes[0] == 0, "wcrtomb_s accepted a null state"); RequireViolation(before, "wcrtomb_s: bad argument", 22); before = constraintCalls; bytes[0] = 'x'; Require(wcrtomb_s_nid_postfix(&result, bytes, 0, 'B', state) == 22 && result == failed && bytes[0] == 'x', "wcrtomb_s accepted a zero-size buffer"); RequireViolation(before, "wcrtomb_s: bad argument", 22); } void CheckMultibyteToWide() { std::uint16_t wide[8]; std::size_t result = 0; Require(mbstowcs_s_nid_postfix(&result, nullptr, 0, "hello", 0) == 0 && result == 5, "mbstowcs_s did not count the characters"); Require(mbstowcs_s_nid_postfix(&result, wide, 8, "hello", 8) == 0 && result == 5 && wide[0] == 'h' && wide[4] == 'o' && wide[5] == 0, "mbstowcs_s did not convert the string"); Require(mbstowcs_s_nid_postfix(&result, wide, 8, "hello", 3) == 0 && result == 3 && wide[2] == 'l' && wide[3] == 0, "mbstowcs_s did not stop at len"); wide[0] = 'x'; Require(mbstowcs_s_nid_postfix(&result, wide, 8, "hello", 0) == 0 && result == 0 && wide[0] == 0, "mbstowcs_s did not handle a zero len"); int before = constraintCalls; wide[0] = 'x'; Require(mbstowcs_s_nid_postfix(&result, wide, 5, "hello", 8) == 34 && result == failed && wide[0] == 0, "mbstowcs_s overflowed the buffer"); RequireViolation(before, "mbstowcs_s: buffer overflow", 34); before = constraintCalls; wide[0] = 'x'; Require(mbstowcs_s_nid_postfix(&result, wide, 8, nullptr, 8) == 22 && result == failed && wide[0] == 0, "mbstowcs_s accepted a null source"); RequireViolation(before, "mbstowcs_s: bad argument", 22); unsigned char state[128]{}; const char* source = "world"; Require(mbsrtowcs_s_nid_postfix(&result, nullptr, 0, &source, 0, state) == 0 && result == 5 && std::strcmp(source, "world") == 0, "mbsrtowcs_s did not count the characters"); Require(mbsrtowcs_s_nid_postfix(&result, wide, 8, &source, 8, state) == 0 && result == 5 && wide[4] == 'd' && wide[5] == 0 && source == nullptr, "mbsrtowcs_s did not convert the string"); const char* text = "world"; source = text; Require(mbsrtowcs_s_nid_postfix(&result, wide, 8, &source, 2, state) == 0 && result == 2 && wide[2] == 0 && source == text + 2, "mbsrtowcs_s did not stop at len"); before = constraintCalls; source = text; Require(mbsrtowcs_s_nid_postfix(&result, wide, 3, &source, 8, state) == 34 && result == failed && wide[0] == 0 && source == text + 3, "mbsrtowcs_s overflowed the buffer"); RequireViolation(before, "mbsrtowcs_s: buffer too small", 34); before = constraintCalls; source = nullptr; Require(mbsrtowcs_s_nid_postfix(&result, wide, 8, &source, 8, state) == 22 && result == failed, "mbsrtowcs_s accepted a null source string"); RequireViolation(before, "mbsrtowcs_s: bad argument", 22); before = constraintCalls; source = text; Require(mbsrtowcs_s_nid_postfix(&result, wide, tooLarge, &source, 8, state) == 22 && result == failed, "mbsrtowcs_s accepted an oversized buffer"); RequireViolation(before, "mbsrtowcs_s: bad argument", 22); } void CheckGets(const char* path) { { std::ofstream input(path, std::ios::binary | std::ios::trunc); input << "short\nexactly8\nrest"; Require(input.good(), "could not write the gets_s input"); } FileStream* const saved = __stdinp_nid_postfix; FileStream* const stream = fopen_nid_postfix(path, "r"); Require(stream != nullptr, "could not open the gets_s input"); __stdinp_nid_postfix = stream; char buffer[8]; Require(gets_s_nid_postfix(buffer, sizeof(buffer)) == buffer && std::strcmp(buffer, "short") == 0, "gets_s did not read a line"); int before = constraintCalls; Require(gets_s_nid_postfix(buffer, sizeof(buffer)) == nullptr && buffer[0] == 0, "gets_s accepted a line longer than the buffer"); RequireViolation(before, "gets_s: short buffer", 34); Require(gets_s_nid_postfix(buffer, sizeof(buffer)) == buffer && std::strcmp(buffer, "rest") == 0, "gets_s did not discard the rest of a long line"); buffer[0] = 'x'; Require(gets_s_nid_postfix(buffer, sizeof(buffer)) == nullptr && buffer[0] == 0, "gets_s did not report end of file"); before = constraintCalls; Require(gets_s_nid_postfix(buffer, 0) == nullptr, "gets_s accepted a zero size"); RequireViolation(before, "gets_s: zero buffer size", 22); before = constraintCalls; Require(gets_s_nid_postfix(nullptr, sizeof(buffer)) == nullptr, "gets_s accepted a null buffer"); RequireViolation(before, "gets_s: bad argument", 22); __stdinp_nid_postfix = saved; fclose_nid_postfix(stream); std::remove(path); } } // namespace int main(int argc, char** argv) { Require(argc > 1, "usage: guest_bounds_checking_tests "); set_constraint_handler_s_nid_postfix(reinterpret_cast(RecordConstraint)); CheckBsearch(); CheckWcstok(); CheckWideToMultibyte(); CheckMultibyteToWide(); CheckGets(argv[1]); return 0; }