Early error on ANY operator before new.target
authorcaitp@igalia.com <caitp@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Aug 2017 21:34:20 +0000 (21:34 +0000)
committercaitp@igalia.com <caitp@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Aug 2017 21:34:20 +0000 (21:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=157970

Reviewed by Saam Barati.

Instead of throwing if any unary operator precedes new.target, only
throw if the unary operator updates the reference.

The following become legal in JSC:

```
!new.target
~new.target
typeof new.target
delete new.target
void new.target
```

All of which are legal in v8 and SpiderMonkey in strict and sloppy mode

JSTests:

* stress/new-target-syntax-errors.js:
* stress/new-target.js:

Source/JavaScriptCore:

* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseUnaryExpression):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220481 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/default-value-parsing-should-propagate-error.js
JSTests/stress/new-target-syntax-errors.js
JSTests/stress/new-target.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/parser/Parser.cpp

index c11bc85..d697d87 100644 (file)
@@ -1,3 +1,28 @@
+2017-08-09  Caitlin Potter  <caitp@igalia.com>
+
+        Early error on ANY operator before new.target
+        https://bugs.webkit.org/show_bug.cgi?id=157970
+
+        Reviewed by Saam Barati.
+
+        Instead of throwing if any unary operator precedes new.target, only
+        throw if the unary operator updates the reference.
+
+        The following become legal in JSC:
+
+        ```
+        !new.target
+        ~new.target
+        typeof new.target
+        delete new.target
+        void new.target
+        ```
+
+        All of which are legal in v8 and SpiderMonkey in strict and sloppy mode
+
+        * stress/new-target-syntax-errors.js:
+        * stress/new-target.js:
+
 2017-08-09  Ryan Haddad  <ryanhaddad@apple.com>
 
         Skip failing JSC tests stress/regress-169783.js and wasm.yaml/wasm/stress/oom.js.
