Skip to content

Fix proxy defineProperty descriptors to omit absent fields#1936

Closed
Kenaz123 wants to merge 2 commits intofacebook:static_hfrom
Kenaz123:fix-proxy-accessor-descriptor
Closed

Fix proxy defineProperty descriptors to omit absent fields#1936
Kenaz123 wants to merge 2 commits intofacebook:static_hfrom
Kenaz123:fix-proxy-accessor-descriptor

Conversation

@Kenaz123
Copy link
Copy Markdown

Summary

Fix proxy defineProperty descriptor conversion to omit absent fields.

Before this change, objectFromPropertyDescriptor() in lib/VM/Operations.cpp would materialize some descriptor fields unconditionally based on descriptor kind, instead of only emitting fields that were actually present in the input
property descriptor.

For data descriptors, it always emitted value, even when [[Value]] was not present:

if (!dpFlags.isAccessor()) {
  // Data Descriptor
  auto result = JSObject::defineOwnProperty(
      obj,
      runtime,
      Predefined::getSymbolID(Predefined::value),
      dpf,
      valueOrAccessor,
      PropOpFlags().plusThrowOnError());
  ...
}

For accessor descriptors, it always emitted both get and set, even when one side was absent:

auto result = JSObject::defineOwnProperty(
    obj,
    runtime,
    Predefined::getSymbolID(Predefined::get),
    dpf,
    getter,
    PropOpFlags().plusThrowOnError());
...

result = JSObject::defineOwnProperty(
    obj,
    runtime,
    Predefined::getSymbolID(Predefined::set),
    dpf,
    setter,
    PropOpFlags().plusThrowOnError());

That is incorrect for forwarding proxies. When a proxy defineProperty trap receives a descriptor object, absent fields must stay absent. Emitting them unconditionally changes semantics:

  • Accessor-only updates could inject set: undefined and silently remove an existing setter.
  • Attribute-only data updates could inject value: undefined and silently clobber an existing property value.

This change fixes both cases by only emitting value, get, and set when their corresponding DefinePropertyFlags bits are set.

accessor-poc.js

Original accessor PoC reproduction:

./build/bin/hermes ./accessor-poc.js

Original accessor output before the fix:

BUG CONFIRMED: setter was silently removed (spurious 'set: undefined' injected into descriptor)

value-poc.js

Original value PoC reproduction:

./build/bin/hermes ./value-poc.js

Original value output before the fix:

target.x = undefined

Test Plan

cmake -S ./hermes -B ./hermes/build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build ./hermes/build --target hermes -j4

./hermes/build/bin/hermes -Xes6-proxy -non-strict -O -target=HBC \
  ./hermes/test/hermes/regress-proxy-accessor-descriptor.js

./hermes/build/bin/hermes ./accessor-poc.js

./hermes/build/bin/hermes -Xes6-proxy -non-strict -O -target=HBC \
  ./hermes/test/hermes/regress-proxy-defineproperty-value.js

./hermes/build-static_h/bin/hermes ./value-poc.js

Observed results:

$ hermes -Xes6-proxy -non-strict -O -target=HBC test/hermes/regress-proxy-accessor-descriptor.js
has get: true
has set: false
configurable: true
setter called: 5
setter intact: true

$ hermes ./accessor-poc.js
setter called: 5
No bug: setter still intact

$ hermes -Xes6-proxy -non-strict -O -target=HBC test/hermes/regress-proxy-defineproperty-value.js
has value: false
has writable: true
target.x: 42
descriptor.writable: false

$ hermes ./value-poc.js
target.x = 42

@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Mar 12, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented Mar 24, 2026

@tmikov has imported this pull request. If you are a Meta employee, you can view this in D97978736.

Comment thread lib/VM/Operations.cpp
if (result == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GC safety bug: accessor is a raw pointer obtained at line 2319. When both setGetter and setSetter are true, JSObject::defineOwnProperty() in the getter block above is a GC safepoint that may relocate the PropertyAccessor object. By the time we reach this line, accessor is potentially a dangling pointer.

The original code was safe because both getter and setter handles were extracted from accessor before any defineOwnProperty call. This PR splits them into separate if blocks with a GC safepoint in between.

Fix: re-derive accessor from the rooted handle before this block:

if (dpFlags.setSetter) {
  auto *accessor = vmcast<PropertyAccessor>(valueOrAccessor.get());
  auto setter = runtime.makeHandle(
      ...

@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented Mar 25, 2026

@tmikov merged this pull request in d36c900.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Do not delete this pull request or issue due to inactivity. Merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants