Summary
For a (get, set) property whose backing field has a different name, the JVM
target's generated reflection dispatchers _hx_getField / _hx_setField
contain a case "<propertyName>" branch that performs a direct field access
(getfield/putfield) using the property name. No field of that name exists
(the physical field is the differently-named backing var), so the emitted
bytecode references a non-existent field.
On the JVM this is only tolerated because those branches are never executed
(field refs are resolved lazily, at execution time). Under any strict
ahead-of-time / whole-program linker (e.g. TeaVM) every such reference is a hard
link error: Field Foo.vo was not found.
Reproduction
Main.hx:
class Foo {
public var vo(get, set):Int;
var _vo:Int = 0;
inline function get_vo():Int return _vo;
function set_vo(v:Int):Int { _vo = v; return v; }
public function new() {}
}
class Main {
static function main() {
var f = new Foo();
Reflect.setField(f, "vo", 5); // keeps _hx_setField
trace(Reflect.field(f, "vo")); // keeps _hx_getField
}
}
haxe -main Main --jvm Main.jar -dce no
Haxe version: 5.0.0-preview.1+957b2c7
Observed bytecode
Foo declares exactly one field:
There is no field vo. Yet the generated dispatchers do:
_hx_getField:
140: getfield #49 // Field vo:I <-- non-existent field
...
118: getfield #8 // Field _vo:I <-- the real field (correct)
_hx_setField:
46: putfield #49 // Field vo:I <-- non-existent field
57: putfield #8 // Field _vo:I <-- the real field (correct)
Decompiled, the dispatcher makes the intent obvious — the property-name case
does a direct field access instead of routing through the accessor:
// _hx_getField
case 3769: if (name.equals("vo")) return this.vo; // getfield vo -> no such field
case 95064: if (name.equals("_vo")) return this._vo; // correct
// _hx_setField
case 3769: this.vo = (RUb)value; // putfield vo -> no such field
case 95064: this._vo = (RUb)value; // correct
Expected
For a (get, set) property with no physical field of that name, the
"<propertyName>" case should either be omitted, or route through the accessor
methods (get_vo() / set_vo()) — the same way method members are already
handled via Jvm.readFieldClosure. It must not emit getfield/putfield on a
field that does not exist.
Impact
- Latent even on the JVM:
Reflect.field(obj, "vo") on such a property throws
NoSuchFieldError at runtime; it only stays hidden because callers use the
property directly rather than reflecting on it by name.
- Fatal on strict AOT linkers: a real project produced 1888
Field X was not found errors under TeaVM, one getfield + one putfield
per affected property, blocking the whole WASM link.
Summary
For a
(get, set)property whose backing field has a different name, the JVMtarget's generated reflection dispatchers
_hx_getField/_hx_setFieldcontain a
case "<propertyName>"branch that performs a direct field access(
getfield/putfield) using the property name. No field of that name exists(the physical field is the differently-named backing var), so the emitted
bytecode references a non-existent field.
On the JVM this is only tolerated because those branches are never executed
(field refs are resolved lazily, at execution time). Under any strict
ahead-of-time / whole-program linker (e.g. TeaVM) every such reference is a hard
link error:
Field Foo.vo was not found.Reproduction
Main.hx:Haxe version:
5.0.0-preview.1+957b2c7Observed bytecode
Foodeclares exactly one field:There is no field
vo. Yet the generated dispatchers do:_hx_getField:_hx_setField:Decompiled, the dispatcher makes the intent obvious — the property-name case
does a direct field access instead of routing through the accessor:
Expected
For a
(get, set)property with no physical field of that name, the"<propertyName>"case should either be omitted, or route through the accessormethods (
get_vo()/set_vo()) — the same way method members are alreadyhandled via
Jvm.readFieldClosure. It must not emitgetfield/putfieldon afield that does not exist.
Impact
Reflect.field(obj, "vo")on such a property throwsNoSuchFieldErrorat runtime; it only stays hidden because callers use theproperty directly rather than reflecting on it by name.
Field X was not founderrors under TeaVM, onegetfield+ oneputfieldper affected property, blocking the whole WASM link.