index 4bb7968..6bc7ad9 100644 (file)
@@ -16,6 +16,6 @@ function testSyntaxError(script, message) {
 testSyntaxError(`
 function f()
 {
-    ({v = (typeof new.target)} = {})
+    ({v = (++new.target)} = {})
 }
 `, `SyntaxError: Unexpected token '='. Expected a ':' following the property name 'v'.`);
index bc1c238..b37dceb 100644 (file)
@@ -88,6 +88,14 @@ for (let operator of prePostFixOperators) {
     shouldNotBeSyntaxError(functionBody);
 }
 
+let otherUnaryOperators = ["!", "~", "+", "-", "typeof ", "void ", "delete "];
+for (let operator of otherUnaryOperators) {
+    function strict(body) { return `"use strict" ${body}`; }
+    let functionBody = `${operator}new.target`;
+    shouldNotBeSyntaxError(functionBody);
+    shouldNotBeSyntaxError(strict(functionBody));
+}
+
 shouldBeSyntaxError(`({foo: new.target} = {foo:20})`);
 
 // Scripts - 15.1.1 Static Semantics: Early Errors
index 9875236..c3dc7ee 100644 (file)
@@ -20,7 +20,7 @@ test(passed, true, "new.target cannot be called in global scope");
 // Test without class syntax
 
 function test(result, expected, message) {
-    if (result !== expected)
+    if (result !== expected && !(expected !== expected && result !== result))
         throw "Error: " + message + ". was: " + result + " wanted: " + expected;
 }
 
@@ -125,3 +125,77 @@ var newTargetInFunctionInEval = function () {
 
 };
 newTargetInFunctionInEval();
+
+function testUnaryOps() {
+  var result;
+  function call(f) { f(); return result; }
+  function construct(f) { new f(); return result; }
+  
+  function unaryExclamation() { result = !new.target; }
+  test(construct(unaryExclamation), false, "`!new.target` should be false when new.target is not undefined");
+  test(call(unaryExclamation), true, "`!new.target` should be true when new.target is undefined");
+
+  function unaryBitwiseNot() { result = ~new.target; }
+  test(construct(unaryBitwiseNot), -1, "`~new.target` should be -1");
+  test(call(unaryBitwiseNot), -1, "`~new.target` should be -1");
+
+  function unaryTypeof() { result = typeof new.target; }
+  test(construct(unaryTypeof), "function", "`typeof new.target` should be 'function' when new.target is not undefined");
+  test(call(unaryTypeof), "undefined", "`typeof new.target` should be 'undefined' when new.target is undefined");
+
+  function unaryVoid() { result = void new.target; }
+  test(construct(unaryVoid), undefined, "`void new.target` should be undefined");
+  test(call(unaryVoid), undefined, "`void new.target` should be undefined");
+
+  function unaryAbs() { result = +new.target; }
+  test(construct(unaryAbs), NaN, "+new.target should be NaN");
+  test(call(unaryAbs), NaN, "+new.target should be NaN");
+
+  function unaryNeg() { result = -new.target; }
+  test(construct(unaryNeg), NaN, "-new.target should be NaN");
+  test(call(unaryNeg), NaN, "-new.target should be NaN");
+
+  // Multiple variations of delete are tested for completeness:
+  function unaryDelete() { result = delete new.target; }
+  function strictUnaryDelete() { "use strict"; result = delete new.target; }
+
+  // If Type(ref) is not Reference, return true. (per #sec-delete-operator-runtime-semantics-evaluation)
+  test(construct(unaryDelete), true, "`delete new.target` should be true");
+  test(call(unaryDelete), true, "`delete new.target` should be true");
+  test(construct(strictUnaryDelete), true, "`delete new.target` should be true");
+  test(call(strictUnaryDelete), true, "`delete new.target` should be true");
+
+  var unaryDeleteProp = function unaryDeleteProp() { result = delete new.target.prop; }
+  var strictUnaryDeleteProp = function strictUnaryDeleteProp() { "use strict"; result = delete new.target.prop; }
+  unaryDeleteProp.prop = 1;
+  test(construct(unaryDeleteProp), true, "`delete new.target.prop` should be true when new.target is not undefined and prop is a configurable property");
+  strictUnaryDeleteProp.prop = 1;
+  test(construct(strictUnaryDeleteProp), true, "`delete new.target.prop` should be true when new.target is not undefined and prop is a configurable property");
+  
+  unaryDeleteProp = function unaryDeleteProp() { result = delete new.target.prop; }
+  Object.defineProperty(unaryDeleteProp, "prop", { value: false, configurable: false });
+  test(construct(unaryDeleteProp), false, "`delete new.target.prop` should be false when new.target is not undefined and prop is a non-configurable property");
+
+  strictUnaryDeleteProp = function strictUnaryDeleteProp() { "use strict"; result = delete new.target.prop; }
+  // If deleteStatus is false and IsStrictReference(ref) is true, throw a TypeError exception.
+  Object.defineProperty(strictUnaryDeleteProp, "prop", { value: false, configurable: false });
+  try {
+    var passed = false;
+    construct(strictUnaryDeleteProp);
+  } catch (e) {
+    passed = e instanceof TypeError && e.message.indexOf("delete") >= 0;
+  }
+  test(passed, true, "`delete new.target.prop` should throw a TypeError in strict code when prop is a non-configurable property");
+
+  unaryDeleteProp = function unaryDeleteProp() { result = delete new.target.prop; }
+  unaryDeleteProp.prop = 1;
+  try {
+    var passed = false;
+    call(unaryDeleteProp);
+  } catch (e) {
+    passed = e instanceof TypeError && e.message.indexOf("undefined") >= 0;
+  }
+  test(passed, true, "`delete new.target.prop` should throw a TypeError when new.target is undefined");
+}
+testUnaryOps();
+
index ce9625d..404f114 100644 (file)
@@ -1,3 +1,28 @@
+2017-08-09  Caitlin Potter  <caitp@igalia.com>
+
+        Early error on ANY operator before new.target
+        https://bugs.webkit.org/show_bug.cgi?id=157970
+
+        Reviewed by Saam Barati.
+
+        Instead of throwing if any unary operator precedes new.target, only
+        throw if the unary operator updates the reference.
+
+        The following become legal in JSC:
+
+        ```
+        !new.target
+        ~new.target
+        typeof new.target
+        delete new.target
+        void new.target
+        ```
+
+        All of which are legal in v8 and SpiderMonkey in strict and sloppy mode
+
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseUnaryExpression):
+
 2017-08-09  Sam Weinig  <sam@webkit.org>
 
         WTF::Function does not allow for reference / non-default constructible return types
index 74d1e45..e748c54 100644 (file)
@@ -4883,7 +4883,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseUnaryExpress
             failWithMessage("Cannot parse subexpression of ", operatorString(true, lastOperator), "operator");
         failWithMessage("Cannot parse member expression");
     }
-    if (UNLIKELY(lastOperator && context.isNewTarget(expr)))
+    if (UNLIKELY(isUpdateOp(static_cast<JSTokenType>(lastOperator)) && context.isNewTarget(expr)))
         internalFailWithMessage(false, "new.target can't come after a prefix operator");
     bool isEvalOrArguments = false;
     if (strictMode() && !m_syntaxAlreadyValidated) {