HaxeFoundation / HaxeFoundation/haxe

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

Open
#12,984 1 comment 1 reaction 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.