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

[WIP] src: intercept property descriptors on sandbox #15114

Closed
wants to merge 3 commits into from

Conversation

targos
Copy link
Member

@targos targos commented Aug 31, 2017

The main intent of this PR is to fix #2734.
It's not completely ready because one existing test is broken. I'm opening the PR now to get some help.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

src,vm

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. vm Issues and PRs related to the vm subsystem. labels Aug 31, 2017
if (desc.has_configurable()) {
desc_copy.set_configurable(desc.configurable());
}
Maybe<bool> result = ctx->sandbox()->DefineProperty(context, property, desc_copy);
Copy link
Member Author

Choose a reason for hiding this comment

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

@fhinkel It feels wrong to me having to make manual copies of the PropertyDescriptor. Am I missing something?

Copy link
Member

Choose a reason for hiding this comment

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

It's more or less correct. You have to know whether you want a value or accessor descriptor. enumerable, configurable, and DefineProp() is the same for both cases though.

Copy link
Member

Choose a reason for hiding this comment

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

@targos Can you refactor the copying process into a new function? I suspect it might come handy in the future, and it'll also be nice to write the DefineProperty bit only once.

Copy link
Member

Choose a reason for hiding this comment

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

I haven't tried playing around with it, but it does seem like one should be able to just pass in desc without making a copy. I'm sure there's an excellent reason why not tho :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

@jasnell I can't just pass desc because it's a const reference. DefineProperty wants a non-const reference.

Copy link
Member

Choose a reason for hiding this comment

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

sigh... ok yeah, that's the kind of question that is easily answered by reading the docs ;-) That is unfortunate.

@targos
Copy link
Member Author

targos commented Aug 31, 2017

The failing test is here:

assert.throws(() => vm.runInContext('"use strict"; x = 0', ctx),
/Cannot assign to read only property 'x'/);

@targos
Copy link
Member Author

targos commented Aug 31, 2017

/cc @TimothyGu @fhinkel @domenic

@fhinkel
Copy link
Member

fhinkel commented Aug 31, 2017

I think @AnnaMag already has a PR open for this. Her PR removes the copyProperties() and thus fixes most of the vm issues.

Copy link
Member

@TimothyGu TimothyGu left a comment

Choose a reason for hiding this comment

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

@AnnaMag's PR: #13265

I suppose it's okay to apply the ES6-aware parts first, before the more complete CopyProperties() removal. However, I think https://codereview.chromium.org/2807333003/ might be a crucial part of getting this fully working even not counting CopyProperties() removal, so I'd wait for @AnnaMag's reply before proceeding.

args.GetReturnValue().Set(prop_attr);
if (!maybe_prop_desc.IsEmpty()) {
Local<Value> prop_desc = maybe_prop_desc.ToLocalChecked();
if (!prop_desc->IsUndefined()) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this check necessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

When the property does not exist on the sandbox, the property descriptor is undefined. In that case we don't intercept the call (so that Object.getOwnPropertyDescriptor(this, 'Object') can get it from the global object)

if (desc.has_configurable()) {
desc_copy.set_configurable(desc.configurable());
}
Maybe<bool> result = ctx->sandbox()->DefineProperty(context, property, desc_copy);
Copy link
Member

Choose a reason for hiding this comment

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

@targos Can you refactor the copying process into a new function? I suspect it might come handy in the future, and it'll also be nice to write the DefineProperty bit only once.

if (result.IsJust()) {
args.GetReturnValue().Set(result.FromJust());
}
}
Copy link
Member

Choose a reason for hiding this comment

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

else { UNREACHABLE(); }?

@targos
Copy link
Member Author

targos commented Aug 31, 2017

@TimothyGu I don't see how I can refactor the copy to a function. Do you have an idea, knowing that PropertyDescriptor has no copy constructor?

@jasnell
Copy link
Member

jasnell commented Aug 31, 2017

@targos... for that failing test, looking at it now. This is the bit of code that is failing: https://github.com/targos/node/blob/3eceb415fa3049764befd0e3f2149df00228d9f6/src/node_contextify.cc#L431-L432

There are two specific issues here:

  1. The attributes do not appear to be getting set correctly and
  2. When the value is read_only, it simply returns without throwing

I'm still looking at why the first one is happening, but for the second one, to match the expected behavior, the setter should likely throw instead of simply returning.

@jasnell
Copy link
Member

jasnell commented Aug 31, 2017

@targos ... a patch like this gets the test-vm-context.js test working with one small tweak..

diff --git a/src/node_contextify.cc b/src/node_contextify.cc
index 10bd46b..e40039f 100644
--- a/src/node_contextify.cc
+++ b/src/node_contextify.cc
@@ -414,6 +414,7 @@ class ContextifyContext {
       const PropertyCallbackInfo<Value>& args) {
     ContextifyContext* ctx;
     ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As<Object>());
+    Environment* env = Environment::GetCurrent(args.GetIsolate());

     // Still initializing
     if (ctx->context_.IsEmpty())
@@ -421,15 +422,16 @@ class ContextifyContext {

     auto attributes = PropertyAttribute::None;
     bool is_declared =
-        ctx->global_proxy()->GetRealNamedPropertyAttributes(ctx->context(),
-                                                            property)
+        ctx->sandbox()->GetRealNamedPropertyAttributes(ctx->context(), property)
         .To(&attributes);
     bool read_only =
         static_cast<int>(attributes) &
         static_cast<int>(PropertyAttribute::ReadOnly);
-
-    if (is_declared && read_only)
+    if (is_declared && read_only) {
+      if (args.ShouldThrowOnError())
+        env->ThrowError("Cannot assign to read only property");
       return;
+    }

     // true for x = 5
     // false for this.x = 5
diff --git a/test/parallel/test-vm-context.js b/test/parallel/test-vm-context.js
index 7e54047..9e770f5 100644
--- a/test/parallel/test-vm-context.js
+++ b/test/parallel/test-vm-context.js
@@ -116,5 +116,5 @@ vm.runInContext('x = 0', ctx);                      // Does not throw but x...
 assert.strictEqual(vm.runInContext('x', ctx), 42);  // ...should be unaltered.

 assert.throws(() => vm.runInContext('"use strict"; x = 0', ctx),
-              /Cannot assign to read only property 'x'/);
+              /Cannot assign to read only property/);
 assert.strictEqual(vm.runInContext('x', ctx), 42);

To make the error message match perfectly, we would need to grab the name of the property (just slightly trickier with Symbol props) and include that in the error message.

@targos
Copy link
Member Author

targos commented Sep 1, 2017

@jasnell thanks. I made that change. I also added a check to the PropertyDefinerCallback for when the property is non-configurable.

@targos
Copy link
Member Author

targos commented Sep 1, 2017

While I was working on this, I found a possibly unexpected behavior with calls like Reflect.ownKeys(this). I added a failing test here: https://github.com/targos/node/blob/8176f21cc70467899c83c2a072569550ddf08b25/test/known_issues/test-vm-ownkeys.js
Am I correct to think that it should work?

Edit: currently, the three tests return ['a']

@TimothyGu
Copy link
Member

@targos In case you want to go down the route of throwing the error manually, V8 uses the following algorithm:

"Cannot assign to read only property '%s' of %s '%s'",
    property->ToDetailString(), ctx->sandbox()->TypeOf(), ctx->sandbox()->ToDetailString()

Still not a fan though. Also note that certain other errors need to be checked as well, such as an accessor property w/o a setter.

@jasnell
Copy link
Member

jasnell commented Sep 1, 2017

I'll have to look into the Reflect.ownKeys(this) issue further but so far this change LGTM

@targos
Copy link
Member Author

targos commented Sep 1, 2017

So... I did what I could for the error messages with my limited C++ knowledge... Please make suggestions if you know how to improve that :)

@TimothyGu thank you, we're almost there! Compared to the error thrown by V8, only the last string is inconsistent: ctx->sandbox->ToDetailString() returns [object Object]. The V8 error has #<Object>.

inline const char* ToString(Local<Value> value) {
String::Utf8Value utf8(value);
return *utf8;
}
Copy link
Member

Choose a reason for hiding this comment

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

Uh – This won’t work. :/ Once utf8 goes out of scope, it also potentially frees the memory used for storing value in its UTF-8 form … I think you need to inline this everywhere (i.e. create distinct Utf8Values for the different values).

Copy link
Member

@addaleax addaleax Sep 1, 2017

Choose a reason for hiding this comment

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

Or, alternatively, you could probably do this (warning, haven’t compiled or tried it):

std::ostream& operator<<(std::ostream& os, Local<Value> value) {
  String::Utf8Value utf8(value);
  return os << *utf8;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. That seems to work.

@targos
Copy link
Member Author

targos commented Sep 4, 2017

@TimothyGu
Copy link
Member

@targos

Compared to the error thrown by V8, only the last string is inconsistent: ctx->sandbox->ToDetailString() returns [object Object]. The V8 error has #<Object>.

Hmm. ToDetailString uses the same algorithm as V8 when figuring out what to print... in particular it looks at the toString property to determine whether to use #<X> (for when toString === Object.prototype.toString) or [object X]. I'm frankly not quite sure what's going on here.

@targos
Copy link
Member Author

targos commented Sep 5, 2017

@TimothyGu I thought it could be because the wrong context was used so I tried with the main context and the contextified one but the result is the same :(

@targos
Copy link
Member Author

targos commented Sep 5, 2017

Confirmed this condition is not met:

} else if (*to_string == *isolate->object_to_string()) {

@nodejs/v8 any idea?

@bmeurer
Copy link
Member

bmeurer commented Sep 5, 2017

cc @fhinkel @isheludko

@BridgeAR
Copy link
Member

Ping @nodejs/v8

@psmarshall
Copy link
Contributor

@hashseed Any ideas?

@BridgeAR
Copy link
Member

I am not sure how we can properly progress further. It seems hard to get any further insight into the issue. @targos is the current status quo of the PR already better than the situation before?

@AnnaMag
Copy link
Member

AnnaMag commented Oct 3, 2017

My apologies for the delay in my reaction to this.
As @fhinkel mentioned, #13265 provides a fix for this and most other vm issues. The PR was stalled due to https://codereview.chromium.org/2807333003/.
IMHO it is worth waiting for #13265 to land. I'm working on moving things forward.

@fhinkel fhinkel mentioned this pull request Oct 18, 2017
8 tasks
@targos
Copy link
Member Author

targos commented Oct 21, 2017

Closing in favor of #16293.

@targos targos closed this Oct 21, 2017
fhinkel added a commit to fhinkel/node that referenced this pull request Oct 23, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

Refs: nodejs#6283
Refs: nodejs#15114
Refs: nodejs#13265

Fixes: nodejs#2734
Fixes: nodejs#10223
Fixes: nodejs#11803
Fixes: nodejs#11902
fhinkel added a commit to fhinkel/node that referenced this pull request Oct 23, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

PR-URL: nodejs#16293
Fixes: nodejs#2734
Fixes: nodejs#10223
Fixes: nodejs#11803
Fixes: nodejs#11902
Ref: nodejs#6283
Ref: nodejs#15114
Ref: nodejs#13265
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
addaleax pushed a commit to ayojs/ayo that referenced this pull request Oct 26, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs/node#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs/node#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

PR-URL: nodejs/node#16293
Fixes: nodejs/node#2734
Fixes: nodejs/node#10223
Fixes: nodejs/node#11803
Fixes: nodejs/node#11902
Ref: nodejs/node#6283
Ref: nodejs/node#15114
Ref: nodejs/node#13265
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
addaleax pushed a commit to ayojs/ayo that referenced this pull request Dec 7, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs/node#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs/node#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

PR-URL: nodejs/node#16293
Fixes: nodejs/node#2734
Fixes: nodejs/node#10223
Fixes: nodejs/node#11803
Fixes: nodejs/node#11902
Ref: nodejs/node#6283
Ref: nodejs/node#15114
Ref: nodejs/node#13265
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
@targos targos deleted the fix-2734 branch October 17, 2020 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. vm Issues and PRs related to the vm subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

vm: accessor properties get converted to data properties inside the vm
10 participants