Skip to content

genjvm: _hx_getField/_hx_setField emit getfield/putfield on the property name for (get,set) properties, referencing a non-existent field #12984

Description

@barisyild

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:

public int _vo;

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions