From 81852b1d12e4c2d44e428f762e69cb785a614369 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Mon, 25 Aug 2025 11:41:51 +0100 Subject: [PATCH 1/4] Avoid duplicate string iteration in TCopyString TConvertToUTF8 treats strings as null terminating if the length is 0, so rather than calculating the size manually we can set it to 0 if it the input to TCopyString is -1. For GCStringDup, -1 is handled as null terminating, but 0 is treated as a 0 length string. We can pass the length argument straight in without modification there. This avoids duplicate loops in both the TConvertToUTF8 and GCStringDup cases. --- include/hxString.h | 1 + src/String.cpp | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/hxString.h b/include/hxString.h index b53ddd4a3..b0ef9e8d3 100644 --- a/include/hxString.h +++ b/include/hxString.h @@ -42,6 +42,7 @@ class HXCPP_EXTERN_CLASS_ATTRIBUTES String inline String(const char16_t *inPtr) { *this = create(inPtr); } inline String(const char *inPtr) { *this = create(inPtr); } + // If inLen is -1, the input string is treated as null terminated. static String create(const wchar_t *inPtr,int inLen=-1); static String create(const char16_t *inPtr,int inLen=-1); static String create(const char *inPtr,int inLen=-1); diff --git a/src/String.cpp b/src/String.cpp index 4dc69a23b..58b64a003 100644 --- a/src/String.cpp +++ b/src/String.cpp @@ -446,9 +446,6 @@ inline String TCopyString(const T *inString,int inLength) return String(); #ifndef HX_SMART_STRINGS - if (inLength<0) - for(inLength=0; !inString[inLength]; inString++) { } - if (sizeof(T)==1) { int len = 0; @@ -457,7 +454,7 @@ inline String TCopyString(const T *inString,int inLength) } else { - int length = inLength; + int length = inLength > 0 ? inLength : 0; const char *ptr = TConvertToUTF8(inString, &length, 0, true ); return String(ptr,length); } From 4ce4af3c3293123dd440ba56a3b60ecce2f21b32 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Tue, 26 Aug 2025 18:14:36 +0100 Subject: [PATCH 2/4] Add test for String::create unspecified length --- .github/workflows/test.yml | 16 ++++++++ test/regression/Issue849/Main.hx | 10 +++++ test/regression/Issue849/build.hxml | 3 ++ test/regression/Issue849/stdout.txt | 3 ++ test/regression/Run.hx | 60 +++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 test/regression/Issue849/Main.hx create mode 100644 test/regression/Issue849/build.hxml create mode 100644 test/regression/Issue849/stdout.txt create mode 100644 test/regression/Run.hx diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e037b3f49..e9c91316e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -231,3 +231,19 @@ jobs: run: haxe compile-cpp.hxml -D ${{ env.HXCPP_ARCH_FLAG }} -D no_http - name: run run: bin${{ inputs.sep }}cpp${{ inputs.sep }}TestMain-debug + + regression: + runs-on: ${{ inputs.os }} + name: regression tests + defaults: + run: + working-directory: test/regression + steps: + - name: checkout + uses: actions/checkout@v4 + - name: setup + uses: ./.github/workflows/setup + with: + haxe: ${{ inputs.haxe }} + - name: run + run: haxe --run Run -D ${{ env.HXCPP_ARCH_FLAG }} diff --git a/test/regression/Issue849/Main.hx b/test/regression/Issue849/Main.hx new file mode 100644 index 000000000..39af8d2ef --- /dev/null +++ b/test/regression/Issue849/Main.hx @@ -0,0 +1,10 @@ +function main() { + // char + trace(untyped __cpp__('::String::create("Hello world")')); + + // wchar_t + trace(untyped __cpp__('::String::create(L"Hello world")')); + + // char16_t + trace(untyped __cpp__('::String::create(u"Hello world")')); +} diff --git a/test/regression/Issue849/build.hxml b/test/regression/Issue849/build.hxml new file mode 100644 index 000000000..542c501e0 --- /dev/null +++ b/test/regression/Issue849/build.hxml @@ -0,0 +1,3 @@ +-cpp bin +-D disable-unicode-strings +-m Main diff --git a/test/regression/Issue849/stdout.txt b/test/regression/Issue849/stdout.txt new file mode 100644 index 000000000..d3bf90c6f --- /dev/null +++ b/test/regression/Issue849/stdout.txt @@ -0,0 +1,3 @@ +Main.hx:3: Hello world +Main.hx:6: Hello world +Main.hx:9: Hello world diff --git a/test/regression/Run.hx b/test/regression/Run.hx new file mode 100644 index 000000000..a233a18c7 --- /dev/null +++ b/test/regression/Run.hx @@ -0,0 +1,60 @@ +import sys.io.Process; +import sys.io.File; +import sys.FileSystem; + +using StringTools; + +function runOutput(test:String):String { + final slash = Sys.systemName() == "Windows" ? "\\" : "/"; + final proc = new Process([test, "bin", 'Main'].join(slash)); + final code = proc.exitCode(); + + if (code != 0) { + throw 'return code was $code'; + } + + return proc.stdout.readAll().toString().replace("\r\n", "\n"); +} + +function main() { + var successes = 0; + var total = 0; + + final args = Sys.args(); + + for (test in FileSystem.readDirectory(".")) { + if (!FileSystem.isDirectory(test)) { + continue; + } + + total++; + + final buildExitCode = Sys.command("haxe", ["-C", test, "build.hxml"].concat(args)); + if (buildExitCode != 0) { + Sys.println('Failed to build test $test. Exit code: $buildExitCode'); + continue; + } + + final expectedStdout = File.getContent('$test/stdout.txt').replace("\r\n", "\n"); + final actualStdout = try { + runOutput(test); + } catch (e) { + Sys.println('Test $test failed: $e'); + continue; + }; + + if (actualStdout != expectedStdout) { + Sys.println('Test $test failed: Output did not match'); + + Sys.println("Expected stdout:"); + Sys.println(expectedStdout); + Sys.println("Actual stdout:"); + Sys.println(actualStdout); + continue; + } + + successes++; + } + + Sys.println('Regression tests complete. Successes: $successes / $total'); +} From ac350b020b29d8ced0d5343c46e575b4ce0d79b6 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Wed, 27 Aug 2025 18:59:06 +0100 Subject: [PATCH 3/4] Fix String::create(_, 0) without HX_SMART_STRINGS When smart strings are enabled, this always returns an empty string, however, if they are disabled this currently has different behaviour depending on the type of character passed in. This fixes the behaviour so that it also returns an empty string for wchar_t and char16_t strings when smart strings are disabled. --- src/String.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/String.cpp b/src/String.cpp index 58b64a003..4473d845b 100644 --- a/src/String.cpp +++ b/src/String.cpp @@ -454,6 +454,9 @@ inline String TCopyString(const T *inString,int inLength) } else { + if (inLength == 0) { + return String::emptyString; + } int length = inLength > 0 ? inLength : 0; const char *ptr = TConvertToUTF8(inString, &length, 0, true ); return String(ptr,length); From 82dae602be5ecdcce30ee646722186a41a9e1a58 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Wed, 27 Aug 2025 19:03:40 +0100 Subject: [PATCH 4/4] Add checks for String::create with length 0 --- test/regression/Issue849/Main.hx | 11 +++++++++++ test/regression/Issue849/stdout.txt | 3 +++ 2 files changed, 14 insertions(+) diff --git a/test/regression/Issue849/Main.hx b/test/regression/Issue849/Main.hx index 39af8d2ef..f2e758d2f 100644 --- a/test/regression/Issue849/Main.hx +++ b/test/regression/Issue849/Main.hx @@ -7,4 +7,15 @@ function main() { // char16_t trace(untyped __cpp__('::String::create(u"Hello world")')); + + // explicit 0 length + + // char + trace(untyped __cpp__('::String::create("Hello world", 0)')); + + // wchar_t + trace(untyped __cpp__('::String::create(L"Hello world", 0)')); + + // char16_t + trace(untyped __cpp__('::String::create(u"Hello world", 0)')); } diff --git a/test/regression/Issue849/stdout.txt b/test/regression/Issue849/stdout.txt index d3bf90c6f..c1214794c 100644 --- a/test/regression/Issue849/stdout.txt +++ b/test/regression/Issue849/stdout.txt @@ -1,3 +1,6 @@ Main.hx:3: Hello world Main.hx:6: Hello world Main.hx:9: Hello world +Main.hx:14: +Main.hx:17: +Main.hx:20: