Skip to content

Add with statement support#1571

Open
trossimel-sc wants to merge 3 commits intofacebook:static_hfrom
trossimel-sc:with-support
Open

Add with statement support#1571
trossimel-sc wants to merge 3 commits intofacebook:static_hfrom
trossimel-sc:with-support

Conversation

@trossimel-sc
Copy link
Contributor

@trossimel-sc trossimel-sc commented Nov 27, 2024

Summary

This PR introduces support for the with statement via IR

  • Identifiers within a with statement are transformed into a series of conditional chain. Each condition checks the presence of a key in the object properties, and accesses it if present. This is done through createConditionalChainImpl
  • We need to keep track of each with statement depth. Hermes uses a task queue to lazily compile functions, and for this reason, we also need to copy the with depths when adding the declaration to the queue.

The changes are designed to minimize disruption. The logic only activates when a with statement is present, and the bulk of the implementation is located in a separate file, ESTreeIRGen-with.

Test Plan

  • Added a unit test covering most common scenarios
  • Test262 result on with statements tests: 92.98%.

This commit introduces support for the with statement via IR, by replacing the previous AST transformation with a more robust alternative.

**Logic:**
- Identifiers are replaced with a conditional chain that checks the presence of a key in the object properties.
- We need to keep track of each `with` statement depth. Hermes also uses a task queue to lazily compile functions, and for this reason, we also need to copy the `with` depths when adding the declaration to the queue.
- Compared to an AST transformation, `buildConditionalChain` any call to the Builder will be executed in the same called order. This means we cannot generate the nested conditional chain starting from the inner condition.

**Test262 results:**  92.98%.
Cooled by trossimel
@facebook-github-bot
Copy link
Contributor

Hi @trossimel-sc!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@trossimel-sc
Copy link
Contributor Author

trossimel-sc commented Nov 27, 2024

While the output is correct, I've noticed that when running in debug mode, it occasionally fails with the following message:

Assertion failed: (I->hasOutput() && "Instruction has no output"), function encodeValue, file ISel.cpp, line 264.

And in this case the IR output seems to be incorrectly generated. This issue arises specifically when a store operation is present within the with statement. Here's an example:

let e = 20;
with (obj) {
    e = 30;
}

Is there a straightforward way to handle this case? I'm not very familiar with Hermes IR, so I'm unsure why this failure occurs, while the output is still correct. Thank you for your help!

Please feel free to respond once my CLA is signed, if needed. —I hope this process won't take much longer. 🫤

@trossimel-sc trossimel-sc changed the title [WIP] Add with statement support Add with statement support Nov 27, 2024
@tmikov
Copy link
Contributor

tmikov commented Nov 29, 2024

@trossimel-sc I haven't reviewed the PR, but I compiled it and looked at the IR output of your example:

  %10 = CallInst (:any) %8: any, empty: any, false: boolean, empty: any, undefined: undefined, undefined: undefined, %9: any, "e": string
  %11 = CondBranchInst %10: any, %BB2, %BB1
%BB1:
  %12 = StoreFrameInst %0: environment, 30: number, [%VS0.e]: any
  %13 = BranchInst %BB3
%BB2:
  %14 = LoadFrameInst (:any) %0: environment, [%VS0.with1]: any
  %15 = StorePropertyLooseInst 30: number, %14: any, "e": string
  %16 = BranchInst %BB3
%BB3:
  %17 = PhiInst (:notype) %15: notype, %BB2, %12: notype, %BB1

You are calling a helper method to check if e exists in the object and branching to either a property store %15 or a variable store %12. The problem is that you are treating these stores as if they have a result and in instruction %17 you are reading this result. Stores have no result, they just have side effects.

AFAICT, instruction %17 is incorrect and more importantly, unnecessary. You don't need to combine the stores with Phi, they have already done their side effect.

BTW, I think that we probably want a new bytecode instruction for this instead of calling a helper, but we can do that incrementally. Also, the helper should just be a "builtin", which is safer and easier to call. But these are details that we can address when we start the review.

