feat(libc): implement _WStoul, _Iswctype and swscanf_s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
63cce467e7
commit
51fca3cec3
|
|
@ -28,6 +28,7 @@ target_sources(libc PRIVATE
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/JumpBuffer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Formatting.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/FormattingWide.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ScanningWide.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RuntimeSupport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleSupport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Process.cpp
|
||||
|
|
@ -117,6 +118,15 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_wide_strings PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_wide_conversion_tests ${LIBS_INCLUDE_DIR}/tests/GuestWideConversion.cpp)
|
||||
target_include_directories(guest_wide_conversion_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_wide_conversion_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_wide_conversion_tests)
|
||||
add_test(NAME guest_wide_conversion COMMAND guest_wide_conversion_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_wide_conversion PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_character_tests ${LIBS_INCLUDE_DIR}/tests/GuestCharacter.cpp)
|
||||
target_include_directories(guest_character_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_character_tests PRIVATE libc)
|
||||
|
|
|
|||
|
|
@ -56,22 +56,12 @@ std::uint64_t APS5_VABI _Cnd_destroy_nid_postfix() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI _Iswctype_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
APS5_EXPORT("H+8UBOwfScI", libcCyberUnknown08);
|
||||
std::uint64_t APS5_VABI libcCyberUnknown08(void) {
|
||||
NotImplemented_nid_no_patch("H+8UBOwfScI");
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI _WStoul_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI _Cnd_init_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
|
|
@ -101,9 +91,4 @@ std::uint64_t APS5_VABI _Cnd_wait_nid_postfix() {
|
|||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI swscanf_s_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace GuestRune {
|
||||
|
||||
|
|
@ -107,6 +110,16 @@ int APS5_VABI iswupper_nid_postfix(std::uint32_t c) { return GuestRune::Is(c, Gu
|
|||
int APS5_VABI iswxdigit_nid_postfix(std::uint32_t c) { return GuestRune::Is(c, GuestRune::Hex); }
|
||||
int APS5_VABI iswctype_nid_postfix(std::uint32_t c, std::uint64_t mask) { return GuestRune::Is(c, mask); }
|
||||
|
||||
int APS5_VABI _Iswctype_nid_postfix(std::uint32_t c, std::uint64_t type) {
|
||||
static constexpr std::uint64_t masks[] = {
|
||||
GuestRune::Alpha | GuestRune::Number, GuestRune::Alpha, GuestRune::Control, GuestRune::Digit,
|
||||
GuestRune::Graph, GuestRune::Lower, GuestRune::Print, GuestRune::Punct,
|
||||
GuestRune::Space, GuestRune::Upper, GuestRune::Hex, GuestRune::Blank,
|
||||
};
|
||||
if (type == 0 || type > std::size(masks)) throw std::invalid_argument("_Iswctype: unknown class " + std::to_string(type));
|
||||
return GuestRune::Is(c, masks[type - 1]);
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI wctype_nid_postfix(const char* property) {
|
||||
static constexpr struct {
|
||||
const char* name;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,408 @@
|
|||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/WindowsFormatting.hpp"
|
||||
|
||||
extern "C" int APS5_VABI iswspace_nid_postfix(std::uint32_t c);
|
||||
extern "C" std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value, void* state);
|
||||
|
||||
namespace {
|
||||
|
||||
enum class Fit { Invalid, Prefix, Valid };
|
||||
|
||||
enum class Outcome { Done, Matching, Input, Violation };
|
||||
|
||||
struct Directive {
|
||||
bool suppress = false;
|
||||
std::size_t width = 0;
|
||||
std::string length;
|
||||
char16_t conversion = 0;
|
||||
const char16_t* setBegin = nullptr;
|
||||
const char16_t* setEnd = nullptr;
|
||||
bool setExcluded = false;
|
||||
};
|
||||
|
||||
bool IsSpace(char16_t character) {
|
||||
return iswspace_nid_postfix(character) != 0;
|
||||
}
|
||||
|
||||
int DigitValue(char character) {
|
||||
if (character >= '0' && character <= '9') return character - '0';
|
||||
if (character >= 'a' && character <= 'z') return character - 'a' + 10;
|
||||
if (character >= 'A' && character <= 'Z') return character - 'A' + 10;
|
||||
return 36;
|
||||
}
|
||||
|
||||
bool IsDecimal(char character) {
|
||||
return character >= '0' && character <= '9';
|
||||
}
|
||||
|
||||
bool IsHex(char character) {
|
||||
return DigitValue(character) < 16;
|
||||
}
|
||||
|
||||
char Lower(char character) {
|
||||
return character >= 'A' && character <= 'Z' ? static_cast<char>(character - 'A' + 'a') : character;
|
||||
}
|
||||
|
||||
bool StartsWord(std::string_view text, std::string_view word) {
|
||||
if (text.size() > word.size()) return false;
|
||||
for (std::size_t index = 0; index < text.size(); ++index) {
|
||||
if (Lower(text[index]) != word[index]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string_view Unsigned(std::string_view text) {
|
||||
if (!text.empty() && (text[0] == '+' || text[0] == '-')) text.remove_prefix(1);
|
||||
return text;
|
||||
}
|
||||
|
||||
Fit IntegerFit(std::string_view text, int base) {
|
||||
text = Unsigned(text);
|
||||
if (text.empty()) return Fit::Prefix;
|
||||
int radix = base;
|
||||
std::size_t index = 0;
|
||||
if ((base == 0 || base == 16) && text[0] == '0' && text.size() >= 2 && (text[1] == 'x' || text[1] == 'X')) {
|
||||
radix = 16;
|
||||
index = 2;
|
||||
if (index == text.size()) return Fit::Prefix;
|
||||
} else if (base == 0) {
|
||||
radix = text[0] == '0' ? 8 : 10;
|
||||
}
|
||||
for (; index < text.size(); ++index) {
|
||||
if (DigitValue(text[index]) >= radix) return Fit::Invalid;
|
||||
}
|
||||
return Fit::Valid;
|
||||
}
|
||||
|
||||
Fit SpecialFloatFit(std::string_view text) {
|
||||
if (StartsWord(text, "infinity")) return text.size() == 3 || text.size() == 8 ? Fit::Valid : Fit::Prefix;
|
||||
if (StartsWord(text, "nan")) return text.size() == 3 ? Fit::Valid : Fit::Prefix;
|
||||
if (text.size() < 4 || !StartsWord(text.substr(0, 3), "nan") || text[3] != '(') return Fit::Invalid;
|
||||
for (std::size_t index = 4; index < text.size(); ++index) {
|
||||
const char character = text[index];
|
||||
if (character == ')') return index + 1 == text.size() ? Fit::Valid : Fit::Invalid;
|
||||
if (DigitValue(character) == 36 && character != '_') return Fit::Invalid;
|
||||
}
|
||||
return Fit::Prefix;
|
||||
}
|
||||
|
||||
Fit FloatFit(std::string_view text) {
|
||||
text = Unsigned(text);
|
||||
if (text.empty()) return Fit::Prefix;
|
||||
if (Lower(text[0]) == 'i' || Lower(text[0]) == 'n') return SpecialFloatFit(text);
|
||||
const bool hex = text.size() >= 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X');
|
||||
const auto digit = [hex](char character) { return hex ? IsHex(character) : IsDecimal(character); };
|
||||
std::size_t index = hex ? 2 : 0;
|
||||
std::size_t digits = 0;
|
||||
for (; index < text.size() && digit(text[index]); ++index) ++digits;
|
||||
if (index < text.size() && text[index] == '.') {
|
||||
for (++index; index < text.size() && digit(text[index]); ++index) ++digits;
|
||||
}
|
||||
if (index == text.size()) return digits != 0 ? Fit::Valid : Fit::Prefix;
|
||||
if (digits == 0) return Fit::Invalid;
|
||||
if (Lower(text[index]) != (hex ? 'p' : 'e')) return Fit::Invalid;
|
||||
++index;
|
||||
if (index < text.size() && (text[index] == '+' || text[index] == '-')) ++index;
|
||||
if (index == text.size()) return Fit::Prefix;
|
||||
for (; index < text.size(); ++index) {
|
||||
if (!IsDecimal(text[index])) return Fit::Invalid;
|
||||
}
|
||||
return Fit::Valid;
|
||||
}
|
||||
|
||||
std::size_t IntegerSize(const std::string& length) {
|
||||
if (length.empty()) return sizeof(std::int32_t);
|
||||
if (length == "hh") return sizeof(std::int8_t);
|
||||
if (length == "h") return sizeof(std::int16_t);
|
||||
if (length == "l" || length == "ll" || length == "j" || length == "z" || length == "t") return sizeof(std::int64_t);
|
||||
throw std::invalid_argument("swscanf_s: invalid integer length modifier " + length);
|
||||
}
|
||||
|
||||
class WideScanner {
|
||||
const char16_t* const start;
|
||||
const char16_t* input;
|
||||
const char16_t* format;
|
||||
LibcDetail::FormatArguments args;
|
||||
int assigned = 0;
|
||||
bool converted = false;
|
||||
|
||||
public:
|
||||
WideScanner(const char16_t* buffer, const char16_t* formatText, const void* source)
|
||||
: start(buffer), input(buffer), format(formatText), args(source) {}
|
||||
|
||||
int Run() {
|
||||
while (*format != 0) {
|
||||
Outcome outcome = Outcome::Done;
|
||||
if (IsSpace(*format)) {
|
||||
while (IsSpace(*format)) ++format;
|
||||
SkipSpace();
|
||||
} else if (*format != u'%') {
|
||||
outcome = Literal(*format++);
|
||||
} else {
|
||||
++format;
|
||||
outcome = Conversion();
|
||||
}
|
||||
if (outcome == Outcome::Matching) return assigned;
|
||||
if (outcome == Outcome::Input) return converted ? assigned : EOF;
|
||||
if (outcome == Outcome::Violation) return EOF;
|
||||
}
|
||||
return assigned;
|
||||
}
|
||||
|
||||
private:
|
||||
void SkipSpace() {
|
||||
while (*input != 0 && IsSpace(*input)) ++input;
|
||||
}
|
||||
|
||||
Outcome Literal(char16_t expected) {
|
||||
if (*input == 0) return Outcome::Input;
|
||||
if (*input != expected) return Outcome::Matching;
|
||||
++input;
|
||||
return Outcome::Done;
|
||||
}
|
||||
|
||||
Directive Parse() {
|
||||
Directive directive;
|
||||
if (*format == u'*') {
|
||||
directive.suppress = true;
|
||||
++format;
|
||||
}
|
||||
const char16_t* digits = format;
|
||||
for (; *format >= u'0' && *format <= u'9'; ++format) {
|
||||
const std::size_t digit = static_cast<std::size_t>(*format - u'0');
|
||||
const std::size_t limit = (std::numeric_limits<std::size_t>::max() - digit) / 10;
|
||||
directive.width = directive.width > limit ? std::numeric_limits<std::size_t>::max() : directive.width * 10 + digit;
|
||||
}
|
||||
if (format != digits && directive.width == 0) throw std::invalid_argument("swscanf_s: zero field width");
|
||||
if (*format == u'h' || *format == u'l') {
|
||||
directive.length += static_cast<char>(*format++);
|
||||
if (*format == directive.length[0]) directive.length += static_cast<char>(*format++);
|
||||
} else if (*format == u'j' || *format == u'z' || *format == u't' || *format == u'L') {
|
||||
directive.length += static_cast<char>(*format++);
|
||||
}
|
||||
directive.conversion = *format;
|
||||
if (directive.conversion == 0) throw std::invalid_argument("swscanf_s: incomplete conversion specification");
|
||||
++format;
|
||||
if (directive.conversion == u'[') {
|
||||
directive.setExcluded = *format == u'^';
|
||||
if (directive.setExcluded) ++format;
|
||||
directive.setBegin = format;
|
||||
if (*format == u']') ++format;
|
||||
while (*format != 0 && *format != u']') ++format;
|
||||
if (*format == 0) throw std::invalid_argument("swscanf_s: unterminated scanset");
|
||||
directive.setEnd = format++;
|
||||
for (const char16_t* member = directive.setBegin + 1; member + 1 < directive.setEnd; ++member) {
|
||||
if (*member == u'-') throw std::invalid_argument("swscanf_s: scanset range is implementation-defined");
|
||||
}
|
||||
}
|
||||
return directive;
|
||||
}
|
||||
|
||||
Outcome Conversion() {
|
||||
if (*format == u'%') {
|
||||
++format;
|
||||
SkipSpace();
|
||||
return Literal(u'%');
|
||||
}
|
||||
const Directive directive = Parse();
|
||||
switch (directive.conversion) {
|
||||
case u'd': return Integer(directive, 10, true);
|
||||
case u'i': return Integer(directive, 0, true);
|
||||
case u'o': return Integer(directive, 8, false);
|
||||
case u'u': return Integer(directive, 10, false);
|
||||
case u'x':
|
||||
case u'X': return Integer(directive, 16, false);
|
||||
case u'a': case u'A': case u'e': case u'E':
|
||||
case u'f': case u'F': case u'g': case u'G': return Float(directive);
|
||||
case u's':
|
||||
case u'c':
|
||||
case u'[': return Text(directive);
|
||||
case u'n': return Count(directive);
|
||||
default: throw std::invalid_argument("swscanf_s: unsupported conversion " + std::to_string(directive.conversion));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TFit>
|
||||
std::string Field(std::size_t width, TFit fit) const {
|
||||
std::string field;
|
||||
const std::size_t limit = width == 0 ? std::numeric_limits<std::size_t>::max() : width;
|
||||
while (field.size() < limit) {
|
||||
const char16_t next = input[field.size()];
|
||||
if (next == 0 || next >= 0x80) break;
|
||||
field.push_back(static_cast<char>(next));
|
||||
if (fit(field) == Fit::Invalid) {
|
||||
field.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
Outcome Integer(const Directive& directive, int base, bool isSigned) {
|
||||
const std::size_t size = IntegerSize(directive.length);
|
||||
void* target = nullptr;
|
||||
if (!directive.suppress && (target = args.Next<void*>()) == nullptr) return Outcome::Violation;
|
||||
SkipSpace();
|
||||
if (*input == 0) return Outcome::Input;
|
||||
const std::string field = Field(directive.width, [base](std::string_view text) { return IntegerFit(text, base); });
|
||||
if (IntegerFit(field, base) != Fit::Valid) return Outcome::Matching;
|
||||
input += field.size();
|
||||
if (target != nullptr) {
|
||||
const std::uint64_t value = isSigned ? static_cast<std::uint64_t>(std::strtoll(field.c_str(), nullptr, base)) : std::strtoull(field.c_str(), nullptr, base);
|
||||
std::memcpy(target, &value, size);
|
||||
++assigned;
|
||||
}
|
||||
converted = true;
|
||||
return Outcome::Done;
|
||||
}
|
||||
|
||||
Outcome Float(const Directive& directive) {
|
||||
static_assert(sizeof(long double) == 16 && std::numeric_limits<long double>::digits == 64);
|
||||
if (!directive.length.empty() && directive.length != "l" && directive.length != "L")
|
||||
throw std::invalid_argument("swscanf_s: invalid floating length modifier " + directive.length);
|
||||
void* target = nullptr;
|
||||
if (!directive.suppress && (target = args.Next<void*>()) == nullptr) return Outcome::Violation;
|
||||
SkipSpace();
|
||||
if (*input == 0) return Outcome::Input;
|
||||
const std::string field = Field(directive.width, FloatFit);
|
||||
if (FloatFit(field) != Fit::Valid) return Outcome::Matching;
|
||||
input += field.size();
|
||||
if (target != nullptr) {
|
||||
if (directive.length == "L") {
|
||||
const long double value = std::strtold(field.c_str(), nullptr);
|
||||
std::memcpy(target, &value, sizeof(value));
|
||||
} else if (directive.length == "l") {
|
||||
const double value = std::strtod(field.c_str(), nullptr);
|
||||
std::memcpy(target, &value, sizeof(value));
|
||||
} else {
|
||||
const float value = std::strtof(field.c_str(), nullptr);
|
||||
std::memcpy(target, &value, sizeof(value));
|
||||
}
|
||||
++assigned;
|
||||
}
|
||||
converted = true;
|
||||
return Outcome::Done;
|
||||
}
|
||||
|
||||
static bool InSet(const Directive& directive, char16_t character) {
|
||||
bool found = false;
|
||||
for (const char16_t* member = directive.setBegin; member != directive.setEnd && !found; ++member) found = *member == character;
|
||||
return found != directive.setExcluded;
|
||||
}
|
||||
|
||||
static Outcome Truncated(void* target, std::size_t capacity, bool wide) {
|
||||
if (capacity != 0) {
|
||||
if (wide) static_cast<char16_t*>(target)[0] = 0;
|
||||
else static_cast<char*>(target)[0] = 0;
|
||||
}
|
||||
return Outcome::Matching;
|
||||
}
|
||||
|
||||
Outcome Text(const Directive& directive) {
|
||||
if (!directive.length.empty() && directive.length != "l")
|
||||
throw std::invalid_argument("swscanf_s: invalid string length modifier " + directive.length);
|
||||
void* target = nullptr;
|
||||
std::size_t capacity = 0;
|
||||
if (!directive.suppress) {
|
||||
target = args.Next<void*>();
|
||||
capacity = args.Next<unsigned int>();
|
||||
if (target == nullptr) return Outcome::Violation;
|
||||
}
|
||||
if (directive.conversion == u's') SkipSpace();
|
||||
if (*input == 0) return Outcome::Input;
|
||||
std::u16string item;
|
||||
if (directive.conversion == u'c') {
|
||||
const std::size_t count = directive.width == 0 ? 1 : directive.width;
|
||||
while (item.size() < count && input[item.size()] != 0) item.push_back(input[item.size()]);
|
||||
if (item.size() < count) return Outcome::Matching;
|
||||
} else {
|
||||
const std::size_t limit = directive.width == 0 ? std::numeric_limits<std::size_t>::max() : directive.width;
|
||||
while (item.size() < limit && input[item.size()] != 0) {
|
||||
const char16_t next = input[item.size()];
|
||||
if (directive.conversion == u's' ? IsSpace(next) : !InSet(directive, next)) break;
|
||||
item.push_back(next);
|
||||
}
|
||||
if (item.empty()) return Outcome::Matching;
|
||||
}
|
||||
input += item.size();
|
||||
if (target == nullptr) {
|
||||
converted = true;
|
||||
return Outcome::Done;
|
||||
}
|
||||
const std::size_t terminator = directive.conversion == u'c' ? 0 : 1;
|
||||
if (directive.length == "l") {
|
||||
if (item.size() + terminator > capacity) return Truncated(target, capacity, true);
|
||||
std::memcpy(target, item.data(), item.size() * sizeof(char16_t));
|
||||
if (terminator != 0) static_cast<char16_t*>(target)[item.size()] = 0;
|
||||
} else {
|
||||
std::string bytes;
|
||||
for (const char16_t unit : item) {
|
||||
char byte[8];
|
||||
const std::size_t written = wcrtomb_nid_postfix(byte, unit, nullptr);
|
||||
if (written == static_cast<std::size_t>(-1)) throw std::invalid_argument("swscanf_s: wide character has no single-byte form");
|
||||
bytes.append(byte, written);
|
||||
}
|
||||
if (bytes.size() + terminator > capacity) return Truncated(target, capacity, false);
|
||||
std::memcpy(target, bytes.data(), bytes.size());
|
||||
if (terminator != 0) static_cast<char*>(target)[bytes.size()] = 0;
|
||||
}
|
||||
++assigned;
|
||||
converted = true;
|
||||
return Outcome::Done;
|
||||
}
|
||||
|
||||
Outcome Count(const Directive& directive) {
|
||||
if (directive.suppress || directive.width != 0) throw std::invalid_argument("swscanf_s: %n with suppression or width");
|
||||
const std::size_t size = IntegerSize(directive.length);
|
||||
void* target = args.Next<void*>();
|
||||
if (target == nullptr) return Outcome::Violation;
|
||||
const std::int64_t count = input - start;
|
||||
std::memcpy(target, &count, size);
|
||||
return Outcome::Done;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI swscanf_s_nid_postfix(const char16_t* buffer, const char16_t* format, ...) {
|
||||
if (buffer == nullptr || format == nullptr) return EOF;
|
||||
#ifdef _WIN32
|
||||
__builtin_sysv_va_list args;
|
||||
__builtin_sysv_va_start(args, format);
|
||||
#else
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
#endif
|
||||
try {
|
||||
const int result = WideScanner(buffer, format, args).Run();
|
||||
#ifdef _WIN32
|
||||
__builtin_sysv_va_end(args);
|
||||
#else
|
||||
va_end(args);
|
||||
#endif
|
||||
return result;
|
||||
} catch (...) {
|
||||
#ifdef _WIN32
|
||||
__builtin_sysv_va_end(args);
|
||||
#else
|
||||
va_end(args);
|
||||
#endif
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -443,6 +443,11 @@ unsigned long long APS5_VABI wcstoull_nid_postfix(const char16_t* str, char16_t*
|
|||
return ParseAsciiPrefix(str, endptr, [base](const char* text, char** end) { return StopAtPrefixLetter_nid_no_patch(text, end, base) ? 0ULL : std::strtoull(text, end, base); });
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI _WStoul_nid_postfix(const char16_t* str, char16_t** endptr, int base) {
|
||||
if (base < 0 || base == 1 || base > 36) throw std::invalid_argument("_WStoul: unsupported base " + std::to_string(base));
|
||||
return wcstoul_nid_postfix(str, endptr, base);
|
||||
}
|
||||
|
||||
int APS5_VABI wcscoll_nid_postfix(const char16_t* first, const char16_t* second) {
|
||||
return wcscmp_nid_postfix(first, second);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
std::uint64_t APS5_VABI _WStoul_nid_postfix(const char16_t*, char16_t**, int);
|
||||
int APS5_VABI _Iswctype_nid_postfix(std::uint32_t, std::uint64_t);
|
||||
int APS5_VABI swscanf_s_nid_postfix(const char16_t*, const char16_t*, ...);
|
||||
int APS5_VABI iswalnum_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswalpha_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswcntrl_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswdigit_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswgraph_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswlower_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswprint_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswpunct_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswspace_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswupper_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswxdigit_nid_postfix(std::uint32_t);
|
||||
int APS5_VABI iswblank_nid_postfix(std::uint32_t);
|
||||
}
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void Require(bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "%s\n", message);
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TCall> static bool Throws(TCall call) {
|
||||
try {
|
||||
call();
|
||||
} catch (const std::exception&) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void CheckStoul(const char16_t* text, int base, std::uint64_t value, std::size_t consumed, int error, const char* message) {
|
||||
char16_t* end = nullptr;
|
||||
errno = 0;
|
||||
const std::uint64_t result = _WStoul_nid_postfix(text, &end, base);
|
||||
const int saved = errno;
|
||||
if (result != value || end != text + consumed || saved != error) {
|
||||
std::fprintf(stderr, "_WStoul %s: value %llu, consumed %lld, errno %d\n", message,
|
||||
static_cast<unsigned long long>(result), static_cast<long long>(end - text), saved);
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckStoul() {
|
||||
constexpr std::uint64_t max = std::numeric_limits<std::uint64_t>::max();
|
||||
CheckStoul(u" 4294967296", 10, 4294967296ULL, 12, 0, "keeps 64 bits");
|
||||
CheckStoul(u"18446744073709551615", 10, max, 20, 0, "maximum");
|
||||
CheckStoul(u"18446744073709551616", 10, max, 20, ERANGE, "overflow");
|
||||
CheckStoul(u"-1", 10, max, 2, 0, "negation");
|
||||
CheckStoul(u"\t+7xyz", 10, 7, 3, 0, "stops at a non-digit");
|
||||
CheckStoul(u"0x1F", 16, 31, 4, 0, "hex prefix with base 16");
|
||||
CheckStoul(u"0x1f", 0, 31, 4, 0, "hex prefix with base 0");
|
||||
CheckStoul(u"017", 0, 15, 3, 0, "octal with base 0");
|
||||
CheckStoul(u"z", 36, 35, 1, 0, "base 36");
|
||||
CheckStoul(u"0b1", 0, 0, 1, 0, "no binary prefix");
|
||||
CheckStoul(u"0xg", 16, 0, 1, 0, "prefix without digits");
|
||||
CheckStoul(u"12é3", 10, 12, 2, 0, "stops at a non-ASCII unit");
|
||||
CheckStoul(u" 5", 10, 0, 0, 0, "non-ASCII space is not skipped");
|
||||
CheckStoul(u"", 10, 0, 0, 0, "empty string");
|
||||
Require(_WStoul_nid_postfix(u"9", nullptr, 10) == 9, "_WStoul accepts a null end pointer");
|
||||
Require(Throws([] { _WStoul_nid_postfix(u"1", nullptr, 1); }), "_WStoul throws for base 1");
|
||||
Require(Throws([] { _WStoul_nid_postfix(u"1", nullptr, 37); }), "_WStoul throws for base 37");
|
||||
}
|
||||
|
||||
static void CheckClassification() {
|
||||
int (APS5_VABI* const classes[])(std::uint32_t) = {
|
||||
iswalnum_nid_postfix, iswalpha_nid_postfix, iswcntrl_nid_postfix, iswdigit_nid_postfix,
|
||||
iswgraph_nid_postfix, iswlower_nid_postfix, iswprint_nid_postfix, iswpunct_nid_postfix,
|
||||
iswspace_nid_postfix, iswupper_nid_postfix, iswxdigit_nid_postfix, iswblank_nid_postfix,
|
||||
};
|
||||
for (std::uint64_t type = 1; type <= 12; ++type) {
|
||||
for (std::uint32_t c = 0; c < 0x300; ++c) {
|
||||
if ((_Iswctype_nid_postfix(c, type) != 0) != (classes[type - 1](c) != 0)) {
|
||||
std::fprintf(stderr, "_Iswctype(%#x, %llu) differs from its isw function\n", c, static_cast<unsigned long long>(type));
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
Require(_Iswctype_nid_postfix(0xffffffffu, type) == 0, "_Iswctype is false for WEOF");
|
||||
}
|
||||
Require(_Iswctype_nid_postfix(u'A', 2) != 0 && _Iswctype_nid_postfix(u'1', 2) == 0, "_Iswctype alpha");
|
||||
Require(_Iswctype_nid_postfix(u'7', 4) != 0 && _Iswctype_nid_postfix(u'f', 11) != 0, "_Iswctype digit and xdigit");
|
||||
Require(_Iswctype_nid_postfix(u'\t', 12) != 0 && _Iswctype_nid_postfix(u'\n', 12) == 0, "_Iswctype blank");
|
||||
Require(_Iswctype_nid_postfix(u'\n', 9) != 0 && _Iswctype_nid_postfix(u' ', 5) == 0, "_Iswctype space and graph");
|
||||
Require(_Iswctype_nid_postfix(u'a', 6) != 0 && _Iswctype_nid_postfix(u'a', 10) == 0, "_Iswctype lower and upper");
|
||||
Require(_Iswctype_nid_postfix(0x7f, 3) != 0 && _Iswctype_nid_postfix(u'!', 8) != 0, "_Iswctype cntrl and punct");
|
||||
Require(_Iswctype_nid_postfix(0xe9, 2) == 0 && _Iswctype_nid_postfix(0x3042, 1) == 0, "_Iswctype C locale has no non-ASCII letters");
|
||||
Require(Throws([] { _Iswctype_nid_postfix(u'a', 0); }), "_Iswctype throws for class 0");
|
||||
Require(Throws([] { _Iswctype_nid_postfix(u'a', 13); }), "_Iswctype throws for class 13");
|
||||
Require(Throws([] { _Iswctype_nid_postfix(u'a', 0x100); }), "_Iswctype throws for a rune mask");
|
||||
}
|
||||
|
||||
static void CheckScan() {
|
||||
int first = 0;
|
||||
int second = 0;
|
||||
Require(swscanf_s_nid_postfix(u" 42 -7", u"%d%d", &first, &second) == 2 && first == 42 && second == -7, "%d");
|
||||
Require(swscanf_s_nid_postfix(u"x=0x1f", u"x=%i", &first) == 1 && first == 31, "%i with a hex prefix");
|
||||
Require(swscanf_s_nid_postfix(u"1 2", u"%*d%d", &first) == 1 && first == 2, "suppressed %d");
|
||||
Require(swscanf_s_nid_postfix(u"12345", u"%3d%d", &first, &second) == 2 && first == 123 && second == 45, "%d width");
|
||||
std::int64_t wide = 0;
|
||||
Require(swscanf_s_nid_postfix(u"5000000000", u"%ld", &wide) == 1 && wide == 5000000000LL, "%ld is 64-bit");
|
||||
short half = 0;
|
||||
Require(swscanf_s_nid_postfix(u"-2", u"%hd", &half) == 1 && half == -2, "%hd");
|
||||
unsigned value = 0;
|
||||
Require(swscanf_s_nid_postfix(u"0xg", u"%x", &value) == 0, "%x prefix without digits is a matching failure");
|
||||
|
||||
char16_t text[6] = {u'?'};
|
||||
Require(swscanf_s_nid_postfix(u"hello world", u"%ls", text, std::size_t{6}) == 1 && std::u16string(text) == u"hello", "%ls fits");
|
||||
char16_t small[5] = {u'?', u'?', u'?', u'?', u'?'};
|
||||
Require(swscanf_s_nid_postfix(u"hello", u"%ls", small, std::size_t{5}) == 0 && small[0] == 0, "%ls too small");
|
||||
Require(swscanf_s_nid_postfix(u"7 hello", u"%d%ls", &first, small, std::size_t{5}) == 1 && first == 7, "%ls too small after a conversion");
|
||||
char narrow[4] = {};
|
||||
Require(swscanf_s_nid_postfix(u"abc def", u"%s", narrow, std::size_t{4}) == 1 && std::strcmp(narrow, "abc") == 0, "%s");
|
||||
char16_t pair[3] = {u'?', u'?', u'?'};
|
||||
Require(swscanf_s_nid_postfix(u"xyz", u"%2lc", pair, std::size_t{2}) == 1 && pair[0] == u'x' && pair[1] == u'y' && pair[2] == u'?', "%2lc");
|
||||
Require(swscanf_s_nid_postfix(u"xyz", u"%2lc", pair, std::size_t{1}) == 0, "%2lc too small");
|
||||
Require(swscanf_s_nid_postfix(u"x", u"%2lc", pair, std::size_t{2}) == 0, "%2lc short input");
|
||||
Require(swscanf_s_nid_postfix(u"cab12", u"%l[abc]%d", text, std::size_t{6}, &first) == 2 && std::u16string(text) == u"cab" && first == 12, "%l[");
|
||||
Require(swscanf_s_nid_postfix(u"été!", u"%l[^!]", text, std::size_t{6}) == 1 && std::u16string(text) == u"été", "%l[^");
|
||||
std::int64_t count = -1;
|
||||
Require(swscanf_s_nid_postfix(u" 12 ab", u"%d %ln", &first, &count) == 1 && count == 4, "%ln");
|
||||
|
||||
double real = 0;
|
||||
Require(swscanf_s_nid_postfix(u"1.5e3", u"%lf", &real) == 1 && real == 1500.0, "%lf");
|
||||
Require(swscanf_s_nid_postfix(u"1e+x", u"%lf", &real) == 0, "%lf incomplete exponent is a matching failure");
|
||||
long double extended = 0;
|
||||
Require(swscanf_s_nid_postfix(u"2.25", u"%Lf", &extended) == 1 && extended == 2.25L, "%Lf");
|
||||
float single = 0;
|
||||
Require(swscanf_s_nid_postfix(u"-inf", u"%f", &single) == 1 && single == -std::numeric_limits<float>::infinity(), "%f infinity");
|
||||
|
||||
Require(swscanf_s_nid_postfix(u"a", u"b%d", &first) == 0, "literal mismatch is a matching failure");
|
||||
Require(swscanf_s_nid_postfix(u"", u"%d", &first) == -1, "empty input is an input failure");
|
||||
Require(swscanf_s_nid_postfix(u" ", u"%ls", text, std::size_t{6}) == -1, "white space only is an input failure");
|
||||
Require(swscanf_s_nid_postfix(u"5", u"%d%d", &first, &second) == 1, "input failure after a conversion");
|
||||
Require(swscanf_s_nid_postfix(u"5", u"%d", static_cast<int*>(nullptr)) == -1, "null %d argument");
|
||||
Require(swscanf_s_nid_postfix(u"5 abc", u"%d%ls", &first, static_cast<char16_t*>(nullptr), std::size_t{4}) == -1, "null %ls argument");
|
||||
Require(swscanf_s_nid_postfix(nullptr, u"%d", &first) == -1, "null buffer");
|
||||
Require(swscanf_s_nid_postfix(u"5", nullptr) == -1, "null format");
|
||||
Require(Throws([&] { swscanf_s_nid_postfix(u"abc", u"%l[a-c]", text, std::size_t{6}); }), "scanset range throws");
|
||||
Require(Throws([&] { swscanf_s_nid_postfix(u"5", u"%Ld", &wide); }), "%Ld throws");
|
||||
Require(Throws([&] { swscanf_s_nid_postfix(u"Ā", u"%s", narrow, std::size_t{4}); }), "unconvertible narrow %s throws");
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckStoul();
|
||||
CheckClassification();
|
||||
CheckScan();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
|
@ -487,3 +487,4 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [AJM](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp), [APR](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) and [NGS2](../../core/libs/prx/libSceNgs2.native/src/System.cpp) state is allocated with `new` and never freed on purpose: title threads still call into these libraries while `std::exit` runs static destructors, so ordinary statics would be destroyed under them. Keep them leaked; `guest_ajm_teardown` covers the AJM case on Unix.
|
||||
- [Null vertex buffer V#](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) (`NUM_RECORDS` 0, `DST_SEL` 0 selects 0), not measured on hardware.
|
||||
- [_fstatfs](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) fills the FreeBSD 11 `struct statfs` of the PS5 payload SDK with the host volume that holds the file: block size, transfer size, total, free and available blocks, maximum name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0, as NTFS has no fixed number of files). The filesystem type number and name, mount paths, owner, filesystem id and I/O counters are left 0 or empty, since they would describe a host mount, not a console one. Sockets and pipes return `EINVAL`, as FreeBSD's `getvnode` does; a Windows descriptor that is neither a file nor a pipe throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields
|
||||
- [_WStoul](../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../core/libs/prx/libc/src/RuneLocale.cpp) and [swscanf_s](../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. It throws for `%p`, `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert
|
||||
|
|
|
|||
Loading…
Reference in New Issue