fix(ground-suite): handle failed UTF-16 conversion sizes
Reject failed UTF-16 sizing queries before allocating the UTF-8 result and preserve terminating-NUL accounting. Validation: nine host-stub cases with 16-bit wchar_t; native Windows execution remains unverified.
This commit is contained in:
1 parent
a78754d909
commit
4953f0a974
2 files changed
+131
-7
No files matched your search
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run the production conversion function with a controlled Windows API stub.
|
||||||
|
|
||||||
|
This host check compiles only Utf8FromUtf16 from utils.cpp. It checks API arguments,
|
||||||
|
counts, and failure handling using 16-bit wchar_t and known UTF-16/UTF-8 data. It
|
||||||
|
does not build the Windows runner or validate Windows' Unicode implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
PREFIX = r"""
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static_assert(sizeof(wchar_t) == 2, "test requires UTF-16 code units");
|
||||||
|
constexpr unsigned CP_UTF8 = 65001;
|
||||||
|
constexpr unsigned WC_ERR_INVALID_CHARS = 0x80;
|
||||||
|
static const wchar_t* expected_source;
|
||||||
|
static std::string expected_utf8;
|
||||||
|
static int query_result;
|
||||||
|
static int conversion_result;
|
||||||
|
static int calls;
|
||||||
|
|
||||||
|
int WideCharToMultiByte(unsigned code_page, unsigned flags, const wchar_t* source,
|
||||||
|
int input_length, char* target, int capacity,
|
||||||
|
const char* default_char, int* used_default_char) {
|
||||||
|
++calls;
|
||||||
|
assert(code_page == CP_UTF8 && flags == WC_ERR_INVALID_CHARS);
|
||||||
|
assert(source == expected_source && input_length == -1);
|
||||||
|
assert(default_char == nullptr && used_default_char == nullptr);
|
||||||
|
if (target == nullptr) {
|
||||||
|
assert(calls == 1 && capacity == 0);
|
||||||
|
return query_result;
|
||||||
|
}
|
||||||
|
assert(calls == 2 && capacity == query_result);
|
||||||
|
assert(capacity == static_cast<int>(expected_utf8.size()) + 1);
|
||||||
|
std::memcpy(target, expected_utf8.c_str(), expected_utf8.size() + 1);
|
||||||
|
return conversion_result;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
SUFFIX = r"""
|
||||||
|
void configure(const wchar_t* source, const std::string& expected, int query,
|
||||||
|
int converted) {
|
||||||
|
expected_source = source;
|
||||||
|
expected_utf8 = expected;
|
||||||
|
query_result = query;
|
||||||
|
conversion_result = converted;
|
||||||
|
calls = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
assert(Utf8FromUtf16(nullptr).empty());
|
||||||
|
assert(calls == 0);
|
||||||
|
|
||||||
|
const wchar_t empty[] = {0};
|
||||||
|
configure(empty, "", 1, 1);
|
||||||
|
assert(Utf8FromUtf16(empty).empty());
|
||||||
|
assert(calls == 2);
|
||||||
|
|
||||||
|
// Independent known encoding: A, U+00E9, U+1F642 (a surrogate pair).
|
||||||
|
const wchar_t text[] = {0x0041, 0x00e9, 0xd83d, 0xde42, 0};
|
||||||
|
const std::string utf8 = "A\xc3\xa9\xf0\x9f\x99\x82";
|
||||||
|
configure(text, utf8, 8, 8);
|
||||||
|
assert(Utf8FromUtf16(text) == utf8);
|
||||||
|
assert(calls == 2);
|
||||||
|
|
||||||
|
// WC_ERR_INVALID_CHARS reports an isolated surrogate as a zero query result.
|
||||||
|
const wchar_t invalid[] = {0xd800, 0};
|
||||||
|
for (int failed : {0, -1}) {
|
||||||
|
configure(invalid, "", failed, 0);
|
||||||
|
assert(Utf8FromUtf16(invalid).empty());
|
||||||
|
assert(calls == 1);
|
||||||
|
}
|
||||||
|
for (int failed_or_mismatched : {0, -1, 7, 9}) {
|
||||||
|
configure(text, utf8, 8, failed_or_mismatched);
|
||||||
|
assert(Utf8FromUtf16(text).empty());
|
||||||
|
assert(calls == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--work-dir", type=Path, required=True)
|
||||||
|
parser.add_argument("--cxx", default="c++")
|
||||||
|
args = parser.parse_args()
|
||||||
|
work = args.work_dir.resolve()
|
||||||
|
work.mkdir(parents=True, exist_ok=True)
|
||||||
|
source = Path(__file__).with_name("utils.cpp").read_text()
|
||||||
|
start = source.index("std::string Utf8FromUtf16(")
|
||||||
|
end = source.index("\n}", start) + 2
|
||||||
|
fixture = work / "utf8_from_utf16.cpp"
|
||||||
|
fixture.write_text(PREFIX + source[start:end] + SUFFIX)
|
||||||
|
binary = work / "utf8_from_utf16_test"
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
args.cxx,
|
||||||
|
"-std=c++17",
|
||||||
|
"-fshort-wchar",
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
"-Werror",
|
||||||
|
"-pedantic",
|
||||||
|
str(fixture),
|
||||||
|
"-o",
|
||||||
|
str(binary),
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
subprocess.run([str(binary)], check=True)
|
||||||
|
print("Utf8FromUtf16 host stub checks passed (9 cases, 16-bit wchar_t)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -45,21 +45,24 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
|||||||
if (utf16_string == nullptr) {
|
if (utf16_string == nullptr) {
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
unsigned int target_length = ::WideCharToMultiByte(
|
const int target_length = ::WideCharToMultiByte(
|
||||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||||
-1, nullptr, 0, nullptr, nullptr)
|
-1, nullptr, 0, nullptr, nullptr);
|
||||||
-1; // remove the trailing null character
|
if (target_length <= 0) {
|
||||||
int input_length = (int)wcslen(utf16_string);
|
return std::string();
|
||||||
|
}
|
||||||
std::string utf8_string;
|
std::string utf8_string;
|
||||||
if (target_length == 0 || target_length > utf8_string.max_size()) {
|
if (static_cast<size_t>(target_length) > utf8_string.max_size()) {
|
||||||
return utf8_string;
|
return utf8_string;
|
||||||
}
|
}
|
||||||
|
// With a terminated input, both calls include the trailing null character.
|
||||||
utf8_string.resize(target_length);
|
utf8_string.resize(target_length);
|
||||||
int converted_length = ::WideCharToMultiByte(
|
int converted_length = ::WideCharToMultiByte(
|
||||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||||
input_length, utf8_string.data(), target_length, nullptr, nullptr);
|
-1, utf8_string.data(), target_length, nullptr, nullptr);
|
||||||
if (converted_length == 0) {
|
if (converted_length != target_length) {
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
utf8_string.resize(static_cast<size_t>(converted_length) - 1);
|
||||||
return utf8_string;
|
return utf8_string;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user