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

Array.copyWithin should throw when invoked on TypedArray with missing slots #4790

Merged
merged 1 commit into from
Mar 9, 2018

Conversation

IrinaYatsenko
Copy link
Contributor

@IrinaYatsenko IrinaYatsenko commented Mar 7, 2018

Fixes #4356

  delete obj.foo non-configurable delete ta.foo configurable delete ta.foo doesn't exist delete ta.foo non-configurable delete ta[index] Array.copyWithin with delete
strict throw delete, return true nop, return true throw throw throw
non-strict nop, return false delete, return true nop, return true nop, return false nop, return false throw

@@ -4818,7 +4818,8 @@ namespace Js
#endif
}

if (result == FALSE && (propertyOperationFlags & PropertyOperation_StrictMode))
if (result == FALSE &&
(propertyOperationFlags & (PropertyOperation_StrictMode | PropertyOperation_ThrowOnDeleteIfNotConfig)))
Copy link
Contributor

@MSLaguana MSLaguana Mar 7, 2018

Choose a reason for hiding this comment

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

This looks reasonable to me, but it does make me wonder whether there's some other scenario somewhere that might directly call into someArray->DeleteItem and expect that it throws when appropriate; a quick look around showed code like this: https://github.com/Microsoft/ChakraCore/blob/master/lib/Runtime/Library/JavascriptArray.cpp#L5649 which I think converts the false return into throwing explicitly, but seems to expect the typed array will do something with the flag it is given rather than completely ignoring it. Do you know if there are any other potential scenarios? #Resolved

Copy link
Contributor

@boingoing boingoing Mar 8, 2018

Choose a reason for hiding this comment

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

+1

Not sure this is the right place for this check. When result == FALSE, it might be for another reason other than not configurable. #Resolved

Copy link
Contributor

@digitalinfinity digitalinfinity Mar 9, 2018

Choose a reason for hiding this comment

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

result looks like it always is returned by JavascriptOperators::DeleteProperty. What other reason could it return false for? #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks to me like result is only false in the non-configurable case, so this should work.

Like Jimmy mentioned, various other methods invite misuse because they accept a PropertyOperationFlags parameter but don't pay attention to PropertyOperation_ThrowOnDeleteIfNotConfig. It seems somewhat inconsistent; some methods like PathTypeHandlerBase::DeleteProperty do respect that flag, but others don't. I'm not sure if it's worth trying to track them all down at this point.


In reply to: 173333391 [](ancestors = 173333391)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After all, it seems that it's better to have this handling in TypedArrayBase::DeleteItem as we discussed before :)


In reply to: 173024703 [](ancestors = 173024703)

@boingoing
Copy link
Contributor

boingoing commented Mar 8, 2018

@dotnet-bot test Pre-Merge signing tool please #Resolved

@MSLaguana
Copy link
Contributor

MSLaguana commented Mar 8, 2018

That's not part of the dotnet CI; I think a push will be required to trigger that. #Resolved

@boingoing boingoing closed this Mar 8, 2018
@boingoing boingoing reopened this Mar 8, 2018
@@ -9131,7 +9131,8 @@ namespace Js
}
else
{
JS_REENTRANT(jsReentLock, obj->DeleteItem(toIndex, PropertyOperation_ThrowOnDeleteIfNotConfig));
JavascriptOperators::OP_DeleteElementI(obj, JavascriptNumber::ToVar(toIndex, scriptContext), scriptContext, PropertyOperation_ThrowOnDeleteIfNotConfig);
Copy link
Contributor

@digitalinfinity digitalinfinity Mar 9, 2018

Choose a reason for hiding this comment

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

Don't we still need to wrap this in JS_REENTRANT? #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

For more context, we explicitly mark any lines in here that are allowed to call back into script (it would be a bug if any line without such a marking could call script). This pattern is useful because we can assume that certain things don't change as long as we haven't called into script (for instance, an instance of JavascriptArray hasn't magically transformed itself into an instance of ES5Array). The object here could be a Proxy with a deleteProperty handler, in which case this deletion operation would call script.


In reply to: 173333445 [](ancestors = 173333445)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the explanations! I'm reverting this file but I might need to follow up offline to understand better about the Proxies and reentrancy :)


In reply to: 173337067 [](ancestors = 173337067,173333445)

@@ -4818,7 +4818,8 @@ namespace Js
#endif
}

if (result == FALSE && (propertyOperationFlags & PropertyOperation_StrictMode))
if (result == FALSE &&
(propertyOperationFlags & (PropertyOperation_StrictMode | PropertyOperation_ThrowOnDeleteIfNotConfig)))
{
JavascriptError::ThrowCantDelete(propertyOperationFlags, scriptContext, GetPropertyDisplayNameForError(index, scriptContext)->GetString());
Copy link
Contributor

@boingoing boingoing Mar 9, 2018

Choose a reason for hiding this comment

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

Doesn't JavascriptError::ThrowCantDelete already check propertyOperationFlags? Can we write this as:

if (!result)
{
    JavascriptError::ThrowCantDelete(propertyOperationFlags, scriptContext, GetPropertyDisplayNameForError(index, scriptContext)->GetString());
}
``` #Resolved

@@ -9131,7 +9131,8 @@ namespace Js
}
else
{
JS_REENTRANT(jsReentLock, obj->DeleteItem(toIndex, PropertyOperation_ThrowOnDeleteIfNotConfig));
JavascriptOperators::OP_DeleteElementI(obj, JavascriptNumber::ToVar(toIndex, scriptContext), scriptContext, PropertyOperation_ThrowOnDeleteIfNotConfig);
//JS_REENTRANT(jsReentLock, obj->DeleteItem(toIndex, PropertyOperation_ThrowOnDeleteIfNotConfig));
Copy link
Contributor

@boingoing boingoing Mar 9, 2018

Choose a reason for hiding this comment

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

Also nitpick, please delete this commented line. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm moving the throw into TypedArrayBase to be aligned with other DeleteProperty/DeleteItem handlers, so there will be no change here.


In reply to: 173349228 [](ancestors = 173349228)

@@ -694,7 +694,7 @@ namespace Js
return false;
}

bool JavascriptError::ThrowCantDelete(PropertyOperationFlags flags, ScriptContext* scriptContext, PCWSTR varName)
bool JavascriptError::ThrowCantDeleteIfStrictModeOrNonconfigurable(PropertyOperationFlags flags, ScriptContext* scriptContext, PCWSTR varName)
Copy link
Contributor Author

@IrinaYatsenko IrinaYatsenko Mar 9, 2018

Choose a reason for hiding this comment

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

ThrowCantDeleteIfStrictModeOrNonconfigurable [](start = 26, length = 44)

Ugly name, but similar to "ThrowCantDeleteIfStrictMode". #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

ThrowCantDeleteIfRequiredForNonConfigurableProperty? Current name sounds like it would throw if the property was nonconfigurable (regardless of the operation flags), which isn't true (and in fact this method has no way to even know if the property was configurable, which must be checked by the caller).


In reply to: 173516752 [](ancestors = 173516752)

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, on the topic of this method, I don't like how it forces the caller to do a possibly-expensive conversion to string before figuring out whether we need it. I'd rather take a Var and do GetPropertyDisplayNameForError(varName, scriptContext)->GetString() only once we determine that we actually need to throw. Throwing is super expensive anyway, so any additional conversion cost there doesn't particularly matter. Anyhow, this wasn't introduced by your change, so feel free to leave as-is.


In reply to: 173520340 [](ancestors = 173520340,173516752)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. name: it throws either in strict mode or if non-configurable. Because this information can only be extracted from the flags, I think the name is more clear now (it doesn't give the impression that it guarantees to throw anymore).
  2. string vs var: the callers seem to differ in how they get hold of the string and I don't have a good grasp of details to assess whether var would make perf better for them.

In reply to: 173521221 [](ancestors = 173521221,173520340,173516752)

Copy link
Contributor

Choose a reason for hiding this comment

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

Would be fair to have an overload of this method which takes a Var for varName and then use GetPropertyDisplayNameForError if we'll throw an error. Otherwise don't do the conversion. Could save some time in that case.

@@ -4818,11 +4818,6 @@ namespace Js
#endif
}

if (result == FALSE && (propertyOperationFlags & PropertyOperation_StrictMode))
Copy link
Contributor

@MSLaguana MSLaguana Mar 9, 2018

Choose a reason for hiding this comment

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

To try and catch any errors in future, it might be worth to add an assert here along the lines of Assert(result || !(propertyOperationFlags & PropertyOperation_StrictMode); that way there is no overhead in release builds, but we should catch regressions in testing. #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe result || !(propertyOperationFlags & (PropertyOperation_StrictMode | PropertyOperation_ThrowOnDeleteOrNotConfig)), since the point of this change is updating the contract elsewhere to respect both flags


In reply to: 173516816 [](ancestors = 173516816)

@@ -851,6 +851,13 @@ namespace Js
return PropertyQueryFlags::Property_NotFound_NoProto;
}

BOOL TypedArrayBase::DeleteItem(uint32 index, Js::PropertyOperationFlags flags)
Copy link
Contributor Author

@IrinaYatsenko IrinaYatsenko Mar 9, 2018

Choose a reason for hiding this comment

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

flags [](start = 77, length = 5)

when running in strict mode, the flags will contain PropertyOperation_StrictMode
methods such as copyWithin would pass PropertyOperation_ThrowOnDeleteIfNotConfig #Resolved

@@ -912,7 +912,8 @@ namespace Js
else if (!(descriptor->Attributes & PropertyConfigurable))
{
// Let/const properties do not have attributes and they cannot be deleted
JavascriptError::ThrowCantDelete(propertyOperationFlags, scriptContext, propertyNameString->GetString());
JavascriptError::ThrowCantDeleteIfStrictModeOrNonconfigurable(
Copy link
Contributor Author

@IrinaYatsenko IrinaYatsenko Mar 9, 2018

Choose a reason for hiding this comment

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

ThrowCantDeleteIfStrictModeOrNonconfigurable [](start = 33, length = 44)

I wonder whether this one (and a few other current callers of ThrowCantDeleteIfStrictModeOrNonconfigurable) should actually call ThrowCantDeleteIfStrictMode because I don't think the flags would be set here to PropertyOperation_ThrowOnDeleteIfNotConfig (but I don't know how to confirm this). #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems safer to call the one that respects both flags. If ThrowOnDeleteIfNotConfig is never set, then it would make no difference in behavior.


In reply to: 173517742 [](ancestors = 173517742)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree, that it's safer. I was wondering about whether we could limit usage of ThrowCantDeleteIfStrictModeOrNonconfigurable (because I don't like this method :)).


In reply to: 173523027 [](ancestors = 173523027,173517742)

@@ -879,7 +879,8 @@ namespace Js
}
if (!(attr & ObjectSlotAttr_Configurable))
{
JavascriptError::ThrowCantDelete(PropertyOperation_None, scriptContext, scriptContext->GetPropertyName(propertyId)->GetBuffer());
JavascriptError::ThrowCantDeleteIfStrictModeOrNonconfigurable(
flags, scriptContext, scriptContext->GetPropertyName(propertyId)->GetBuffer());
Copy link
Contributor Author

@IrinaYatsenko IrinaYatsenko Mar 9, 2018

Choose a reason for hiding this comment

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

flags [](start = 16, length = 5)

Fix for #4118 (to throw when deleting non-configurable properties in strict mode) #Resolved

<test>
<default>
<files>13.delete_sm.js</files>
<baseline>13.delete_sm.baseline</baseline>
</default>
Copy link
Contributor Author

@IrinaYatsenko IrinaYatsenko Mar 9, 2018

Choose a reason for hiding this comment

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

This existing test passes "as is" so decided not to rewrite it :) #Resolved

@MSLaguana
Copy link
Contributor

MSLaguana commented Mar 9, 2018

Looks like this test has caused a test in ES6TypedArrayExtensions.js to fail. From what I can tell, it seems that it is expecting one kind of error, but is now getting an error on deleting a nonconfiguable property #Resolved

@MSLaguana
Copy link
Contributor

MSLaguana commented Mar 9, 2018

I think the new behavior is more correct, so the test just needs to have its expectation updated I believe. #Resolved

@IrinaYatsenko
Copy link
Contributor Author

Yes, a different error message because. I've updated the test.


In reply to: 371887110 [](ancestors = 371887110)

@IrinaYatsenko
Copy link
Contributor Author

I don't know why the signing tool didn't kick in on my PRs yesterday, but it seems to be doing so now.


In reply to: 371616241 [](ancestors = 371616241)

… slots

Rename ThrowCantDelete and fix regression from chakra-core#4118 (delete non-config props in strict mode)
Copy link
Contributor

@boingoing boingoing left a comment

Choose a reason for hiding this comment

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

👍

@chakrabot chakrabot merged commit f219294 into chakra-core:master Mar 9, 2018
chakrabot pushed a commit that referenced this pull request Mar 9, 2018
… on TypedArray with missing slots

Merge pull request #4790 from irinayat-MS:Throw_copyWithin

Fixes #4356 and a regression from #4118
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.

6 participants