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

Normative: Coerce evaluate's argument to string #319

Closed
wants to merge 1 commit into from

Conversation

leobalter
Copy link
Member

Ref #318

@leobalter
Copy link
Member Author

It's important to highlight @caridy's PoV from #304 (comment) here:

it is that strict to avoid the confusion with eval, which has a bizarre behavior by returning the value of the first argument if it is not a string value. As for the second one, I believe it is a trying to match the semantics of dynamic import, which does not exhibit any bizarre behavior. An alternative here is to make evaluate to perform type coercion, which I'm open to, which will mean people moving from eval(value) to myRealm.evaluate(value) will not result in an error, but the behavior will not match the original behavior of eval either.

@@ -292,12 +292,15 @@ <h1>Realm.prototype.evaluate ( _sourceText_ )</h1>
<emu-alg>
1. Let _O_ be *this* value.
1. Perform ? ValidateRealmObject(_O_).
1. If Type(_sourceText_) is not String, throw a *TypeError* exception.
1. Let _sourceStr_ be ? ToString(_sourceText_).
Copy link
Member

Choose a reason for hiding this comment

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

Why not keep it throw? 👀

Copy link
Member Author

Choose a reason for hiding this comment

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

This came in as a suggestion from @gibson042's spec review (#304).

As I pointed out in the comment above, @caridy shared some agreement to this change in #304 (comment) and #318.

The high level overview for the change is to make the evaluate method consistent with importValue and other general methods often seen in ECMAScript.

The main reason it throws today is to make it slightly consistent with eval, but the consistency is not perfect. eval will just return the argument if it's not a string:

const obj = { toString() { return "40 + 2"; } };

eval(obj) === obj; // and not 42

With the Realms API, we have the current outcome:

const obj = { toString() { return "40 + 2"; } };
const r = new Realm();

r.evaluate(obj); // throws TypeError
r.evaluate(new String('Math.PI')); // TypeError

With this PR, we could have some better usage of argument:

const obj = { toString() { return "40 + 2"; } };
const r = new Realm();

r.evaluate(obj); // 42
r.evaluate(new String('Math.PI')) === Math.PI; // true, considering Math.PI is not altered in both realms

We do have precedent for things like this today, such as the Function constructor coercing args and body to string. The approach from this PR only makes evaluate a bit more consistent with functionality of other methods and putting it slightly farther from eval. Anyway, I believe the weird behavior from eval on non string arguments is undesirable legacy, but part of web reality.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

I think it'd make sense to close this PR and leave the behavior as is. importValue should be analogous to import() (which coerces) and evaluate should be analogous to eval (which returns its input when passed not a string--the natural analogue is to throw, since that behavior is actually surprising).

Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to accidentally enables some attack surface?

Copy link
Member Author

Choose a reason for hiding this comment

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

I find validity in both sides, I don't have a strong preference and I can see a good reason either way.

Copy link
Member

Choose a reason for hiding this comment

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

That example falls into what I mentioned above, that there's similar behavior elsewhere, so I don't think it expands the "vulnerability" meaningfully.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@mikesamuel where is your proposal to use a non-string eval argument for a trusted-type-like branding for approved evaluation in a CSP-no-eval context? If you could answer quickly, that'd be great because we're in the midst of a tc39 meeting where this may become relevant. Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

@ljharb that's it. Thanks!

Copy link
Member

@mhofman mhofman Jul 13, 2021

Choose a reason for hiding this comment

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

I'm not sure I understand the benefit in coercing the source argument to a string, besides making it more similar to the Function constructor. And given that the callable boundary prevents both access to the other Realm's Function.[[Construct]] and passing non primitive arguments, it should be impossible to coerce to string and evaluate an object in another isolated realm using that route.

Comment on lines +301 to +302
<emu-note type=editor>
The argument _sourceText_ is coerced to a string value and this behavior differs from <emu-xref href="#sec-eval-x"></emu-xref>, which has a different behavior for non-String arguments. `eval(x)` returns `x` - with no evaluation - if `x` is not a String.

Choose a reason for hiding this comment

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

I found @caridy's explanation satisfactory, and don't have strong feelings about throwing vs. coercing. But I do think a callout of the behavioral difference should remain visible in the rendered spec.

@leobalter
Copy link
Member Author

After some discussion with @caridy, we agreed to hold on and not move this PR forward for now. The reasons to keep the API as is have a good weight to also avoid such a noise before this TC39 meeting.

I'm gonna hold on this PR and I'm pretty sure I can do a better recap to previous discussion and works such as those pointed out by @erights and @ljharb.

Thanks everyone for the quick feedback!

@leobalter leobalter closed this Jul 13, 2021
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.

7 participants