hermes icon indicating copy to clipboard operation
hermes copied to clipboard

TypeError: ownKeys target is non-extensible but key is missing from trap result error when using numeric string keys on Hermes

Open keisan1231 opened this issue 10 months ago • 6 comments

Bug Description

When using Valtio with Hermes in React Native, an error is thrown if the proxy object contains numeric string keys. For example, using a key like '12345' results in the following error:

TypeError: ownKeys target is non-extensible but key is missing from trap result, js engine: hermes

I've confirmed that using non-numeric string keys (e.g. text) avoids the error.

  • [x] I have run gradle clean and confirmed this bug does not occur with JSC
  • [ ] The issue is reproducible with the latest version of React Native.

Hermes git revision (if applicable): React Native version: 0.76.5 (new arch is false) OS: iOS, Android Platform (most likely one of arm64-v8a, armeabi-v7a, x86, x86_64): arm64

Steps To Reproduce

  1. Create a Valtio proxy object with a numeric string as key:
import { useSnapshot, proxy } from 'valtio';
const state = proxy({ '12345': 'hello' });

2.In a ReactNative component, take a snapshot and log it when a button is pressed:

export const MyScreen: React.FC = () => {
  const snap = useSnapshot(state);
  return (
    <Button
      onPress={() => {
        // Triggering iteration on the snapshot throws the error:
        Object.entries(snap).forEach(([key, value]) => {
          console.log(key, value);
        });
      }}
    >
      button for test
    </Button>
  );
}
  1. Run the application on a device/emulator with Hermes enabled.
  2. Press the button. The error occurs:
TypeError: ownKeys target is non-extensible but key is missing from trap result, js engine: hermes

The Expected Behavior

Using a numeric string as a key in a Valtio proxy should not lead to an error when accessing the snapshot (e.g., via Object.entries or direct logging). The behavior should be consistent with proxy objects that use non-numeric keys.

Additional context:

  • Replacing the key with a non-numeric string (for example, proxy({ text: 'hello' })) avoids the error.
  • A patch from this Valtio PR appears to work around the issue, suggesting the problem may be related to how Hermes handles Proxy traps.
  • A similar issue is discussed in the Hermes issue #1063, though the root cause has not yet been identified.

Any guidance or fixes for this issue would be greatly appreciated. Thank you for your help!

keisan1231 avatar Feb 04 '25 04:02 keisan1231

Hi, thanks for the bug report! Unfortunately we are unable to investigate bug reports that require installing dependencies or building and running an entire RN app. Can you minimize the problem to something that can be reproduced standalone in the Hermes CLI?

tmikov avatar Feb 04 '25 17:02 tmikov

@tmikov Thank you for your reply

I created a file named test.js and executed the following commands, which resulted in the same error

created file

'use strict';

// Proxy creation function
function createProxy(target) {
  return new Proxy(target, {
    // In ownKeys, exclude keys that are numeric strings
    ownKeys(target) {
      return Reflect.ownKeys(target).filter((key) => isNaN(Number(key)));
    },
    // getOwnPropertyDescriptor is required: returns the descriptor of the target object
    getOwnPropertyDescriptor(target, key) {
      return Object.getOwnPropertyDescriptor(target, key);
    },
  });
}

// --- Case with numeric string keys ---
// Make the target object frozen and non-extensible
const numericKeyTarget = Object.freeze({ 12345: 'hello' });
const numericKeyProxy = createProxy(numericKeyTarget);

print('[Case with numeric string keys]');
try {
  // Attempting to get proxy keys with Object.keys
  // In Hermes, this causes an error as the key "12345" is excluded from ownKeys results
  print(Object.keys(numericKeyProxy));
} catch (e) {
  print('Error:', e);
}

// --- Case with non-numeric string keys ---
// Similarly frozen target object but with key "text"
const nonNumericKeyTarget = Object.freeze({ text: 'hello' });
const nonNumericKeyProxy = createProxy(nonNumericKeyTarget);

print('\n[Case with non-numeric string keys]');
try {
  // When converted to number, key becomes NaN so not filtered out - keys returned normally
  print(Object.keys(nonNumericKeyProxy));
} catch (e) {
  print('Error:', e);
}

command

bin/hermes -exec path/to/test.js

result log

[Case with numeric string keys]
Error: TypeError: ownKeys target key is non-configurable but not present in trap result

[Case with non-numeric string keys]
text

keisan1231 avatar Feb 06 '25 05:02 keisan1231

Thanks! When I run your repro with Hermes, v8 and JSC, I get similar results. What is the expected behavior?

$ hermes t.js
[Case with numeric string keys]
Error: TypeError: ownKeys target key is non-configurable but not present in trap result

[Case with non-numeric string keys]
text

$ v8 t.js
[Case with numeric string keys]
Error: TypeError: 'ownKeys' on proxy: trap result did not include '12345'

[Case with non-numeric string keys]
text

$ jsc t.js
[Case with numeric string keys]
Error: TypeError: Proxy object's 'target' has the non-configurable property '12345' that was not in the result from the 'ownKeys' trap

[Case with non-numeric string keys]
text

tmikov avatar Feb 06 '25 05:02 tmikov

I found a reproducible code example. I encountered an issue when combining Proxy and Reflect.ownKeys on Hermes. The following code works on V8 but fails on Hermes:

if (typeof console === "undefined") {
  globalThis.console = { log: print };
}

// Works on both Hermes and V8.
// const target = Object.preventExtensions({ foo: "bar" });

// Does not work on Hermes, but works on V8.
const target = Object.preventExtensions({ 123: "bar" });

const proxy = new Proxy(target, {
  ownKeys(target) {
    return Reflect.ownKeys(target);
  },
});

console.log(Object.keys(proxy));

When the property key is a number (123 in this case), Hermes throws an error, whereas V8 correctly logs the keys. This discrepancy does not occur when the property key is a string (e.g., foo).

ichiki1023 avatar Feb 12 '25 08:02 ichiki1023

https://github.com/facebook/hermes/issues/1609#issuecomment-2653017237

I got results.

v8

$ node test.js
[ '123' ]

hermes

$ bin/hermes test.js
Uncaught TypeError: ownKeys target is non-extensible but key is missing from trap result
    at keys (native)
    at global (test.js:17:24)

ichiki1023 avatar Feb 12 '25 14:02 ichiki1023

@ichiki1023 Thank you, I was able to reproduce it with your example!

tmikov avatar Feb 13 '25 00:02 tmikov

Is this problem solved? I have the same problem.

JackTong0312 avatar Apr 21 '25 01:04 JackTong0312

@tmikov

JackTong0312 avatar Apr 21 '25 07:04 JackTong0312

We are aware of the problem but haven't been able to work on it yet. Proxy is forbidden in React Native at Meta, because it is extremely slow, which makes it challenging to elevate this issue to high priority.

tmikov avatar Apr 24 '25 17:04 tmikov