#include "prx/libSceJsc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "JavaScriptCore string check failed at line %d\n", line); std::abort(); } } #define Require(value) Check((value), __LINE__) static void Unicode() { std::u32string scalars; for (char32_t codepoint = 1; codepoint <= 0x10FFFF; ++codepoint) { if (codepoint < 0xD800 || codepoint > 0xDFFF) scalars.push_back(codepoint); } std::codecvt_utf8 encode; std::codecvt_utf8_utf16 decode; std::mbstate_t state{}; const char32_t* readScalars = nullptr; std::string utf8(scalars.size() * 4, '\0'); char* writtenBytes = nullptr; Require(encode.out(state, scalars.data(), scalars.data() + scalars.size(), readScalars, utf8.data(), utf8.data() + utf8.size(), writtenBytes) == std::codecvt_base::ok); Require(readScalars == scalars.data() + scalars.size()); utf8.resize(writtenBytes - utf8.data()); state = {}; const char* readBytes = nullptr; std::u16string utf16(utf8.size(), 0); char16_t* writtenUnits = nullptr; Require(decode.in(state, utf8.data(), utf8.data() + utf8.size(), readBytes, utf16.data(), utf16.data() + utf16.size(), writtenUnits) == std::codecvt_base::ok); Require(readBytes == utf8.data() + utf8.size()); utf16.resize(writtenUnits - utf16.data()); auto* string = JSStringCreateWithUTF8CString(utf8.c_str()); Require(JSStringGetLength(string) == utf16.size()); const auto* characters = JSStringGetCharactersPtr(string); Require(std::equal(utf16.begin(), utf16.end(), characters)); const std::vector units(utf16.begin(), utf16.end()); auto* other = JSStringCreateWithCharacters(units.data(), units.size()); Require(JSStringIsEqual(string, other)); Require(JSStringGetMaximumUTF8CStringSize(string) == utf16.size() * 3 + 1); std::vector buffer(utf8.size() + 2, '?'); Require(JSStringGetUTF8CString(string, buffer.data(), buffer.size() - 1) == utf8.size() + 1); Require(std::memcmp(buffer.data(), utf8.c_str(), utf8.size() + 1) == 0); Require(buffer.back() == '?'); JSStringRelease(other); JSStringRelease(string); } static void Boundaries() { const char text[] = "A\xC2\xA2\xE2\x82\xAC\xF0\x9F\x98\x80Z"; auto* string = JSStringCreateWithUTF8CString(text); Require(JSStringGetLength(string) == 6); const std::array complete{0, 1, 3, 6, 10, 11}; for (std::size_t capacity = 1; capacity <= sizeof(text) + 1; ++capacity) { std::array buffer; buffer.fill('?'); std::size_t expected = 0; for (const auto length : complete) { if (length < capacity) expected = length; } Require(JSStringGetUTF8CString(string, buffer.data(), capacity) == expected + 1); Require(std::memcmp(buffer.data(), text, expected) == 0 && buffer[expected] == '\0'); for (auto index = expected + 1; index < buffer.size(); ++index) Require(buffer[index] == '?'); } char unchanged = '!'; Require(JSStringGetUTF8CString(string, &unchanged, 0) == 0 && unchanged == '!'); Require(JSStringGetUTF8CString(string, nullptr, 10) == 0); Require(JSStringGetUTF8CString(nullptr, &unchanged, 1) == 0 && unchanged == '!'); Require(JSStringGetLength(nullptr) == 0 && JSStringGetCharactersPtr(nullptr) == nullptr); Require(JSStringIsEqual(nullptr, nullptr) && !JSStringIsEqual(nullptr, string)); Require(JSStringIsEqualToUTF8CString(string, text)); Require(!JSStringIsEqualToUTF8CString(string, "a")); JSStringRelease(string); } static void Invalid() { const auto* unavailable = reinterpret_cast(std::uintptr_t{1}); for (const auto count : {std::size_t{2147483636}, static_cast(std::numeric_limits::max()), static_cast(std::numeric_limits::max()) + 1, static_cast(std::numeric_limits::max()), std::size_t{1} << 32, std::numeric_limits::max()}) { bool threw = false; try { JSStringCreateWithCharacters(unavailable, count); } catch (const std::length_error&) { threw = true; } Require(threw); auto* nullCharacters = JSStringCreateWithCharacters(nullptr, count); Require(JSStringGetLength(nullCharacters) == 0 && JSStringGetCharactersPtr(nullCharacters) == nullptr); Require(JSStringIsEqualToUTF8CString(nullCharacters, nullptr)); JSStringRelease(nullCharacters); } auto* empty = JSStringCreateWithUTF8CString(""); Require(JSStringGetLength(empty) == 0); auto* nullString = JSStringCreateWithCharacters(nullptr, 0); Require(JSStringGetLength(nullString) == 0 && JSStringGetCharactersPtr(nullString) == nullptr); Require(!JSStringIsEqual(empty, nullString)); Require(JSStringIsEqualToUTF8CString(nullString, "\xFF")); JSStringRelease(nullString); for (const auto* invalid : {"\x80", "\xC0\x80", "\xC1\xBF", "\xE0\x80\x80", "\xED\xA0\x80", "\xF0\x80\x80\x80", "\xF4\x90\x80\x80", "\xF5\x80\x80\x80", "\xFF", "x\xC2", "x\xE2\x82", "x\xF0\x9F\x98", "\xE2!"}) { auto* string = JSStringCreateWithUTF8CString(invalid); Require(JSStringGetLength(string) == 0 && JSStringGetCharactersPtr(string) == nullptr); Require(!JSStringIsEqual(string, empty)); Require(JSStringIsEqualToUTF8CString(string, "\xFF")); char buffer = '!'; Require(JSStringGetUTF8CString(string, &buffer, 1) == 1 && buffer == '\0'); JSStringRelease(string); } for (const auto& units : std::vector>{{0xD800}, {0xDC00}, {'x', 0xD800}, {'x', 0xDC00, 'y'}, {'x', 0xD800, 'y'}}) { auto* string = JSStringCreateWithCharacters(units.data(), units.size()); Require(JSStringGetLength(string) == units.size()); Require(std::equal(units.begin(), units.end(), JSStringGetCharactersPtr(string))); std::array buffer; buffer.fill('?'); const std::size_t prefix = units[0] == 'x' ? 1 : 0; Require(JSStringGetUTF8CString(string, buffer.data(), buffer.size()) == prefix + 1); Require(buffer[prefix] == '\0' && buffer[prefix + 1] == '?'); JSStringRelease(string); } JSStringRelease(empty); } static void NullText() { auto* nullText = JSStringCreateWithUTF8CString(nullptr); Require(JSStringGetLength(nullText) == 0 && JSStringGetCharactersPtr(nullText) == nullptr); Require(JSStringGetMaximumUTF8CStringSize(nullText) == 1); char buffer = '!'; Require(JSStringGetUTF8CString(nullText, &buffer, 1) == 1 && buffer == '\0'); auto* malformed = JSStringCreateWithUTF8CString("\xFF"); auto* nullCharacters = JSStringCreateWithCharacters(nullptr, 0); auto* empty = JSStringCreateWithUTF8CString(""); const std::array unit{'x'}; auto* emptyCharacters = JSStringCreateWithCharacters(unit.data(), 0); auto* text = JSStringCreateWithUTF8CString("null"); Require(JSStringIsEqual(nullText, malformed) && JSStringIsEqual(nullText, nullCharacters)); Require(!JSStringIsEqual(nullText, empty) && !JSStringIsEqual(nullText, emptyCharacters)); Require(JSStringGetCharactersPtr(emptyCharacters) != nullptr && JSStringIsEqual(empty, emptyCharacters)); Require(JSStringIsEqualToUTF8CString(nullText, nullptr)); Require(JSStringIsEqualToUTF8CString(malformed, nullptr)); Require(JSStringIsEqualToUTF8CString(nullCharacters, nullptr)); Require(!JSStringIsEqualToUTF8CString(empty, nullptr)); Require(!JSStringIsEqualToUTF8CString(emptyCharacters, nullptr)); Require(!JSStringIsEqualToUTF8CString(text, nullptr)); Require(!JSStringIsEqualToUTF8CString(nullptr, nullptr)); Require(!JSStringIsEqualToUTF8CString(nullptr, "")); Require(JSStringIsEqualToUTF8CString(nullText, "\xC0\x80")); Require(!JSStringIsEqualToUTF8CString(nullText, "")); for (auto* string : {nullText, malformed, nullCharacters, empty, emptyCharacters, text}) JSStringRelease(string); } template static bool Faults(Call call) { try { call(); } catch (const std::invalid_argument&) { return true; } return false; } static void NullHandles() { Require(Faults([] { JSStringRetain(nullptr); })); Require(Faults([] { JSStringRelease(nullptr); })); Require(Faults([] { JSStringGetMaximumUTF8CStringSize(nullptr); })); } static void References() { auto* string = JSStringCreateWithUTF8CString("wrap"); Require(string->references.load() == 1); Require(JSStringRetain(string) == string && string->references.load() == 2); JSStringRelease(string); Require(string->references.load() == 1); string->references.store(std::numeric_limits::max()); Require(JSStringRetain(string) == string && string->references.load() == 0); JSStringRelease(string); Require(string->references.load() == std::numeric_limits::max()); Require(JSStringIsEqualToUTF8CString(string, "wrap")); string->references.store(1); JSStringRelease(string); } static void Limits() { constexpr std::size_t utf8Limit = std::numeric_limits::max(); constexpr std::size_t utf16Limit = 2147483635; for (const std::size_t length : {std::size_t{0}, std::size_t{1}, utf16Limit, utf16Limit + 1, utf8Limit}) { Require(GuestJSStringValidLength(length)); } for (const std::size_t length : {utf8Limit + 1, std::size_t{std::numeric_limits::max()}, std::size_t{1} << 32, std::numeric_limits::max()}) { Require(!GuestJSStringValidLength(length)); } for (const std::size_t length : {std::size_t{0}, std::size_t{1}, utf16Limit}) { Require(GuestJSStringValidLength(length)); } for (const std::size_t length : {utf16Limit + 1, utf8Limit, utf8Limit + 1, std::numeric_limits::max()}) { Require(!GuestJSStringValidLength(length)); } } static void MaximumSize() { for (const auto* text : {"", "a", "\xC2\xA2", "\xE2\x82\xAC", "\xF0\x9F\x98\x80", "abc\xF0\x9F\x98\x80"}) { auto* string = JSStringCreateWithUTF8CString(text); Require(JSStringGetMaximumUTF8CStringSize(string) == JSStringGetLength(string) * 3 + 1); JSStringRelease(string); } std::vector units(1000, 'u'); auto* string = JSStringCreateWithCharacters(units.data(), units.size()); Require(JSStringGetMaximumUTF8CStringSize(string) == 3001); JSStringRelease(string); } static void Ownership() { std::array units{'A', 0, 0x03A9, 'B'}; auto* string = JSStringCreateWithCharacters(units.data(), units.size()); units.fill('?'); const auto* pointer = JSStringGetCharactersPtr(string); Require(pointer[0] == 'A' && pointer[1] == 0 && pointer[2] == 0x03A9 && pointer[3] == 'B'); std::array buffer{}; Require(JSStringGetUTF8CString(string, buffer.data(), buffer.size()) == 6); const std::array expected{'A', 0, static_cast(0xCE), static_cast(0xA9), 'B', 0}; Require(std::equal(expected.begin(), expected.end(), buffer.begin())); auto* retained = JSStringRetain(string); Require(retained == string); JSStringRelease(string); Require(JSStringGetCharactersPtr(retained) == pointer && JSStringGetLength(retained) == 4); std::vector threads; for (unsigned index = 0; index < 4; ++index) { threads.emplace_back([retained] { for (unsigned count = 0; count < 5000; ++count) { Require(JSStringRetain(retained) == retained); Require(JSStringGetLength(retained) == 4); JSStringRelease(retained); } }); } for (auto& thread : threads) thread.join(); JSStringRelease(retained); } int main() { Unicode(); Boundaries(); Invalid(); NullText(); NullHandles(); References(); Limits(); MaximumSize(); Ownership(); }