HaxeFoundation / HaxeFoundation/haxe
genjvm: `_hx_getField`/`_hx_setField` emit `getfield`/`putfield` on the property name for `(get,set)` properties, referencing a non-existent field
- Dominant language
- Haxe
- Stars
- 6.9k
- Forks
- 715
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 11
Description
## 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 ""` 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
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:
```java
// _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
`""` 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.
Contributor guide
Research direction
Reproduce the issue with Main.hx using `haxe -main Main --jvm Main.jar -dce no`, then inspect the JVM target code that generates `_hx_getField` and `_hx_setField`, including the existing `Jvm.readFieldClosure` handling. Done means `(get, set)` properties with differently named backing fields produce no invalid property-name `getfield`/`putfield` references and reflection works without missing-field errors.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100