Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ses): removeUnpermittedIntrinsics on Hermes #2655

Merged
merged 3 commits into from
Dec 19, 2024

Conversation

leotm
Copy link
Contributor

@leotm leotm commented Dec 5, 2024

Follow-up to

Progresses


Description

Fix removeUnpermittedIntrinsics (previously whitelistIntrinsics) on Hermes

SES Removing unpermitted intrinsics
  Removing intrinsics.Promise.caller
  failed to delete intrinsics.Promise.caller TypeError: Property is not configurable
Uncaught TypeError: Property is not configurable

after starting again on the problem (now with #2624 addressing completePrototypes), it boils down to tolerating two more undeletable non-standard properties (.caller and .arguments) on the remaining Hermes functions after attempting to remove them
edit: and .prototype on some properties

so we can simply extend cauterizeProperty after dealing with prototypes to deal with these additional properties

repro

  1. gh pr checkout 2334 feat(ses): Shim compatible with Hermes compiler #2334
  2. enable Hermes VM in packages/ses/scripts/hermes-test.sh
  3. comment // assertDirectEvalAvailable() in packages/ses/src/lockdown.js
  4. yarn build:hermes
  5. yarn test:hermes
Hermes VM output (before)
endo git:(ses-hermes) ✗ yarn build:hermes && yarn test:hermes
-- Building 'hermes' version of SES --
Bundle size: 448125 bytes
Copied ./types.d.ts to ./dist/types.d.cts
Concatenating: dist/ses-hermes.cjs + test/_hermes-smoke.js
Generated: test/_hermes-smoke-dist.js
Executing: test/_hermes-smoke-dist.js on Hermes compiler
test/_hermes-smoke-dist.js:13007:27: warning: the variable "Compartment" was not declared in function "?anon_0_?anon_0_testCompartmentHooks"
  const compartment = new Compartment({}, {}, { resolveHook, importHook });
                          ^~~~~~~~~~~
test/_hermes-smoke-dist.js:13015:3: warning: the variable "assert" was not declared in function "?anon_0_?anon_0_testCompartmentHooks"
  assert(module);
  ^~~~~~
test/_hermes-smoke-dist.js:3838:12: warning: the variable "AggregateError" was not declared in anonymous function " 119#"
if( typeof AggregateError!==  'undefined') {
           ^~~~~~~~~~~~~~
test/_hermes-smoke-dist.js:7053:5: warning: the variable "console" was not declared in function "getOwnPropertyDescriptor"
    console.warn(
    ^~~~~~~
test/_hermes-smoke-dist.js:8613:5: warning: the variable "harden" was not declared in arrow function "makeCausalConsoleFromLogger"
    harden(baseConsole);
    ^~~~~~
test/_hermes-smoke-dist.js:12978:3: warning: the variable "lockdown" was not declared in arrow function "testLockdown"
  lockdown();
  ^~~~~~~~
Generated: test/_hermes-smoke-dist.hbc
Hermes compiler done
Executing generated bytecode file on Hermes VM
Removing lockdown.prototype
Tolerating undeletable lockdown.prototype === undefined
Removing harden.prototype
Tolerating undeletable harden.prototype === undefined
Removing %InitialGetStackString%.prototype
Tolerating undeletable %InitialGetStackString%.prototype === undefined
SES Removing unpermitted intrinsics
  Removing intrinsics.Promise.caller
  failed to delete intrinsics.Promise.caller TypeError: Property is not configurable
Uncaught TypeError: Property is not configurable
Hermes VM output (after)
endo git:(ses-hermes) ✗ yarn build:hermes && yarn test:hermes
-- Building 'hermes' version of SES --
Bundle size: 448108 bytes
Copied ./types.d.ts to ./dist/types.d.cts
Concatenating: dist/ses-hermes.cjs + test/_hermes-smoke.js
Generated: test/_hermes-smoke-dist.js
Executing: test/_hermes-smoke-dist.js on Hermes compiler
test/_hermes-smoke-dist.js:13007:27: warning: the variable "Compartment" was not declared in function "?anon_0_?anon_0_testCompartmentHooks"
  const compartment = new Compartment({}, {}, { resolveHook, importHook });
                          ^~~~~~~~~~~
test/_hermes-smoke-dist.js:13015:3: warning: the variable "assert" was not declared in function "?anon_0_?anon_0_testCompartmentHooks"
  assert(module);
  ^~~~~~
test/_hermes-smoke-dist.js:3838:12: warning: the variable "AggregateError" was not declared in anonymous function " 119#"
if( typeof AggregateError!==  'undefined') {
           ^~~~~~~~~~~~~~
test/_hermes-smoke-dist.js:7053:5: warning: the variable "console" was not declared in function "getOwnPropertyDescriptor"
    console.warn(
    ^~~~~~~
test/_hermes-smoke-dist.js:8613:5: warning: the variable "harden" was not declared in arrow function "makeCausalConsoleFromLogger"
    harden(baseConsole);
    ^~~~~~
test/_hermes-smoke-dist.js:12978:3: warning: the variable "lockdown" was not declared in arrow function "testLockdown"
  lockdown();
  ^~~~~~~~
Generated: test/_hermes-smoke-dist.hbc
Hermes compiler done
Executing generated bytecode file on Hermes VM
Removing lockdown.prototype
Tolerating undeletable lockdown.prototype === undefined
Removing harden.prototype
Tolerating undeletable harden.prototype === undefined
Removing %InitialGetStackString%.prototype
Tolerating undeletable %InitialGetStackString%.prototype === undefined
SES Removing unpermitted intrinsics
  Removing intrinsics.Promise.caller
  Tolerating undeletable intrinsics.Promise.caller
  Removing intrinsics.Promise.arguments
  Tolerating undeletable intrinsics.Promise.arguments
  Removing intrinsics.Promise._l
  Removing intrinsics.Promise._m
  Removing intrinsics.Promise._n
  Removing intrinsics.Promise.resolve.caller
  Tolerating undeletable intrinsics.Promise.resolve.caller
  Removing intrinsics.Promise.resolve.arguments
  Tolerating undeletable intrinsics.Promise.resolve.arguments
  Removing intrinsics.Promise.resolve.prototype
  Tolerating undeletable intrinsics.Promise.resolve.prototype === undefined
  Removing intrinsics.Promise.all.caller
  Tolerating undeletable intrinsics.Promise.all.caller
  Removing intrinsics.Promise.all.arguments
  Tolerating undeletable intrinsics.Promise.all.arguments
  Removing intrinsics.Promise.all.prototype
  Tolerating undeletable intrinsics.Promise.all.prototype === undefined
  Removing intrinsics.Promise.reject.caller
  Tolerating undeletable intrinsics.Promise.reject.caller
  Removing intrinsics.Promise.reject.arguments
  Tolerating undeletable intrinsics.Promise.reject.arguments
  Removing intrinsics.Promise.reject.prototype
  Tolerating undeletable intrinsics.Promise.reject.prototype === undefined
  Removing intrinsics.Promise.race.caller
  Tolerating undeletable intrinsics.Promise.race.caller
  Removing intrinsics.Promise.race.arguments
  Tolerating undeletable intrinsics.Promise.race.arguments
  Removing intrinsics.Promise.race.prototype
  Tolerating undeletable intrinsics.Promise.race.prototype === undefined
  Removing intrinsics.lockdown.caller
  Tolerating undeletable intrinsics.lockdown.caller
  Removing intrinsics.lockdown.arguments
  Tolerating undeletable intrinsics.lockdown.arguments
  Removing intrinsics.lockdown.prototype
  Tolerating undeletable intrinsics.lockdown.prototype === undefined
  Removing intrinsics.harden.caller
  Tolerating undeletable intrinsics.harden.caller
  Removing intrinsics.harden.arguments
  Tolerating undeletable intrinsics.harden.arguments
  Removing intrinsics.harden.prototype
  Tolerating undeletable intrinsics.harden.prototype === undefined
  Removing intrinsics.%InertFunction%.caller
  Tolerating undeletable intrinsics.%InertFunction%.caller
  Removing intrinsics.%InertFunction%.arguments
  Tolerating undeletable intrinsics.%InertFunction%.arguments
  Removing intrinsics.%InertGeneratorFunction%.caller
  Tolerating undeletable intrinsics.%InertGeneratorFunction%.caller
  Removing intrinsics.%InertGeneratorFunction%.arguments
  Tolerating undeletable intrinsics.%InertGeneratorFunction%.arguments
  Removing intrinsics.%InertAsyncFunction%.caller
  Tolerating undeletable intrinsics.%InertAsyncFunction%.caller
  Removing intrinsics.%InertAsyncFunction%.arguments
  Tolerating undeletable intrinsics.%InertAsyncFunction%.arguments
  Removing intrinsics.%InitialDate%.caller
  Tolerating undeletable intrinsics.%InitialDate%.caller
  Removing intrinsics.%InitialDate%.arguments
  Tolerating undeletable intrinsics.%InitialDate%.arguments
  Removing intrinsics.%SharedDate%.caller
  Tolerating undeletable intrinsics.%SharedDate%.caller
  Removing intrinsics.%SharedDate%.arguments
  Tolerating undeletable intrinsics.%SharedDate%.arguments
  Removing intrinsics.%SharedDate%.now.caller
  Tolerating undeletable intrinsics.%SharedDate%.now.caller
  Removing intrinsics.%SharedDate%.now.arguments
  Tolerating undeletable intrinsics.%SharedDate%.now.arguments
  Removing intrinsics.%SharedDate%.now.prototype
  Tolerating undeletable intrinsics.%SharedDate%.now.prototype === undefined
  Removing intrinsics.%InitialGetStackString%.caller
  Tolerating undeletable intrinsics.%InitialGetStackString%.caller
  Removing intrinsics.%InitialGetStackString%.arguments
  Tolerating undeletable intrinsics.%InitialGetStackString%.arguments
  Removing intrinsics.%InitialGetStackString%.prototype
  Tolerating undeletable intrinsics.%InitialGetStackString%.prototype === undefined
  Removing intrinsics.%InitialError%.caller
  Tolerating undeletable intrinsics.%InitialError%.caller
  Removing intrinsics.%InitialError%.arguments
  Tolerating undeletable intrinsics.%InitialError%.arguments
  Removing intrinsics.%InitialError%.stackTraceLimit<get>.caller
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<get>.caller
  Removing intrinsics.%InitialError%.stackTraceLimit<get>.arguments
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<get>.arguments
  Removing intrinsics.%InitialError%.stackTraceLimit<get>.prototype
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<get>.prototype === undefined
  Removing intrinsics.%InitialError%.stackTraceLimit<set>.caller
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<set>.caller
  Removing intrinsics.%InitialError%.stackTraceLimit<set>.arguments
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<set>.arguments
  Removing intrinsics.%InitialError%.stackTraceLimit<set>.prototype
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<set>.prototype === undefined
  Removing intrinsics.%InitialError%.captureStackTrace.caller
  Tolerating undeletable intrinsics.%InitialError%.captureStackTrace.caller
  Removing intrinsics.%InitialError%.captureStackTrace.arguments
  Tolerating undeletable intrinsics.%InitialError%.captureStackTrace.arguments
  Removing intrinsics.%InitialError%.captureStackTrace.prototype
  Tolerating undeletable intrinsics.%InitialError%.captureStackTrace.prototype === undefined
  Removing intrinsics.%InitialError%.prepareStackTrace<get>.caller
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<get>.caller
  Removing intrinsics.%InitialError%.prepareStackTrace<get>.arguments
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<get>.arguments
  Removing intrinsics.%InitialError%.prepareStackTrace<get>.prototype
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<get>.prototype === undefined
  Removing intrinsics.%InitialError%.prepareStackTrace<set>.caller
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<set>.caller
  Removing intrinsics.%InitialError%.prepareStackTrace<set>.arguments
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<set>.arguments
  Removing intrinsics.%InitialError%.prepareStackTrace<set>.prototype
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<set>.prototype === undefined
  Removing intrinsics.%SharedError%.caller
  Tolerating undeletable intrinsics.%SharedError%.caller
  Removing intrinsics.%SharedError%.arguments
  Tolerating undeletable intrinsics.%SharedError%.arguments
  Removing intrinsics.%SharedError%.stackTraceLimit<get>.caller
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<get>.caller
  Removing intrinsics.%SharedError%.stackTraceLimit<get>.arguments
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<get>.arguments
  Removing intrinsics.%SharedError%.stackTraceLimit<get>.prototype
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<get>.prototype === undefined
  Removing intrinsics.%SharedError%.stackTraceLimit<set>.caller
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<set>.caller
  Removing intrinsics.%SharedError%.stackTraceLimit<set>.arguments
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<set>.arguments
  Removing intrinsics.%SharedError%.stackTraceLimit<set>.prototype
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<set>.prototype === undefined
  Removing intrinsics.%SharedError%.prepareStackTrace<get>.caller
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<get>.caller
  Removing intrinsics.%SharedError%.prepareStackTrace<get>.arguments
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<get>.arguments
  Removing intrinsics.%SharedError%.prepareStackTrace<get>.prototype
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<get>.prototype === undefined
  Removing intrinsics.%SharedError%.prepareStackTrace<set>.caller
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<set>.caller
  Removing intrinsics.%SharedError%.prepareStackTrace<set>.arguments
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<set>.arguments
  Removing intrinsics.%SharedError%.prepareStackTrace<set>.prototype
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<set>.prototype === undefined
  Removing intrinsics.%SharedError%.captureStackTrace.caller
  Tolerating undeletable intrinsics.%SharedError%.captureStackTrace.caller
  Removing intrinsics.%SharedError%.captureStackTrace.arguments
  Tolerating undeletable intrinsics.%SharedError%.captureStackTrace.arguments
  Removing intrinsics.%SharedError%.captureStackTrace.prototype
  Tolerating undeletable intrinsics.%SharedError%.captureStackTrace.prototype === undefined
  Removing intrinsics.%SharedMath%.random.caller
  Tolerating undeletable intrinsics.%SharedMath%.random.caller
  Removing intrinsics.%SharedMath%.random.arguments
  Tolerating undeletable intrinsics.%SharedMath%.random.arguments
  Removing intrinsics.%SharedMath%.random.prototype
  Tolerating undeletable intrinsics.%SharedMath%.random.prototype === undefined
  Removing intrinsics.%InitialRegExp%.caller
  Tolerating undeletable intrinsics.%InitialRegExp%.caller
  Removing intrinsics.%InitialRegExp%.arguments
  Tolerating undeletable intrinsics.%InitialRegExp%.arguments
  Removing intrinsics.%SharedRegExp%.caller
  Tolerating undeletable intrinsics.%SharedRegExp%.caller
  Removing intrinsics.%SharedRegExp%.arguments
  Tolerating undeletable intrinsics.%SharedRegExp%.arguments
  Removing intrinsics.%SharedSymbol%.caller
  Tolerating undeletable intrinsics.%SharedSymbol%.caller
  Removing intrinsics.%SharedSymbol%.arguments
  Tolerating undeletable intrinsics.%SharedSymbol%.arguments
  Removing intrinsics.%InertCompartment%.caller
  Tolerating undeletable intrinsics.%InertCompartment%.caller
  Removing intrinsics.%InertCompartment%.arguments
  Tolerating undeletable intrinsics.%InertCompartment%.arguments
  Removing intrinsics.%NumberPrototype%.toLocaleString.caller
  Tolerating undeletable intrinsics.%NumberPrototype%.toLocaleString.caller
  Removing intrinsics.%NumberPrototype%.toLocaleString.arguments
  Tolerating undeletable intrinsics.%NumberPrototype%.toLocaleString.arguments
  Removing intrinsics.%NumberPrototype%.toLocaleString.prototype
  Tolerating undeletable intrinsics.%NumberPrototype%.toLocaleString.prototype === undefined
  Removing intrinsics.%PromisePrototype%.then.caller
  Tolerating undeletable intrinsics.%PromisePrototype%.then.caller
  Removing intrinsics.%PromisePrototype%.then.arguments
  Tolerating undeletable intrinsics.%PromisePrototype%.then.arguments
  Removing intrinsics.%PromisePrototype%.then.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.then.prototype === undefined
  Removing intrinsics.%PromisePrototype%.catch.caller
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.caller
  Removing intrinsics.%PromisePrototype%.catch.arguments
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.arguments
  Removing intrinsics.%PromisePrototype%.catch.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.prototype === undefined
  Removing intrinsics.%PromisePrototype%.finally.caller
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.caller
  Removing intrinsics.%PromisePrototype%.finally.arguments
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.arguments
  Removing intrinsics.%PromisePrototype%.finally.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.prototype === undefined
  Removing intrinsics.%StringPrototype%.localeCompare.caller
  Tolerating undeletable intrinsics.%StringPrototype%.localeCompare.caller
  Removing intrinsics.%StringPrototype%.localeCompare.arguments
  Tolerating undeletable intrinsics.%StringPrototype%.localeCompare.arguments
  Removing intrinsics.%StringPrototype%.localeCompare.prototype
  Tolerating undeletable intrinsics.%StringPrototype%.localeCompare.prototype === undefined
  Removing intrinsics.%FunctionPrototype%.toString.caller
  Tolerating undeletable intrinsics.%FunctionPrototype%.toString.caller
  Removing intrinsics.%FunctionPrototype%.toString.arguments
  Tolerating undeletable intrinsics.%FunctionPrototype%.toString.arguments
  Removing intrinsics.%FunctionPrototype%.toString.prototype
  Tolerating undeletable intrinsics.%FunctionPrototype%.toString.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.caller
  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.arguments
  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.name<get>.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.caller
  Removing intrinsics.%CompartmentPrototype%.name<get>.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.arguments
  Removing intrinsics.%CompartmentPrototype%.name<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.evaluate.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.evaluate.caller
  Removing intrinsics.%CompartmentPrototype%.evaluate.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.evaluate.arguments
  Removing intrinsics.%CompartmentPrototype%.evaluate.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.evaluate.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.module.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.module.caller
  Removing intrinsics.%CompartmentPrototype%.module.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.module.arguments
  Removing intrinsics.%CompartmentPrototype%.module.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.module.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.import.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.import.caller
  Removing intrinsics.%CompartmentPrototype%.import.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.import.arguments
  Removing intrinsics.%CompartmentPrototype%.load.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.load.caller
  Removing intrinsics.%CompartmentPrototype%.load.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.load.arguments
  Removing intrinsics.%CompartmentPrototype%.importNow.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.caller
  Removing intrinsics.%CompartmentPrototype%.importNow.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.arguments
  Removing intrinsics.%CompartmentPrototype%.importNow.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.prototype === undefined
Hermes VM done
Hermes tests complete
Removing: test/_hermes-smoke-dist.js
Removing: test/_hermes-smoke-dist.hbc

Security Considerations

Does this change introduce new assumptions or dependencies that, if violated, could introduce security vulnerabilities? How does this PR change the boundaries between mutually-suspicious components? What new authorities are introduced by this change, perhaps by new API calls?

Scaling Considerations

Does this change require or encourage significant increase in consumption of CPU cycles, RAM, on-chain storage, message exchanges, or other scarce resources? If so, can that be prevented or mitigated?

Documentation Considerations

Give our docs folks some hints about what needs to be described to downstream users. Backwards compatibility: what happens to existing data or deployments when this code is shipped? Do we need to instruct users to do something to upgrade their saved data? If there is no upgrade path possible, how bad will that be for users?

Testing Considerations

Every PR should of course come with tests of its own functionality. What additional tests are still needed beyond those unit tests? How does this affect CI, other test automation, or the testnet?

Compatibility Considerations

Does this change break any prior usage patterns? Does this change allow usage patterns to evolve?

Upgrade Considerations

What aspects of this PR are relevant to upgrading live production systems, and how should they be addressed?

Include *BREAKING*: in the commit message with migration instructions for any breaking change.

Update NEWS.md for user-facing changes.

Delete guidance from pull request description before merge (including this!)

@leotm leotm requested a review from erights December 5, 2024 20:46
@leotm leotm force-pushed the removeUnpermittedIntrinsics-cauterizeProperty-hermes branch from d12c2aa to d400b04 Compare December 5, 2024 20:47
@leotm leotm marked this pull request as ready for review December 5, 2024 20:59
Copy link
Contributor

@erights erights left a comment

Choose a reason for hiding this comment

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

Let's first solve the mystery of why some functions look like sloppy functions. If we cannot make these functions themselves strict, then the right thing for cauterize to do, or perhaps even an earlier repair phase, it to replace these functions with strict wrappers that forward to the original functions.

Comment on lines 66 to 72
if (
typeof obj === 'function' &&
(prop === 'caller' || prop === 'arguments')
) {
warn(`Tolerating undeletable ${subPath}`);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

But you first need to cauterize them, like I do with .prototype. In this case, you'd also

  • set them to undefined
  • make them non-writable non-configurable data properties
  • check the sloppy mode cases where these properties would change their value to verify that Hermes obeys the non-writable, non-configurable data property stability restriction.

But it's likely worse than that. These properties are symptomatic of these function being defined as sloppy functions, which we simply need to prohibit all access to. Builtin functions are supposed to act like strict functions, including the absence of .caller and .argument properties. On what functions are you seeing these properties? What is different about how they are defined?

Copy link
Contributor Author

@leotm leotm Dec 9, 2024

Choose a reason for hiding this comment

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

But it's likely worse than that.

very much so 😅

On what functions are you seeing these properties?

the functions in the output after list (in description), i've dug thru the list more thoroughly spotting it's .caller, .arguments and .prototype still, here's the breakdown

output after (detailed)
  Promise .caller

  Removing intrinsics.Promise.caller
  Tolerating undeletable intrinsics.Promise.caller
  Removing intrinsics.Promise.arguments
  Tolerating undeletable intrinsics.Promise.arguments

  Removing intrinsics.Promise._l
  Removing intrinsics.Promise._m
  Removing intrinsics.Promise._n

  Promise .resolve

  Removing intrinsics.Promise.resolve.caller
  Tolerating undeletable intrinsics.Promise.resolve.caller
  Removing intrinsics.Promise.resolve.arguments
  Tolerating undeletable intrinsics.Promise.resolve.arguments
  Removing intrinsics.Promise.resolve.prototype
  Tolerating undeletable intrinsics.Promise.resolve.prototype === undefined
  
  Promise .all

  Removing intrinsics.Promise.all.caller
  Tolerating undeletable intrinsics.Promise.all.caller
  Removing intrinsics.Promise.all.arguments
  Tolerating undeletable intrinsics.Promise.all.arguments
  Removing intrinsics.Promise.all.prototype
  Tolerating undeletable intrinsics.Promise.all.prototype === undefined

  Promise .reject

  Removing intrinsics.Promise.reject.caller
  Tolerating undeletable intrinsics.Promise.reject.caller
  Removing intrinsics.Promise.reject.arguments
  Tolerating undeletable intrinsics.Promise.reject.arguments
  Removing intrinsics.Promise.reject.prototype
  Tolerating undeletable intrinsics.Promise.reject.prototype === undefined

  Promise .race

  Removing intrinsics.Promise.race.caller
  Tolerating undeletable intrinsics.Promise.race.caller
  Removing intrinsics.Promise.race.arguments
  Tolerating undeletable intrinsics.Promise.race.arguments
  Removing intrinsics.Promise.race.prototype
  Tolerating undeletable intrinsics.Promise.race.prototype === undefined

  lockdown

  Removing intrinsics.lockdown.caller
  Tolerating undeletable intrinsics.lockdown.caller
  Removing intrinsics.lockdown.arguments
  Tolerating undeletable intrinsics.lockdown.arguments
  Removing intrinsics.lockdown.prototype
  Tolerating undeletable intrinsics.lockdown.prototype === undefined

  harden

  Removing intrinsics.harden.caller
  Tolerating undeletable intrinsics.harden.caller
  Removing intrinsics.harden.arguments
  Tolerating undeletable intrinsics.harden.arguments
  Removing intrinsics.harden.prototype
  Tolerating undeletable intrinsics.harden.prototype === undefined

  % Inert Function
  
  Removing intrinsics.%InertFunction%.caller
  Tolerating undeletable intrinsics.%InertFunction%.caller
  Removing intrinsics.%InertFunction%.arguments
  Tolerating undeletable intrinsics.%InertFunction%.arguments

  % Inert Generator Function

  Removing intrinsics.%InertGeneratorFunction%.caller
  Tolerating undeletable intrinsics.%InertGeneratorFunction%.caller
  Removing intrinsics.%InertGeneratorFunction%.arguments
  Tolerating undeletable intrinsics.%InertGeneratorFunction%.arguments

  % Inert Async Function

  Removing intrinsics.%InertAsyncFunction%.caller
  Tolerating undeletable intrinsics.%InertAsyncFunction%.caller
  Removing intrinsics.%InertAsyncFunction%.arguments
  Tolerating undeletable intrinsics.%InertAsyncFunction%.arguments

  % Initial Date

  Removing intrinsics.%InitialDate%.caller
  Tolerating undeletable intrinsics.%InitialDate%.caller
  Removing intrinsics.%InitialDate%.arguments
  Tolerating undeletable intrinsics.%InitialDate%.arguments

  % Shared Date

  Removing intrinsics.%SharedDate%.caller
  Tolerating undeletable intrinsics.%SharedDate%.caller
  Removing intrinsics.%SharedDate%.arguments
  Tolerating undeletable intrinsics.%SharedDate%.arguments
  Removing intrinsics.%SharedDate%.now.caller
  Tolerating undeletable intrinsics.%SharedDate%.now.caller
  Removing intrinsics.%SharedDate%.now.arguments
  Tolerating undeletable intrinsics.%SharedDate%.now.arguments
  Removing intrinsics.%SharedDate%.now.prototype
  Tolerating undeletable intrinsics.%SharedDate%.now.prototype === undefined

  % Initial GetStackString

  Removing intrinsics.%InitialGetStackString%.caller
  Tolerating undeletable intrinsics.%InitialGetStackString%.caller
  Removing intrinsics.%InitialGetStackString%.arguments
  Tolerating undeletable intrinsics.%InitialGetStackString%.arguments
  Removing intrinsics.%InitialGetStackString%.prototype
  Tolerating undeletable intrinsics.%InitialGetStackString%.prototype === undefined

  % Initial InitialError

  Removing intrinsics.%InitialError%.caller
  Tolerating undeletable intrinsics.%InitialError%.caller
  Removing intrinsics.%InitialError%.arguments
  Tolerating undeletable intrinsics.%InitialError%.arguments
  Removing intrinsics.%InitialError%.stackTraceLimit<get>.caller
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<get>.caller
  Removing intrinsics.%InitialError%.stackTraceLimit<get>.arguments
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<get>.arguments
  Removing intrinsics.%InitialError%.stackTraceLimit<get>.prototype
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<get>.prototype === undefined
  Removing intrinsics.%InitialError%.stackTraceLimit<set>.caller
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<set>.caller
  Removing intrinsics.%InitialError%.stackTraceLimit<set>.arguments
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<set>.arguments
  Removing intrinsics.%InitialError%.stackTraceLimit<set>.prototype
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<set>.prototype === undefined
  Removing intrinsics.%InitialError%.captureStackTrace.caller
  Tolerating undeletable intrinsics.%InitialError%.captureStackTrace.caller
  Removing intrinsics.%InitialError%.captureStackTrace.arguments
  Tolerating undeletable intrinsics.%InitialError%.captureStackTrace.arguments
  Removing intrinsics.%InitialError%.captureStackTrace.prototype
  Tolerating undeletable intrinsics.%InitialError%.captureStackTrace.prototype === undefined
  Removing intrinsics.%InitialError%.prepareStackTrace<get>.caller
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<get>.caller
  Removing intrinsics.%InitialError%.prepareStackTrace<get>.arguments
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<get>.arguments
  Removing intrinsics.%InitialError%.prepareStackTrace<get>.prototype
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<get>.prototype === undefined
  Removing intrinsics.%InitialError%.prepareStackTrace<set>.caller
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<set>.caller
  Removing intrinsics.%InitialError%.prepareStackTrace<set>.arguments
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<set>.arguments
  Removing intrinsics.%InitialError%.prepareStackTrace<set>.prototype
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<set>.prototype === undefined

  % Shared Error

  Removing intrinsics.%SharedError%.caller
  Tolerating undeletable intrinsics.%SharedError%.caller
  Removing intrinsics.%SharedError%.arguments
  Tolerating undeletable intrinsics.%SharedError%.arguments
  Removing intrinsics.%SharedError%.stackTraceLimit<get>.caller
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<get>.caller
  Removing intrinsics.%SharedError%.stackTraceLimit<get>.arguments
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<get>.arguments
  Removing intrinsics.%SharedError%.stackTraceLimit<get>.prototype
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<get>.prototype === undefined
  Removing intrinsics.%SharedError%.stackTraceLimit<set>.caller
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<set>.caller
  Removing intrinsics.%SharedError%.stackTraceLimit<set>.arguments
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<set>.arguments
  Removing intrinsics.%SharedError%.stackTraceLimit<set>.prototype
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<set>.prototype === undefined
  Removing intrinsics.%SharedError%.prepareStackTrace<get>.caller
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<get>.caller
  Removing intrinsics.%SharedError%.prepareStackTrace<get>.arguments
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<get>.arguments
  Removing intrinsics.%SharedError%.prepareStackTrace<get>.prototype
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<get>.prototype === undefined
  Removing intrinsics.%SharedError%.prepareStackTrace<set>.caller
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<set>.caller
  Removing intrinsics.%SharedError%.prepareStackTrace<set>.arguments
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<set>.arguments
  Removing intrinsics.%SharedError%.prepareStackTrace<set>.prototype
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<set>.prototype === undefined
  Removing intrinsics.%SharedError%.captureStackTrace.caller
  Tolerating undeletable intrinsics.%SharedError%.captureStackTrace.caller
  Removing intrinsics.%SharedError%.captureStackTrace.arguments
  Tolerating undeletable intrinsics.%SharedError%.captureStackTrace.arguments
  Removing intrinsics.%SharedError%.captureStackTrace.prototype
  Tolerating undeletable intrinsics.%SharedError%.captureStackTrace.prototype === undefined

  % Shared Math

  Removing intrinsics.%SharedMath%.random.caller
  Tolerating undeletable intrinsics.%SharedMath%.random.caller
  Removing intrinsics.%SharedMath%.random.arguments
  Tolerating undeletable intrinsics.%SharedMath%.random.arguments
  Removing intrinsics.%SharedMath%.random.prototype
  Tolerating undeletable intrinsics.%SharedMath%.random.prototype === undefined

  % Initial RegExp

  Removing intrinsics.%InitialRegExp%.caller
  Tolerating undeletable intrinsics.%InitialRegExp%.caller
  Removing intrinsics.%InitialRegExp%.arguments
  Tolerating undeletable intrinsics.%InitialRegExp%.arguments

  % Initial Shared RegExp

  Removing intrinsics.%SharedRegExp%.caller
  Tolerating undeletable intrinsics.%SharedRegExp%.caller
  Removing intrinsics.%SharedRegExp%.arguments
  Tolerating undeletable intrinsics.%SharedRegExp%.arguments

  % Initial SharedSymbol

  Removing intrinsics.%SharedSymbol%.caller
  Tolerating undeletable intrinsics.%SharedSymbol%.caller
  Removing intrinsics.%SharedSymbol%.arguments
  Tolerating undeletable intrinsics.%SharedSymbol%.arguments

  % Inert Compartment

  Removing intrinsics.%InertCompartment%.caller
  Tolerating undeletable intrinsics.%InertCompartment%.caller
  Removing intrinsics.%InertCompartment%.arguments
  Tolerating undeletable intrinsics.%InertCompartment%.arguments

  % Number Prototype

  Removing intrinsics.%NumberPrototype%.toLocaleString.caller
  Tolerating undeletable intrinsics.%NumberPrototype%.toLocaleString.caller
  Removing intrinsics.%NumberPrototype%.toLocaleString.arguments
  Tolerating undeletable intrinsics.%NumberPrototype%.toLocaleString.arguments
  Removing intrinsics.%NumberPrototype%.toLocaleString.prototype
  Tolerating undeletable intrinsics.%NumberPrototype%.toLocaleString.prototype === undefined

  % Promise Prototype

  Removing intrinsics.%PromisePrototype%.then.caller
  Tolerating undeletable intrinsics.%PromisePrototype%.then.caller
  Removing intrinsics.%PromisePrototype%.then.arguments
  Tolerating undeletable intrinsics.%PromisePrototype%.then.arguments
  Removing intrinsics.%PromisePrototype%.then.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.then.prototype === undefined
  Removing intrinsics.%PromisePrototype%.catch.caller
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.caller
  Removing intrinsics.%PromisePrototype%.catch.arguments
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.arguments
  Removing intrinsics.%PromisePrototype%.catch.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.prototype === undefined
  Removing intrinsics.%PromisePrototype%.finally.caller
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.caller
  Removing intrinsics.%PromisePrototype%.finally.arguments
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.arguments
  Removing intrinsics.%PromisePrototype%.finally.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.prototype === undefined

  % String Prototype

  Removing intrinsics.%StringPrototype%.localeCompare.caller
  Tolerating undeletable intrinsics.%StringPrototype%.localeCompare.caller
  Removing intrinsics.%StringPrototype%.localeCompare.arguments
  Tolerating undeletable intrinsics.%StringPrototype%.localeCompare.arguments
  Removing intrinsics.%StringPrototype%.localeCompare.prototype
  Tolerating undeletable intrinsics.%StringPrototype%.localeCompare.prototype === undefined

  % Function Prototype

  Removing intrinsics.%FunctionPrototype%.toString.caller
  Tolerating undeletable intrinsics.%FunctionPrototype%.toString.caller
  Removing intrinsics.%FunctionPrototype%.toString.arguments
  Tolerating undeletable intrinsics.%FunctionPrototype%.toString.arguments
  Removing intrinsics.%FunctionPrototype%.toString.prototype
  Tolerating undeletable intrinsics.%FunctionPrototype%.toString.prototype === undefined

  % Compartment Prototype

  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.caller
  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.arguments
  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.name<get>.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.caller
  Removing intrinsics.%CompartmentPrototype%.name<get>.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.arguments
  Removing intrinsics.%CompartmentPrototype%.name<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.evaluate.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.evaluate.caller
  Removing intrinsics.%CompartmentPrototype%.evaluate.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.evaluate.arguments
  Removing intrinsics.%CompartmentPrototype%.evaluate.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.evaluate.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.module.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.module.caller
  Removing intrinsics.%CompartmentPrototype%.module.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.module.arguments
  Removing intrinsics.%CompartmentPrototype%.module.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.module.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.import.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.import.caller
  Removing intrinsics.%CompartmentPrototype%.import.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.import.arguments
  Removing intrinsics.%CompartmentPrototype%.load.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.load.caller
  Removing intrinsics.%CompartmentPrototype%.load.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.load.arguments
  Removing intrinsics.%CompartmentPrototype%.importNow.caller
  Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.caller
  Removing intrinsics.%CompartmentPrototype%.importNow.arguments
  Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.arguments
  Removing intrinsics.%CompartmentPrototype%.importNow.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.prototype === undefined

in summary

  • Promise .caller
  • Promise .resolve (including .prototype)
  • Promise .all (including .prototype)
  • Promise .reject (including .prototype)
  • Promise .race (including .prototype)
  • lockdown (including .prototype)
  • harden (including .prototype)
  • % Inert Function
  • % Inert Generator Function
  • % Inert Async Function
  • % Initial Date
  • % Shared Date (including .prototype)
  • % Initial GetStackString
  • % Initial InitialError (including .prototype with stack traces)
  • % Shared Error (including .prototype with stack traces)
  • % Shared Math (including .prototype)
  • % Initial RegExp
  • % Initial Shared RegExp
  • % Initial SharedSymbol
  • % Inert Compartment
  • % Number Prototype (including .prototype)
  • % Promise Prototype (including .prototype)
  • % String Prototype (including .prototype)
  • % Function Prototype (including .prototype)
  • % Compartment Prototype (including .prototype)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is different about how they are defined?

should hopefully figure this soon from Hermes source code

Copy link
Contributor

@gibson042 gibson042 Dec 9, 2024

Choose a reason for hiding this comment

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

At least in the case of the Promise subtree, I have an answer: facebook/hermes#1009 (comment)

As described here, Hermes implements Promise by using the Promise polyfill https://github.com/then/promise, which unfortunately isn't fully compliant. This was done to ensure compatibility with React Native, which was already using it before Hermes.

...and that polyfill uses naïve functions: https://github.com/then/promise/blob/9d5851d7adefde580589a041ebfcbd030d39f6fd/src/core.js#L72

Promise.prototype.then = function(onFulfilled, onRejected) {
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

set them to undefined

unfortunately we can't cauterize .caller or .arguments on Hermes (like .prototype) for any intrinsic

i.e. obj[prop], obj[prop] = undefined, obj.caller, Promise.arguments, Promise.all.caller, etc

hit us with Uncaught TypeError: Restricted in strict mode

Copy link
Contributor Author

Choose a reason for hiding this comment

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

how about we handle this in the permit on Hermes (similar to earlier)

49f0f41

then remaining prototypes are successfully cauterized 🔥 too

Hermes VM output (fixed via permits.js)
endo git:(ses-hermes) ✗ yarn build:hermes && yarn test:hermes
-- Building 'hermes' version of SES --
Bundle size: 448750 bytes
Copied ./types.d.ts to ./dist/types.d.cts
Concatenating: dist/ses-hermes.cjs + test/_hermes-smoke.js
Generated: test/_hermes-smoke-dist.js
Executing: test/_hermes-smoke-dist.js on Hermes compiler
test/_hermes-smoke-dist.js:13021:27: warning: the variable "Compartment" was not declared in function "?anon_0_?anon_0_testCompartmentHooks"
  const compartment = new Compartment({}, {}, { resolveHook, importHook });
                          ^~~~~~~~~~~
test/_hermes-smoke-dist.js:13029:3: warning: the variable "assert" was not declared in function "?anon_0_?anon_0_testCompartmentHooks"
  assert(module);
  ^~~~~~
test/_hermes-smoke-dist.js:3828:12: warning: the variable "AggregateError" was not declared in anonymous function " 119#"
if( typeof AggregateError!==  'undefined') {
           ^~~~~~~~~~~~~~
test/_hermes-smoke-dist.js:7065:5: warning: the variable "console" was not declared in function "getOwnPropertyDescriptor"
    console.warn(
    ^~~~~~~
test/_hermes-smoke-dist.js:8625:5: warning: the variable "harden" was not declared in arrow function "makeCausalConsoleFromLogger"
    harden(baseConsole);
    ^~~~~~
test/_hermes-smoke-dist.js:12992:3: warning: the variable "lockdown" was not declared in arrow function "testLockdown"
  lockdown();
  ^~~~~~~~
Generated: test/_hermes-smoke-dist.hbc
Hermes compiler done
Executing generated bytecode file on Hermes VM
Skipping assertDirectEvalAvailable
Removing lockdown.prototype
Tolerating undeletable lockdown.prototype === undefined
Removing harden.prototype
Tolerating undeletable harden.prototype === undefined
Removing %InitialGetStackString%.prototype
Tolerating undeletable %InitialGetStackString%.prototype === undefined
SES Removing unpermitted intrinsics
  Removing intrinsics.Promise._l
  Removing intrinsics.Promise._m
  Removing intrinsics.Promise._n
  Removing intrinsics.Promise.resolve.prototype
  Tolerating undeletable intrinsics.Promise.resolve.prototype === undefined
  Removing intrinsics.Promise.all.prototype
  Tolerating undeletable intrinsics.Promise.all.prototype === undefined
  Removing intrinsics.Promise.reject.prototype
  Tolerating undeletable intrinsics.Promise.reject.prototype === undefined
  Removing intrinsics.Promise.race.prototype
  Tolerating undeletable intrinsics.Promise.race.prototype === undefined
  Removing intrinsics.lockdown.prototype
  Tolerating undeletable intrinsics.lockdown.prototype === undefined
  Removing intrinsics.harden.prototype
  Tolerating undeletable intrinsics.harden.prototype === undefined
  Removing intrinsics.%SharedDate%.now.prototype
  Tolerating undeletable intrinsics.%SharedDate%.now.prototype === undefined
  Removing intrinsics.%InitialGetStackString%.prototype
  Tolerating undeletable intrinsics.%InitialGetStackString%.prototype === undefined
  Removing intrinsics.%InitialError%.stackTraceLimit<get>.prototype
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<get>.prototype === undefined
  Removing intrinsics.%InitialError%.stackTraceLimit<set>.prototype
  Tolerating undeletable intrinsics.%InitialError%.stackTraceLimit<set>.prototype === undefined
  Removing intrinsics.%InitialError%.captureStackTrace.prototype
  Tolerating undeletable intrinsics.%InitialError%.captureStackTrace.prototype === undefined
  Removing intrinsics.%InitialError%.prepareStackTrace<get>.prototype
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<get>.prototype === undefined
  Removing intrinsics.%InitialError%.prepareStackTrace<set>.prototype
  Tolerating undeletable intrinsics.%InitialError%.prepareStackTrace<set>.prototype === undefined
  Removing intrinsics.%SharedError%.stackTraceLimit<get>.prototype
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<get>.prototype === undefined
  Removing intrinsics.%SharedError%.stackTraceLimit<set>.prototype
  Tolerating undeletable intrinsics.%SharedError%.stackTraceLimit<set>.prototype === undefined
  Removing intrinsics.%SharedError%.prepareStackTrace<get>.prototype
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<get>.prototype === undefined
  Removing intrinsics.%SharedError%.prepareStackTrace<set>.prototype
  Tolerating undeletable intrinsics.%SharedError%.prepareStackTrace<set>.prototype === undefined
  Removing intrinsics.%SharedError%.captureStackTrace.prototype
  Tolerating undeletable intrinsics.%SharedError%.captureStackTrace.prototype === undefined
  Removing intrinsics.%SharedMath%.random.prototype
  Tolerating undeletable intrinsics.%SharedMath%.random.prototype === undefined
  Removing intrinsics.%NumberPrototype%.toLocaleString.prototype
  Tolerating undeletable intrinsics.%NumberPrototype%.toLocaleString.prototype === undefined
  Removing intrinsics.%PromisePrototype%.then.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.then.prototype === undefined
  Removing intrinsics.%PromisePrototype%.catch.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.prototype === undefined
  Removing intrinsics.%PromisePrototype%.finally.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.prototype === undefined
  Removing intrinsics.%StringPrototype%.localeCompare.prototype
  Tolerating undeletable intrinsics.%StringPrototype%.localeCompare.prototype === undefined
  Removing intrinsics.%FunctionPrototype%.toString.prototype
  Tolerating undeletable intrinsics.%FunctionPrototype%.toString.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.name<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.evaluate.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.evaluate.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.module.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.module.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.importNow.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.prototype === undefined
Hermes VM done
Hermes tests complete
Removing: test/_hermes-smoke-dist.js
Removing: test/_hermes-smoke-dist.hbc

@leotm leotm marked this pull request as draft December 6, 2024 16:16
@leotm leotm marked this pull request as ready for review December 11, 2024 17:37
@leotm leotm requested a review from erights December 11, 2024 17:37
Copy link
Contributor Author

@leotm leotm left a comment

Choose a reason for hiding this comment

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

CI indicating flakey Browser tests failing atm again

Run npx playwright install --with-deps
  npx playwright install --with-deps
  shell: /usr/bin/bash -e {0}
Installing dependencies...
Switching to root user to install dependencies...
Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [14[2](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:2) B]
Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease
Get:[3](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:3) http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease
Get:[4](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:5) http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease [126 kB]
Get:[5](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:6) http://azure.archive.ubuntu.com/ubuntu noble-security InRelease [12[6](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:7) kB]
Hit:7 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease
Get:8 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [[7](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:8)01 kB]
Get:9 http://azure.archive.ubuntu.com/ubuntu noble-updates/main Translation-en [162 kB]
Get:10 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 Components [132 kB]
Get:11 http://azure.archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [727 kB]
Get:12 http://azure.archive.ubuntu.com/ubuntu noble-updates/universe Translation-en [216 kB]
Get:13 http://azure.archive.ubuntu.com/ubuntu noble-updates/universe amd64 Components [310 kB]
Get:14 http://azure.archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Components [212 B]
Get:15 http://azure.archive.ubuntu.com/ubuntu noble-updates/multiverse amd64 Components [940 B]
Get:16 http://azure.archive.ubuntu.com/ubuntu noble-backports/main amd64 Components [20[8](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:9) B]
Get:17 http://azure.archive.ubuntu.com/ubuntu noble-backports/universe amd64 Components [11.7 kB]
Get:18 http://azure.archive.ubuntu.com/ubuntu noble-backports/restricted amd64 Components [216 B]
Get:1[9](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:10) http://azure.archive.ubuntu.com/ubuntu noble-backports/multiverse amd64 Components [212 B]
Get:20 http://azure.archive.ubuntu.com/ubuntu noble-security/main amd64 Packages [501 kB]
Get:21 http://azure.archive.ubuntu.com/ubuntu noble-security/main Translation-en [[10](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:11)2 kB]
Get:22 http://azure.archive.ubuntu.com/ubuntu noble-security/main amd64 Components [7188 B]
Get:23 http://azure.archive.ubuntu.com/ubuntu noble-security/universe amd64 Components [51.9 kB]
Get:24 http://azure.archive.ubuntu.com/ubuntu noble-security/restricted amd64 Components [212 B]
Get:25 http://azure.archive.ubuntu.com/ubuntu noble-security/multiverse amd64 Components [212 B]
Fetched 3303 kB in 1s (6182 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
Package libasound2 is a virtual package provided by:
  liboss4-salsa-asound2 4.2-build2020-1ubuntu3
  libasound2t64 1.2.[11](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:12)-1build2 (= 1.2.11-1build2)

Package libicu70 is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source

E: Package 'libasound2' has no installation candidate
E: Package 'libicu70' has no installation candidate
E: Unable to locate package libffi7
E: Unable to locate package libx264-[16](https://github.com/endojs/endo/actions/runs/12281686710/job/34271115469?pr=2655#step:9:17)3
Failed to install browsers
Error: Installation process exited with code: 100
Error: Process completed with exit code 1.

re-running:suspect:

edit: opened

Copy link
Contributor

@erights erights left a comment

Choose a reason for hiding this comment

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

I really do not understand what Hermes is trying to do here with strictness, .caller, and .arguments. Do we know any of the Hermes people? Would be good to invite to an upcoming Endo meeting to discuss. But not today's as I will not be able to attend.

It is hard for me to form a judgement of what to do about this anomalous behavior without understanding what Hermes is trying to do, and why. At the moment, it makes no sense to me.

Once we understand this, I suspect the right fix is to send a PR upstream to Hermes, and then to do an expedient and reversible local "fix" until we can depend on that Hermes upgrade. That expedient temporary fix may very well be what you've already done. But I don't know enough yet to judge.

@gibson042
Copy link
Contributor

gibson042 commented Dec 11, 2024

For your amusement: implementation divergence w.r.t. function caller and arguments properties.

  • JSC and SM follow spec by defining them only on Function.prototype (but violate it by having distinct values for the 4 respective getter/setter functions).
  • V8 violates the spec by additionally defining them on Function() and non-strict function(){…} instances (non-writable and non-configurable null-valued data properties).
  • XS violates the spec by additionally defining caller [although correctly omitting arguments] on Function() and non-strict function(){…} instances (writable and configurable undefined-valued data properties).
  • Hermes violates the spec by omitting them from Function.prototype, and additionally by defining them on strict function(){…} instances (non-configurable accessor properties with %ThrowTypeError% getter/setter functions).
$ eshost -h 'Hermes,JavaScriptCore,SpiderMonkey,V8,*XS' -sx '
(function(){
  const gOPD = Object.getOwnPropertyDescriptors;
  const throwTypeErrors = new Set(
    [Function.prototype, function(){ "use strict"; }]
      .map(fn => gOPD(fn))
      .flatMap(d => [d?.caller, d?.arguments].flatMap(d => [d?.get, d?.set]))
      .filter(getOrSet => !!getOrSet),
  );
  if (throwTypeErrors.size === 0) throw Error("no %ThrowTypeError%");
  if (throwTypeErrors.size > 1) print(`(${throwTypeErrors.size}+ distinct %ThrowTypeError%s)`);
  const repr = val => {
    if (val === null || val === undefined) return String(val);
    if (throwTypeErrors.has(val)) return "%ThrowTypeError%";
    if (typeof val === "function") return `function ${val.name || String(val)}`;
    return `${typeof val} ${String(val)}`;
  };
  const cases = {
    "%Function.prototype%": Function.prototype,
    "Function()": Function(),
    "function(){}": function(){},
    "function(){ \"use strict\"; }": function(){ "use strict"; },
  };
  for(const [label, obj] of Object.entries(cases)) {
    print("\n# " + label);
    const descriptors = gOPD(obj);
    for(const k of Object.keys(descriptors).sort()) {
      if (!["arguments", "caller"].includes(k)) continue;
      const desc = descriptors[k];
      const { value, get, set } = desc;
      print(
        k,
        `[${["writable", "enumerable", "configurable"].filter(attr => desc[attr])}]:`,
        get || set ? `<get ${repr(get)}, set ${repr(set)}>` : repr(value),
      );
    }
  }
})();'
#### Hermes
# %Function.prototype%

# Function()

# function(){}

# function(){ "use strict"; }
arguments []: <get %ThrowTypeError%, set %ThrowTypeError%>
caller []: <get %ThrowTypeError%, set %ThrowTypeError%>

#### JavaScriptCore, SpiderMonkey
(4+ distinct %ThrowTypeError%s)

# %Function.prototype%
arguments [configurable]: <get %ThrowTypeError%, set %ThrowTypeError%>
caller [configurable]: <get %ThrowTypeError%, set %ThrowTypeError%>

# Function()

# function(){}

# function(){ "use strict"; }

#### Moddable XS
# %Function.prototype%
arguments [configurable]: <get %ThrowTypeError%, set %ThrowTypeError%>
caller [configurable]: <get %ThrowTypeError%, set %ThrowTypeError%>

# Function()
caller [writable,configurable]: undefined

# function(){}
caller [writable,configurable]: undefined

# function(){ "use strict"; }

#### V8
# %Function.prototype%
arguments [configurable]: <get %ThrowTypeError%, set %ThrowTypeError%>
caller [configurable]: <get %ThrowTypeError%, set %ThrowTypeError%>

# Function()
arguments []: null
caller []: null

# function(){}
arguments []: null
caller []: null

# function(){ "use strict"; }

And Hermes has an additional bug: in a module context, … EDIT: eshost -h Hermes -m doesn't exhibit any strictness, so the below observations are not reliable.

in a module context, function(){} is treated differently from function(){ "use strict"; } (cf. Strict Mode Code, "Module code is always strict mode code") for both these function properties and for arguments.callee.

$ for m in '' '-m'; do eshost $m -h Hermes -se 'Object.entries({
  "Function()": Function(),
  "function(){}": function(){},
  "function(){ \"use strict\"; }": function(){ "use strict"; },
})
.map(
  ([s, f]) => `${s} [${["caller", "arguments"].filter(p => Object.hasOwn(f, p))}]`,
)
.join("\n")'; done
#### Hermes
Function() []
function(){} []
function(){ "use strict"; } [caller,arguments]

#### Hermes
Function() []
function(){} []
function(){ "use strict"; } [caller,arguments]

@erights
Copy link
Contributor

erights commented Dec 11, 2024

@gibson042 , thanks for the thorough analysis! Do you happen to know if these non-conformances show up in test262 tests?

@mhofman
Copy link
Contributor

mhofman commented Dec 12, 2024

And Hermes has an additional bug: in a module context, function(){} is treated differently from function(){ "use strict"; } (cf. Strict Mode Code, "Module code is always strict mode code") for both these function properties and for arguments.callee.

What's the consequence of this? At best reaching for these properties doesn't throw for the non-explicitly strict functions? Still can't access .caller from the function I hope?

@gibson042
Copy link
Contributor

@gibson042 , thanks for the thorough analysis! Do you happen to know if these non-conformances show up in test262 tests?

Not that I know of. I opened tc39/test262#4340 to track it, and anba replied with references to tc39/ecma262#562 and https://github.com/claudepache/es-legacy-function-reflection . Do you know to what extent those are still relevant?

And Hermes has an additional bug: in a module context, function(){} is treated differently from function(){ "use strict"; } (cf. Strict Mode Code, "Module code is always strict mode code") for both these function properties and for arguments.callee.

What's the consequence of this? At best reaching for these properties doesn't throw for the non-explicitly strict functions? Still can't access .caller from the function I hope?

Right, we get undefined (as in non-strict mode) rather than the required error:

$ eshost -h Hermes,V8,SpiderMonkey -mse 'Object.entries({               
  "Function()": Function(),
  "function(){}": function(){},
  "function(){ \"use strict\"; }": function(){ "use strict"; },
}).map(Object.defineProperty(Function("entry", `
  const [s, f] = entry;
  f();
  try {
    const { caller } = f;
    return \`$\{s}.caller is $\{caller?.name || String(caller)}\`;
  } catch (err) {
    return \`$\{s}.caller throws $\{err.name} $\{err.message}\`;
  }
`), "name", { value: "checkCaller" }))
.join("\n")'
#### Hermes
Function().caller is undefined
function(){}.caller is undefined
function(){ "use strict"; }.caller throws TypeError Restricted in strict mode

#### SpiderMonkey, V8
Function().caller is null
function(){}.caller throws TypeError 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
function(){ "use strict"; }.caller throws TypeError 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

@leotm
Copy link
Contributor Author

leotm commented Dec 13, 2024

I really do not understand what Hermes is trying to do here with strictness, .caller, and .arguments. Do we know any of the Hermes people? Would be good to invite to an upcoming Endo meeting to discuss. But not today's as I will not be able to attend.

It is hard for me to form a judgement of what to do about this anomalous behavior without understanding what Hermes is trying to do, and why. At the moment, it makes no sense to me.

Once we understand this, I suspect the right fix is to send a PR upstream to Hermes, and then to do an expedient and reversible local "fix" until we can depend on that Hermes upgrade. That expedient temporary fix may very well be what you've already done. But I don't know enough yet to judge.

raised the issue here

with some more details that may help, @tmikov wdyt?

edit: Fixed and now configurable in Static Hermes

@leotm
Copy link
Contributor Author

leotm commented Dec 18, 2024

removeUnpermittedIntrinsics now works on Static Hermes branch 🎉

before facebook/hermes#1582 (comment)

endo git:(ses-hermes) ✗ yarn build:hermes && yarn test:hermes
-- Building 'hermes' version of SES --
Bundle size: 448793 bytes
Copied ./types.d.ts to ./dist/types.d.cts
Concatenating: dist/ses-hermes.cjs + test/_hermes-smoke.js
Generated: test/_hermes-smoke-dist.js
Executing: test/_hermes-smoke-dist.js on Hermes compiler...(warnings)
Generated: test/_hermes-smoke-dist.hbc
Hermes compiler done
Executing generated bytecode file on Hermes VM
Skipping assertDirectEvalAvailable
SES Removing unpermitted intrinsics
  Removing intrinsics.Promise._l
  Removing intrinsics.Promise._m
  Removing intrinsics.Promise._n
  Removing intrinsics.Promise.resolve.prototype
  Tolerating undeletable intrinsics.Promise.resolve.prototype === undefined
  Removing intrinsics.Promise.all.prototype
  Tolerating undeletable intrinsics.Promise.all.prototype === undefined
  Removing intrinsics.Promise.allSettled.prototype
  Tolerating undeletable intrinsics.Promise.allSettled.prototype === undefined
  Removing intrinsics.Promise.reject.prototype
  Tolerating undeletable intrinsics.Promise.reject.prototype === undefined
  Removing intrinsics.Promise.race.prototype
  Tolerating undeletable intrinsics.Promise.race.prototype === undefined
  Removing intrinsics.Promise.any.prototype
  Tolerating undeletable intrinsics.Promise.any.prototype === undefined
  Removing intrinsics.%PromisePrototype%.then.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.then.prototype === undefined
  Removing intrinsics.%PromisePrototype%.catch.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.prototype === undefined
  Removing intrinsics.%PromisePrototype%.finally.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.prototype === undefined
  failed to delete intrinsics.%FunctionPrototype%.caller TypeError: Property is not configurable
Uncaught TypeError: Property is not configurable

after

endo git:(ses-hermes) ✗ yarn build:hermes && yarn test:hermes
-- Building 'hermes' version of SES --
Bundle size: 448793 bytes
Copied ./types.d.ts to ./dist/types.d.cts
Concatenating: dist/ses-hermes.cjs + test/_hermes-smoke.js
Generated: test/_hermes-smoke-dist.js
Executing: test/_hermes-smoke-dist.js on Hermes compiler...(warnings)
Generated: test/_hermes-smoke-dist.hbc
Hermes compiler done
Executing generated bytecode file on Hermes VM
Skipping assertDirectEvalAvailable
SES Removing unpermitted intrinsics
  Removing intrinsics.Promise._l
  Removing intrinsics.Promise._m
  Removing intrinsics.Promise._n
  Removing intrinsics.Promise.resolve.prototype
  Tolerating undeletable intrinsics.Promise.resolve.prototype === undefined
  Removing intrinsics.Promise.all.prototype
  Tolerating undeletable intrinsics.Promise.all.prototype === undefined
  Removing intrinsics.Promise.allSettled.prototype
  Tolerating undeletable intrinsics.Promise.allSettled.prototype === undefined
  Removing intrinsics.Promise.reject.prototype
  Tolerating undeletable intrinsics.Promise.reject.prototype === undefined
  Removing intrinsics.Promise.race.prototype
  Tolerating undeletable intrinsics.Promise.race.prototype === undefined
  Removing intrinsics.Promise.any.prototype
  Tolerating undeletable intrinsics.Promise.any.prototype === undefined
  Removing intrinsics.%PromisePrototype%.then.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.then.prototype === undefined
  Removing intrinsics.%PromisePrototype%.catch.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.catch.prototype === undefined
  Removing intrinsics.%PromisePrototype%.finally.prototype
  Tolerating undeletable intrinsics.%PromisePrototype%.finally.prototype === undefined
  (intrinsics.%FunctionPrototype%.caller deleted)
  (intrinsics.%FunctionPrototype%.arguments deleted)
  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.name<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.prototype === undefined
Hermes VM done

are we happy with the temp permits.js fix? until Static Hermes is released and Hermes is deprecated


edit:

  Removing intrinsics.%CompartmentPrototype%.globalThis<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.globalThis<get>.prototype === undefined
  Removing intrinsics.%CompartmentPrototype%.name<get>.prototype
  Tolerating undeletable intrinsics.%CompartmentPrototype%.name<get>.prototype === undefined

looks like a separate issue

Copy link
Contributor

@erights erights left a comment

Choose a reason for hiding this comment

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

With the change, LGTM, thanks!

'use strict';
};

arrayForEach(getOwnPropertyNames(strict), prop => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer that it only be weird for 'caller' and 'arguments' specifically.

Suggested change
arrayForEach(getOwnPropertyNames(strict), prop => {
// TODO Remove this once we no longer support the Hermes that needed this.
arrayForEach(['caller', 'arguments'], prop => {

Is there any simple reliable check that we are on Hermes? If so, then perhaps condition on that check as well?

Copy link
Contributor Author

@leotm leotm Dec 19, 2024

Choose a reason for hiding this comment

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

for temp local debugging in #2334 i've used AsyncGeneratorFunctionInstance

// packages/ses/src/commons.js
// ...
export const AsyncGeneratorFunctionInstance =
  getAsyncGeneratorFunctionInstance();

/**
 * Print on Hermes VM
 * @param  {...any} args Arguments to print
 */
export const printHermes = (...args) => {
  if (AsyncGeneratorFunctionInstance === undefined) {
    // @ts-expect-error ts(2554)
    // eslint-disable-next-line no-undef
    print(args);
  }
};

but you're right time for a better reliable check (if exists),
i'll follow-up and add the condition once i find it

edit: fixed the CI lint (removed getOwnPropertyNames import)

@leotm leotm force-pushed the removeUnpermittedIntrinsics-cauterizeProperty-hermes branch 3 times, most recently from 8915363 to d057faf Compare December 19, 2024 14:37
@leotm leotm force-pushed the removeUnpermittedIntrinsics-cauterizeProperty-hermes branch from d057faf to fb31aca Compare December 19, 2024 14:52
@leotm leotm merged commit 14731fe into master Dec 19, 2024
14 of 15 checks passed
@leotm leotm deleted the removeUnpermittedIntrinsics-cauterizeProperty-hermes branch December 19, 2024 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants