Files
AnyPS5/core/libs/tests/GuestJscString.cpp
T
simnJS 2b10aa4211 fix(libSceJsc): bound UTF-8 input and decoded length as WebKit does
The shared UTF-8 decoder behind JSStringCreateWithUTF8CString and
JSStringIsEqualToUTF8CString now applies StringImpl::create's bounds: more
than INT32_MAX bytes throws before decoding, and non-ASCII input decoding to
more than 2147483635 UTF-16 units throws after a successful conversion.
GuestJSStringValidLength mirrors isValidLength and is tested at both limits.
2026-10-28 15:51:00 +01:00

270 lines
12 KiB
C++

#include "prx/libSceJsc/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <algorithm>
#include <array>
#include <codecvt>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <locale>
#include <limits>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
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<char32_t> encode;
std::codecvt_utf8_utf16<char16_t> 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<std::uint16_t> 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<char> 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<std::size_t, 6> complete{0, 1, 3, 6, 10, 11};
for (std::size_t capacity = 1; capacity <= sizeof(text) + 1; ++capacity) {
std::array<char, 32> 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<const std::uint16_t*>(std::uintptr_t{1});
for (const auto count : {std::size_t{2147483636}, static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()),
static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()) + 1,
static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max()), std::size_t{1} << 32,
std::numeric_limits<std::size_t>::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<std::vector<std::uint16_t>>{{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<char, 16> 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<std::uint16_t, 1> 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<typename Call> 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<std::uint32_t>::max());
Require(JSStringRetain(string) == string && string->references.load() == 0);
JSStringRelease(string);
Require(string->references.load() == std::numeric_limits<std::uint32_t>::max());
Require(JSStringIsEqualToUTF8CString(string, "wrap"));
string->references.store(1);
JSStringRelease(string);
}
static void Limits() {
constexpr std::size_t utf8Limit = std::numeric_limits<std::int32_t>::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<char8_t>(length));
}
for (const std::size_t length : {utf8Limit + 1, std::size_t{std::numeric_limits<std::uint32_t>::max()},
std::size_t{1} << 32, std::numeric_limits<std::size_t>::max()}) {
Require(!GuestJSStringValidLength<char8_t>(length));
}
for (const std::size_t length : {std::size_t{0}, std::size_t{1}, utf16Limit}) {
Require(GuestJSStringValidLength<char16_t>(length));
}
for (const std::size_t length : {utf16Limit + 1, utf8Limit, utf8Limit + 1, std::numeric_limits<std::size_t>::max()}) {
Require(!GuestJSStringValidLength<char16_t>(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<std::uint16_t> units(1000, 'u');
auto* string = JSStringCreateWithCharacters(units.data(), units.size());
Require(JSStringGetMaximumUTF8CStringSize(string) == 3001);
JSStringRelease(string);
}
static void Ownership() {
std::array<std::uint16_t, 4> 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<char, 10> buffer{};
Require(JSStringGetUTF8CString(string, buffer.data(), buffer.size()) == 6);
const std::array<char, 6> expected{'A', 0, static_cast<char>(0xCE), static_cast<char>(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<std::thread> 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();
}