(We don't want to start reviewing before we know that we can accept the PR, I hope you understand)

@trossimel-sc
Copy link
Contributor Author

Thanks for the response and the assistance @tmikov
Just wanted to update, I'm still waiting for my request to be approved by the company. Unfortunately, it's taking longer than expected, and there's not much I can do except wait for now 🫤

@leotm
Copy link

leotm commented Dec 16, 2024

great work 🎉 adding Fixes: #1056 to description should link the issue to this PR (auto-close on merge)

@facebook-github-bot facebook-github-bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Jan 17, 2025
@dream2333
Copy link

Any progress here?

leotm added a commit to leotm/react-native-template-new-architecture that referenced this pull request Feb 5, 2026
- Lock down realm for a Hardened JavaScript (tamper-resistant JS) runtime execution environment (irreversible frozen realm)
- Remove ambient authority (unpermitted intrinsics) from global scope (PoLA/PoLP/zero-trust), replace dangerous legacy and non-standard properties (+ known JS engine bugs)
- Recursively/transitively (deep) freeze (lock down) ECMA262 (not host) built-in objects (i.e. primordials including hidden intrinsics) and tame (not scuttle) globals
- Turn JavaScript system into a Secure ECMAScript system with now enforced OCap (not identity/ACL based) security (an OCap language) to write defensively consistent programs
- Protect against prototype poisoning/pollution supply chain attacks from vulnerable/compromised (untrusted/malicious) 3rd party code/dependencies (attempting to access e.g. I/Onetwork,reading/stealing private data)
- ECMAScript > ES-strict (static+dynamic i.e. parse+runtime) > Secure ECMAScript (dynamic i.e. runtime)
- Use Endo (Agoric) production-grade (prev audited) SES lockdown (Hermes/JSC) shims
  - SES Proposal partitioned into: Hardened JS (Lockdown) pattern (prerequisite for Compartments), TC39 Compartments (Stage 1), TC39 ShadowRealm (Stage 4 / ES2026)
  - Supporting Compartments: TC39 Source Phase Imports (Stage 3), TC39 Module Expressions (prev: Blocks) (Stage 2) (e.g. const m = module { export const leet = 1337; }), TC39 Module Declarations (Stage 1)
  - Noting already: (Moddable) XS (JS) engine - native SES and Compartments; TC53 (IoT) - SES and Compartments; Node.js core --frozen-intrinsics; Salesforce (Lightning Web Security) - SES
  - github.com/tc39/proposal-compartments, github.com/tc39/proposal-source-phase-imports, github.com/tc39/proposal-module-declarations, github.com/tc39/proposal-module-expressions
  - github.com/nodejs/node/blob/main/lib/internal/freeze_intrinsics.js, per_context/primordials.js
- Prepare realm for code/dependency isolation/sandboxing via new Compartments (stripped non-deterministic Math.random and Date.now) with LavaMoat

- Hook into JS bundler (Metro) final stage (resolve -> transform -> serialize)
- Polyfills: SES (Hermes) shim, repairIntrinsics (called with RN opts and preserved RN Promise), @react-native/js-polyfills, [no user vetted shims yet]
  - github.com/LavaMoat/LavaMoat/blob/main/packages/react-native-lockdown/src/index.js
- Modules: RN InitializeCore -> (hardenIntrinsics) -> entry file (index.js)
  - github.com/facebook/react-native/blob/main/packages/community-cli-plugin/src/utils/loadMetroConfig.js serializer, packages/metro-config/src/index.flow.js types
- Exclude SES shims from Babel transformation to preserve integrity (endojs/endo#662 Detect if ses is being transformed)

- Both Hermes and React Native (for legacy JSC/V8 during InitializeCore) load 'then/promise' polyfill
- SES removes the exposed internals `Promise._<onHandle|onReject|noop>` (`Promise._<l|m|n>`) so we restore them after repair as a vetted shim
  - github.com/LavaMoat/LavaMoat/blob/main/packages/react-native-lockdown/src/repair.js, github.com/endojs/endo/blob/master/docs/lockdown.md
- github.com/facebook/hermes/blob/main/utils/promise/index.js, github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/polyfillPromise.js
  - github.com/then/promise/blob/master/src/core.js (`Promise._<onHandle|onReject|noop`), github.com/then/promise/blob/master/src/es6-extensions.js

- Evade property override mistake ('Cannot assign to read-only property') via lockdown opt (`overrideTaming: 'severe'`)
  - i.e. keep prototype immutable, but allow local instances to override methods (e.g. myObj.toString)
- endojs/endo#1855 Property override mistake, endojs/endo#2037 Ecosystem Compat (2024-02-05+)

- endojs/endo#1511 JSC compat, endojs/endo#1891 Hermes compat
- facebook/hermes#957 eval issue, facebook/hermes#1515 eval PR
- facebook/hermes#1056 eval issue, facebook/hermes#1571 eval PR

- hardenedjs.org, papers.agoric.com/documentation/guides/js-programming/hardened-js.html
- github.com/endojs/endo/blob/master/packages/ses/src/lockdown-shim.js, compartment-shim.js, make-evaluate.js (quad backflip), commons.js
- github.com/endojs/endo/blob/master/docs/guide.md, github.com/endojs/endo/blob/master/docs/reference.md

- github.com/tc39/how-we-work/blob/main/terminology.md, lavamoat.github.io/reference/glossary

- github.com/Moddable-OpenSource/moddable/blob/public/xs/sources/xsLockdown.c, xsModule.c, documentation/xs/XS%20Compartment.md

Resolve: #1803
leotm added a commit to leotm/react-native-template-new-architecture that referenced this pull request Feb 5, 2026
- Lock down realm for a Hardened JavaScript (tamper-resistant JS) runtime execution environment (irreversible frozen realm)
- Remove ambient authority (unpermitted intrinsics) from global scope (PoLA/PoLP/zero-trust), replace dangerous legacy and non-standard properties (+ known JS engine bugs)
- Recursively/transitively (deep) freeze (lock down) ECMA262 (not host) built-in objects (i.e. primordials including hidden intrinsics) and tame (not scuttle) globals
- Turn JavaScript system into a Secure ECMAScript system with now enforced OCap (not identity/ACL based) security (an OCap language) to write defensively consistent programs
- Protect against prototype poisoning/pollution supply chain attacks from vulnerable/compromised (untrusted/malicious) 3rd party code/dependencies (attempting to access e.g. I/Onetwork,reading/stealing private data)
- ECMAScript > ES-strict (static+dynamic i.e. parse+runtime) > Secure ECMAScript (dynamic i.e. runtime)
- Use Endo (Agoric) production-grade (prev audited) SES lockdown (Hermes/JSC) shims
  - SES Proposal partitioned into: Hardened JS (Lockdown) pattern (prerequisite for Compartments), TC39 Compartments (Stage 1), TC39 ShadowRealm (Stage 4 / ES2026)
  - Supporting Compartments: TC39 Source Phase Imports (Stage 3), TC39 Module Expressions (prev: Blocks) (Stage 2) (e.g. const m = module { export const leet = 1337; }), TC39 Module Declarations (Stage 1)
  - Noting already: (Moddable) XS (JS) engine - native SES and Compartments; TC53 (IoT) - SES and Compartments; Node.js core --frozen-intrinsics; Salesforce (Lightning Web Security) - SES
  - github.com/tc39/proposal-compartments, github.com/tc39/proposal-source-phase-imports, github.com/tc39/proposal-module-declarations, github.com/tc39/proposal-module-expressions
  - github.com/nodejs/node/blob/main/lib/internal/freeze_intrinsics.js, per_context/primordials.js
- Prepare realm for code/dependency isolation/sandboxing via new Compartments (stripped non-deterministic Math.random and Date.now) with LavaMoat

- Hook into JS bundler (Metro) final stage (resolve -> transform -> serialize)
- Polyfills: SES (Hermes) shim, repairIntrinsics (called with RN opts and preserved RN Promise), @react-native/js-polyfills, [no user vetted shims yet]
  - github.com/LavaMoat/LavaMoat/blob/main/packages/react-native-lockdown/src/index.js
- Modules: RN InitializeCore -> (hardenIntrinsics) -> entry file (index.js)
  - github.com/facebook/react-native/blob/main/packages/community-cli-plugin/src/utils/loadMetroConfig.js serializer, packages/metro-config/src/index.flow.js types
- Exclude SES shims from Babel transformation to preserve integrity (endojs/endo#662 Detect if ses is being transformed)

- Both Hermes and React Native (for legacy JSC/V8 during InitializeCore) load 'then/promise' polyfill
- SES removes the exposed internals `Promise._<onHandle|onReject|noop>` (`Promise._<l|m|n>`) so we restore them after repair as a vetted shim
  - github.com/LavaMoat/LavaMoat/blob/main/packages/react-native-lockdown/src/repair.js, github.com/endojs/endo/blob/master/docs/lockdown.md
- github.com/facebook/hermes/blob/main/utils/promise/index.js, github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/polyfillPromise.js
  - github.com/then/promise/blob/master/src/core.js (`Promise._<onHandle|onReject|noop`), github.com/then/promise/blob/master/src/es6-extensions.js

- Evade property override mistake ('Cannot assign to read-only property') via lockdown opt (`overrideTaming: 'severe'`)
  - i.e. keep prototype immutable, but allow local instances to override methods (e.g. myObj.toString)
- endojs/endo#1855 Property override mistake, endojs/endo#2037 Ecosystem Compat (2024-02-05+)

- endojs/endo#1511 JSC compat, endojs/endo#1891 Hermes compat
- facebook/hermes#957 eval fn issue, facebook/hermes#1515 PR
- facebook/hermes#1056 with statement issue, facebook/hermes#1571 eval PR

- hardenedjs.org, papers.agoric.com/documentation/guides/js-programming/hardened-js.html
- github.com/endojs/endo/blob/master/packages/ses/src/lockdown-shim.js, compartment-shim.js, make-evaluate.js (quad backflip), commons.js
- github.com/endojs/endo/blob/master/docs/guide.md, github.com/endojs/endo/blob/master/docs/reference.md

- github.com/tc39/how-we-work/blob/main/terminology.md, lavamoat.github.io/reference/glossary

- github.com/Moddable-OpenSource/moddable/blob/public/xs/sources/xsLockdown.c, xsModule.c, documentation/xs/XS%20Compartment.md

Resolve: #1803
leotm added a commit to leotm/react-native-template-new-architecture that referenced this pull request Feb 5, 2026
- Lock down realm for a Hardened JavaScript (tamper-resistant JS) runtime execution environment (irreversible frozen realm)
- Remove ambient authority (unpermitted intrinsics) from global scope (PoLA/PoLP/zero-trust), replace dangerous legacy and non-standard properties (+ known JS engine bugs)
- Recursively/transitively (deep) freeze (lock down) ECMA262 (not host) built-in objects (i.e. primordials including hidden intrinsics) and tame (not scuttle) globals
- Turn JavaScript system into a Secure ECMAScript system with now enforced OCap (not identity/ACL based) security (an OCap language) to write defensively consistent programs
- Protect against prototype poisoning/pollution supply chain attacks from vulnerable/compromised (untrusted/malicious) 3rd party code/dependencies (attempting to access e.g. I/Onetwork,reading/stealing private data)
- ECMAScript > ES-strict (static+dynamic i.e. parse+runtime) > Secure ECMAScript (dynamic i.e. runtime)
- Use Endo (Agoric) production-grade (prev audited) SES lockdown (Hermes/JSC) shims
  - SES Proposal partitioned into: Hardened JS (Lockdown) pattern (prerequisite for Compartments), TC39 Compartments (Stage 1), TC39 ShadowRealm (Stage 4 / ES2026)
  - Supporting Compartments: TC39 Source Phase Imports (Stage 3), TC39 Module Expressions (prev: Blocks) (Stage 2) (e.g. const m = module { export const leet = 1337; }), TC39 Module Declarations (Stage 1)
  - Noting already: (Moddable) XS (JS) engine - native SES and Compartments; TC53 (IoT) - SES and Compartments; Node.js core --frozen-intrinsics; Salesforce (Lightning Web Security) - SES
  - github.com/tc39/proposal-compartments, github.com/tc39/proposal-source-phase-imports, github.com/tc39/proposal-module-declarations, github.com/tc39/proposal-module-expressions
  - github.com/nodejs/node/blob/main/lib/internal/freeze_intrinsics.js, per_context/primordials.js
- Prepare realm for code/dependency isolation/sandboxing via new Compartments (stripped non-deterministic Math.random and Date.now) with LavaMoat

- Hook into JS bundler (Metro) final stage (resolve -> transform -> serialize)
- Polyfills: SES (Hermes) shim, repairIntrinsics (called with RN opts and preserved RN Promise), @react-native/js-polyfills, [no user vetted shims yet]
  - github.com/LavaMoat/LavaMoat/blob/main/packages/react-native-lockdown/src/index.js
- Modules: RN InitializeCore -> (hardenIntrinsics) -> entry file (index.js)
  - github.com/facebook/react-native/blob/main/packages/community-cli-plugin/src/utils/loadMetroConfig.js serializer, packages/metro-config/src/index.flow.js types
- Exclude SES shims from Babel transformation to preserve integrity (endojs/endo#662 Detect if ses is being transformed)

- Both Hermes and React Native (for legacy JSC/V8 during InitializeCore) load 'then/promise' polyfill
- SES removes the exposed internals `Promise._<onHandle|onReject|noop>` (`Promise._<l|m|n>`) so we restore them after repair as a vetted shim
  - github.com/LavaMoat/LavaMoat/blob/main/packages/react-native-lockdown/src/repair.js, github.com/endojs/endo/blob/master/docs/lockdown.md
- github.com/facebook/hermes/blob/main/utils/promise/index.js, github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/polyfillPromise.js
  - github.com/then/promise/blob/master/src/core.js (`Promise._<onHandle|onReject|noop`), github.com/then/promise/blob/master/src/es6-extensions.js

- Evade property override mistake ('Cannot assign to read-only property') via lockdown opt (`overrideTaming: 'severe'`)
  - i.e. keep prototype immutable, but allow local instances to override methods (e.g. myObj.toString)
- endojs/endo#1855 Property override mistake, endojs/endo#2037 Ecosystem Compat (2024-02-05+)

- endojs/endo#1511 JSC compat, endojs/endo#1891 Hermes compat
- facebook/hermes#957 eval fn issue, facebook/hermes#1515 PR
- facebook/hermes#1056 with statement issue, facebook/hermes#1571 PR

- hardenedjs.org, papers.agoric.com/documentation/guides/js-programming/hardened-js.html
- github.com/endojs/endo/blob/master/packages/ses/src/lockdown-shim.js, compartment-shim.js, make-evaluate.js (quad backflip), commons.js
- github.com/endojs/endo/blob/master/docs/guide.md, github.com/endojs/endo/blob/master/docs/reference.md

- github.com/tc39/how-we-work/blob/main/terminology.md, lavamoat.github.io/reference/glossary

- github.com/Moddable-OpenSource/moddable/blob/public/xs/sources/xsLockdown.c, xsModule.c, documentation/xs/XS%20Compartment.md

Resolve: #1803
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants