From f54619e78ecc4c4ccbe15338b7ac558bdfd54f05 Mon Sep 17 00:00:00 2001 From: "Enzo Persillet (Tutez)" Date: Wed, 30 Sep 2026 18:45:28 +0200 Subject: [PATCH 1/2] Barrier the pointer stores of a cppia field set by name. CppiaVar::setValue, behind Reflect.setField and Reflect.setProperty on a cppia instance, stored object and string pointers without a generational write barrier. A young value set on an old instance was reachable only through it, so the next minor collection freed it, and the instance then pointed at reused memory. The JIT and interpreted field setters already had the barrier. Co-Authored-By: Claude Opus 5.5 --- src/hx/cppia/CppiaVars.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/hx/cppia/CppiaVars.cpp b/src/hx/cppia/CppiaVars.cpp index ad98ca47e..29bb1d4c8 100644 --- a/src/hx/cppia/CppiaVars.cpp +++ b/src/hx/cppia/CppiaVars.cpp @@ -227,7 +227,12 @@ Dynamic CppiaVar::setValue(hx::Object *inThis, Dynamic inValue) case fsInt: *(int *)(base) = inValue; return inValue; case fsBool: *(bool *)(base) = inValue; return inValue; case fsFloat: *(Float *)(base) = inValue; return inValue; - case fsString: *(String *)(base) = inValue; return inValue; + case fsString: + *(String *)(base) = inValue; + #ifdef HXCPP_GC_GENERATIONAL + HX_OBJ_WB_GET(inThis, hx::PointerOf(*(String *)(base))); + #endif + return inValue; case fsObject: switch(type->arrayType) { @@ -259,6 +264,10 @@ Dynamic CppiaVar::setValue(hx::Object *inThis, Dynamic inValue) *(Array *)(base) = inValue; break; } + // Every case above stores one object pointer at base. + #ifdef HXCPP_GC_GENERATIONAL + HX_OBJ_WB_GET(inThis, *(hx::Object **)(base)); + #endif return inValue; case fsUnknown: break; From 68322a0da684a074f1a26e05cb5dc97529c909a5 Mon Sep 17 00:00:00 2001 From: "Enzo Persillet (Tutez)" Date: Sat, 3 Oct 2026 17:05:09 +0200 Subject: [PATCH 2/2] Test a set by name on an old cppia instance, with a generational GC. The cppia host now builds with HXCPP_GC_GENERATIONAL. ClientHolder grows old through full collections, then the host sets its array field with Reflect.setField, and a weak reference checks that a generational collection keeps the array. Without the write barrier, it is freed while the holder still points at it. Co-Authored-By: Claude Opus 5.5 --- test/cppia/Client.hx | 7 +++++++ test/cppia/cases/TestCommon.hx | 23 +++++++++++++++++++++++ test/cppia/compile-host.hxml | 1 + 3 files changed, 31 insertions(+) diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 710373f4b..7a3944a2f 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -14,6 +14,13 @@ class ClientFoo implements IFoo { } } +class ClientHolder { + + public var values:Array; + + public function new() {} +} + class Client { public static var clientBool0 = true; diff --git a/test/cppia/cases/TestCommon.hx b/test/cppia/cases/TestCommon.hx index eaacb5a76..30cd0171c 100644 --- a/test/cppia/cases/TestCommon.hx +++ b/test/cppia/cases/TestCommon.hx @@ -1,6 +1,8 @@ package cases; import cpp.cppia.Host; +import cpp.vm.Gc; +import cpp.vm.WeakRef; import utest.Test; import utest.Assert; @@ -59,6 +61,27 @@ class TestCommon extends Test { Assert.equals(2, Common.callbackSet, 'Bad cppia closure'); } + @:depends(testStatus) + function testSetFieldOfOldObject() { + final holder = Type.createInstance(Type.resolveClass('ClientHolder'), []); + + // Full collections make the holder old: a generational collection no longer traverses it. + for (_ in 0...3) { + Gc.run(true); + } + final values = setValues(holder); + Gc.run(false); + + Assert.notNull(values.get(), 'Freed a value set by name on an old object'); + } + + // Only a weak reference to the value leaves this frame, so that the stack does not keep it. + static function setValues(holder:Dynamic):WeakRef> { + final values = [42]; + Reflect.setField(holder, 'values', values); + return new WeakRef(values); + } + @:depends(testStatus) function testInterfaceCalling() { final obj : IFoo = Type.createInstance(Type.resolveClass('ClientFoo'), []); diff --git a/test/cppia/compile-host.hxml b/test/cppia/compile-host.hxml index 76bf2d790..051e26186 100644 --- a/test/cppia/compile-host.hxml +++ b/test/cppia/compile-host.hxml @@ -1,6 +1,7 @@ -m CppiaHost HostExtendedRoot -D scriptable +-D HXCPP_GC_GENERATIONAL -D dll_export=host_classes.info -L utest --dce no