Title: [109177] trunk
Revision
109177
Author
[email protected]
Date
2012-02-28 17:39:15 -0800 (Tue, 28 Feb 2012)

Log Message

[[Get]]/[[Put]] for primitives should not wrap on strict accessor call
https://bugs.webkit.org/show_bug.cgi?id=79588

Reviewed by Oliver Hunt.

In the case of [[Get]], this is a pretty trivial bug - just don't wrap
primitives at the point you call a getter.

For setters, this is a little more involved, since we have already wrapped
the value up in a synthesized object. Stop doing so. There is also a further
subtely, that in strict mode all attempts to create a new data property on
the object should throw.

Source/_javascript_Core: 

* runtime/JSCell.cpp:
(JSC::JSCell::put):
    - [[Put]] to a string primitive should use JSValue::putToPrimitive.
* runtime/JSObject.cpp:
(JSC::JSObject::put):
    - Remove static function called in one place.
* runtime/JSObject.h:
(JSC::JSValue::put):
    - [[Put]] to a non-cell JSValue should use JSValue::putToPrimitive.
* runtime/JSValue.cpp:
(JSC::JSValue::synthesizePrototype):
    - Add support for synthesizing the prototype of strings.
(JSC::JSValue::putToPrimitive):
    - Added, implements [[Put]] for primitive bases, per 8.7.2.
* runtime/JSValue.h:
(JSValue):
    - Add declaration for JSValue::putToPrimitive.
* runtime/PropertySlot.cpp:
(JSC::PropertySlot::functionGetter):
    - Don't call ToObject on primitive this values.

LayoutTests: 

* fast/js/mozilla/strict/15.5.5.1-expected.txt:
* fast/js/primitive-property-access-edge-cases-expected.txt:
* fast/js/read-modify-eval-expected.txt:
* fast/js/script-tests/primitive-property-access-edge-cases.js:
* fast/js/script-tests/read-modify-eval.js:
    - Added new test cases & updated test results.

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (109176 => 109177)


--- trunk/LayoutTests/ChangeLog	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/LayoutTests/ChangeLog	2012-02-29 01:39:15 UTC (rev 109177)
@@ -1,3 +1,25 @@
+2012-02-28  Gavin Barraclough  <[email protected]>
+
+        [[Get]]/[[Put]] for primitives should not wrap on strict accessor call
+        https://bugs.webkit.org/show_bug.cgi?id=79588
+
+        Reviewed by Oliver Hunt.
+
+        In the case of [[Get]], this is a pretty trivial bug - just don't wrap
+        primitives at the point you call a getter.
+
+        For setters, this is a little more involved, since we have already wrapped
+        the value up in a synthesized object. Stop doing so. There is also a further
+        subtely, that in strict mode all attempts to create a new data property on
+        the object should throw.
+
+        * fast/js/mozilla/strict/15.5.5.1-expected.txt:
+        * fast/js/primitive-property-access-edge-cases-expected.txt:
+        * fast/js/read-modify-eval-expected.txt:
+        * fast/js/script-tests/primitive-property-access-edge-cases.js:
+        * fast/js/script-tests/read-modify-eval.js:
+            - Added new test cases & updated test results.
+
 2012-02-28  Daniel Cheng  <[email protected]>
 
         Clipboard::getData should return an empty string instead of undefined

Modified: trunk/LayoutTests/fast/js/mozilla/strict/15.5.5.1-expected.txt (109176 => 109177)


--- trunk/LayoutTests/fast/js/mozilla/strict/15.5.5.1-expected.txt	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/LayoutTests/fast/js/mozilla/strict/15.5.5.1-expected.txt	2012-02-29 01:39:15 UTC (rev 109177)
@@ -4,7 +4,7 @@
 PASS 'use strict'; var s = str(); delete s.length threw exception of type TypeError.
 PASS var s = str(); delete s.length is false
 PASS true === true
-FAIL 'use strict'; "foo".length = 1 should throw an instance of TypeError
+PASS 'use strict'; "foo".length = 1 threw exception of type TypeError.
 PASS "foo".length = 1 is 1
 PASS true === true
 PASS 'use strict'; delete "foo".length threw exception of type TypeError.

Modified: trunk/LayoutTests/fast/js/primitive-property-access-edge-cases-expected.txt (109176 => 109177)


--- trunk/LayoutTests/fast/js/primitive-property-access-edge-cases-expected.txt	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/LayoutTests/fast/js/primitive-property-access-edge-cases-expected.txt	2012-02-29 01:39:15 UTC (rev 109177)
@@ -3,6 +3,30 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
+PASS checkGet(1, Number) is true
+PASS checkGet('hello', String) is true
+PASS checkGet(true, Boolean) is true
+PASS checkSet(1, Number) is true
+PASS checkSet('hello', String) is true
+PASS checkSet(true, Boolean) is true
+PASS checkGetStrict(1, Number) is true
+PASS checkGetStrict('hello', String) is true
+PASS checkGetStrict(true, Boolean) is true
+PASS checkSetStrict(1, Number) is true
+PASS checkSetStrict('hello', String) is true
+PASS checkSetStrict(true, Boolean) is true
+PASS checkRead(1, Number) is true
+PASS checkRead('hello', String) is true
+PASS checkRead(true, Boolean) is true
+PASS checkWrite(1, Number) is true
+PASS checkWrite('hello', String) is true
+PASS checkWrite(true, Boolean) is true
+PASS checkReadStrict(1, Number) is true
+PASS checkReadStrict('hello', String) is true
+PASS checkReadStrict(true, Boolean) is true
+PASS checkWriteStrict(1, Number) threw exception TypeError: Attempted to assign to readonly property..
+PASS checkWriteStrict('hello', String) threw exception TypeError: Attempted to assign to readonly property..
+PASS checkWriteStrict(true, Boolean) threw exception TypeError: Attempted to assign to readonly property..
 PASS didNotCrash is true
 PASS successfullyParsed is true
 

Modified: trunk/LayoutTests/fast/js/read-modify-eval-expected.txt (109176 => 109177)


--- trunk/LayoutTests/fast/js/read-modify-eval-expected.txt	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/LayoutTests/fast/js/read-modify-eval-expected.txt	2012-02-29 01:39:15 UTC (rev 109177)
@@ -19,7 +19,7 @@
 PASS postIncTest(); is true
 PASS postDecTest(); is true
 PASS primitiveThisTest.call(1); is true
-PASS strictThisTest.call(1); is true
+PASS strictThisTest.call(1); threw exception TypeError: Attempted to assign to readonly property..
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/fast/js/script-tests/primitive-property-access-edge-cases.js (109176 => 109177)


--- trunk/LayoutTests/fast/js/script-tests/primitive-property-access-edge-cases.js	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/LayoutTests/fast/js/script-tests/primitive-property-access-edge-cases.js	2012-02-29 01:39:15 UTC (rev 109177)
@@ -37,4 +37,93 @@
         f(.5);
 })();
 
+
+var checkOkay;
+
+function checkGet(x, constructor)
+{
+    checkOkay = false;
+    Object.defineProperty(constructor.prototype, "foo", { get: function() { checkOkay = typeof this === 'object'; }, configurable: true });
+    x.foo;
+    delete constructor.prototype.foo;
+    return checkOkay;
+}
+
+function checkSet(x, constructor)
+{
+    checkOkay = false;
+    Object.defineProperty(constructor.prototype, "foo", { set: function() { checkOkay = typeof this === 'object'; }, configurable: true });
+    x.foo = null;
+    delete constructor.prototype.foo;
+    return checkOkay;
+}
+
+function checkGetStrict(x, constructor)
+{
+    checkOkay = false;
+    Object.defineProperty(constructor.prototype, "foo", { get: function() { "use strict"; checkOkay = typeof this !== 'object'; }, configurable: true });
+    x.foo;
+    delete constructor.prototype.foo;
+    return checkOkay;
+}
+
+function checkSetStrict(x, constructor)
+{
+    checkOkay = false;
+    Object.defineProperty(constructor.prototype, "foo", { set: function() { "use strict"; checkOkay = typeof this !== 'object'; }, configurable: true });
+    x.foo = null;
+    delete constructor.prototype.foo;
+    return checkOkay;
+}
+
+shouldBeTrue("checkGet(1, Number)");
+shouldBeTrue("checkGet('hello', String)");
+shouldBeTrue("checkGet(true, Boolean)");
+shouldBeTrue("checkSet(1, Number)");
+shouldBeTrue("checkSet('hello', String)");
+shouldBeTrue("checkSet(true, Boolean)");
+shouldBeTrue("checkGetStrict(1, Number)");
+shouldBeTrue("checkGetStrict('hello', String)");
+shouldBeTrue("checkGetStrict(true, Boolean)");
+shouldBeTrue("checkSetStrict(1, Number)");
+shouldBeTrue("checkSetStrict('hello', String)");
+shouldBeTrue("checkSetStrict(true, Boolean)");
+
+function checkRead(x, constructor)
+{
+    return x.foo === undefined;
+}
+
+function checkWrite(x, constructor)
+{
+    x.foo = null;
+    return x.foo === undefined;
+}
+
+function checkReadStrict(x, constructor)
+{
+    "use strict";
+    return x.foo === undefined;
+}
+
+function checkWriteStrict(x, constructor)
+{
+    "use strict";
+    x.foo = null;
+    return x.foo === undefined;
+}
+
+shouldBeTrue("checkRead(1, Number)");
+shouldBeTrue("checkRead('hello', String)");
+shouldBeTrue("checkRead(true, Boolean)");
+shouldBeTrue("checkWrite(1, Number)");
+shouldBeTrue("checkWrite('hello', String)");
+shouldBeTrue("checkWrite(true, Boolean)");
+shouldBeTrue("checkReadStrict(1, Number)");
+shouldBeTrue("checkReadStrict('hello', String)");
+shouldBeTrue("checkReadStrict(true, Boolean)");
+shouldThrow("checkWriteStrict(1, Number)");
+shouldThrow("checkWriteStrict('hello', String)");
+shouldThrow("checkWriteStrict(true, Boolean)");
+
 shouldBeTrue("didNotCrash");

Modified: trunk/LayoutTests/fast/js/script-tests/read-modify-eval.js (109176 => 109177)


--- trunk/LayoutTests/fast/js/script-tests/read-modify-eval.js	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/LayoutTests/fast/js/script-tests/read-modify-eval.js	2012-02-29 01:39:15 UTC (rev 109177)
@@ -119,7 +119,7 @@
 {
     // In a strict mode function primitive this values are not converted, so
     // the property access in the first eval is writing a value to a temporary
-    // object, and should not be observed by the second eval.
+    // object. This throws, per section 8.7.2.
     "use strict";
     eval('this.value = "Seekrit message";');
     return eval('this.value') === undefined;
@@ -143,4 +143,4 @@
 shouldBeTrue('postDecTest();');
 
 shouldBeTrue('primitiveThisTest.call(1);');
-shouldBeTrue('strictThisTest.call(1);');
+shouldThrow('strictThisTest.call(1);');

Modified: trunk/Source/_javascript_Core/ChangeLog (109176 => 109177)


--- trunk/Source/_javascript_Core/ChangeLog	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/Source/_javascript_Core/ChangeLog	2012-02-29 01:39:15 UTC (rev 109177)
@@ -1,3 +1,39 @@
+2012-02-28  Gavin Barraclough  <[email protected]>
+
+        [[Get]]/[[Put]] for primitives should not wrap on strict accessor call
+        https://bugs.webkit.org/show_bug.cgi?id=79588
+
+        Reviewed by Oliver Hunt.
+
+        In the case of [[Get]], this is a pretty trivial bug - just don't wrap
+        primitives at the point you call a getter.
+
+        For setters, this is a little more involved, since we have already wrapped
+        the value up in a synthesized object. Stop doing so. There is also a further
+        subtely, that in strict mode all attempts to create a new data property on
+        the object should throw.
+
+        * runtime/JSCell.cpp:
+        (JSC::JSCell::put):
+            - [[Put]] to a string primitive should use JSValue::putToPrimitive.
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::put):
+            - Remove static function called in one place.
+        * runtime/JSObject.h:
+        (JSC::JSValue::put):
+            - [[Put]] to a non-cell JSValue should use JSValue::putToPrimitive.
+        * runtime/JSValue.cpp:
+        (JSC::JSValue::synthesizePrototype):
+            - Add support for synthesizing the prototype of strings.
+        (JSC::JSValue::putToPrimitive):
+            - Added, implements [[Put]] for primitive bases, per 8.7.2.
+        * runtime/JSValue.h:
+        (JSValue):
+            - Add declaration for JSValue::putToPrimitive.
+        * runtime/PropertySlot.cpp:
+        (JSC::PropertySlot::functionGetter):
+            - Don't call ToObject on primitive this values.
+
 2012-02-28  Mark Hahnenberg  <[email protected]>
 
         Re-enable parallel GC on Mac

Modified: trunk/Source/_javascript_Core/runtime/JSCell.cpp (109176 => 109177)


--- trunk/Source/_javascript_Core/runtime/JSCell.cpp	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/Source/_javascript_Core/runtime/JSCell.cpp	2012-02-29 01:39:15 UTC (rev 109177)
@@ -97,6 +97,10 @@
 
 void JSCell::put(JSCell* cell, ExecState* exec, const Identifier& identifier, JSValue value, PutPropertySlot& slot)
 {
+    if (cell->isString()) {
+        JSValue(cell).putToPrimitive(exec, identifier, value, slot);
+        return;
+    }
     JSObject* thisObject = cell->toObject(exec, exec->lexicalGlobalObject());
     thisObject->methodTable()->put(thisObject, exec, identifier, value, slot);
 }

Modified: trunk/Source/_javascript_Core/runtime/JSObject.cpp (109176 => 109177)


--- trunk/Source/_javascript_Core/runtime/JSObject.cpp	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/Source/_javascript_Core/runtime/JSObject.cpp	2012-02-29 01:39:15 UTC (rev 109177)
@@ -119,11 +119,6 @@
     return thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, propertyName), slot);
 }
 
-static void throwSetterError(ExecState* exec)
-{
-    throwError(exec, createTypeError(exec, "setting a property that has only a getter"));
-}
-
 // ECMA 8.6.2.2
 void JSObject::put(JSCell* cell, ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
 {
@@ -161,7 +156,7 @@
                 JSObject* setterFunc = asGetterSetter(gs)->setter();        
                 if (!setterFunc) {
                     if (slot.isStrictMode())
-                        throwSetterError(exec);
+                        throwError(exec, createTypeError(exec, "setting a property that has only a getter"));
                     return;
                 }
                 

Modified: trunk/Source/_javascript_Core/runtime/JSObject.h (109176 => 109177)


--- trunk/Source/_javascript_Core/runtime/JSObject.h	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/Source/_javascript_Core/runtime/JSObject.h	2012-02-29 01:39:15 UTC (rev 109177)
@@ -834,8 +834,7 @@
 inline void JSValue::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
 {
     if (UNLIKELY(!isCell())) {
-        JSObject* thisObject = synthesizeObject(exec);
-        thisObject->methodTable()->put(thisObject, exec, propertyName, value, slot);
+        putToPrimitive(exec, propertyName, value, slot);
         return;
     }
     asCell()->methodTable()->put(asCell(), exec, propertyName, value, slot);

Modified: trunk/Source/_javascript_Core/runtime/JSValue.cpp (109176 => 109177)


--- trunk/Source/_javascript_Core/runtime/JSValue.cpp	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/Source/_javascript_Core/runtime/JSValue.cpp	2012-02-29 01:39:15 UTC (rev 109177)
@@ -27,6 +27,7 @@
 #include "BooleanPrototype.h"
 #include "Error.h"
 #include "ExceptionHelpers.h"
+#include "GetterSetter.h"
 #include "JSGlobalObject.h"
 #include "JSFunction.h"
 #include "JSNotAnObject.h"
@@ -105,7 +106,11 @@
 
 JSObject* JSValue::synthesizePrototype(ExecState* exec) const
 {
-    ASSERT(!isCell());
+    if (isCell()) {
+        ASSERT(isString());
+        return exec->lexicalGlobalObject()->stringPrototype();
+    }
+
     if (isNumber())
         return exec->lexicalGlobalObject()->numberPrototype();
     if (isBoolean())
@@ -116,6 +121,70 @@
     return JSNotAnObject::create(exec);
 }
 
+// ECMA 8.7.2
+void JSValue::putToPrimitive(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
+{
+    JSGlobalData& globalData = exec->globalData();
+
+    // Check if there are any setters or getters in the prototype chain
+    JSObject* obj = synthesizePrototype(exec);
+    JSValue prototype;
+    if (propertyName != exec->propertyNames().underscoreProto) {
+        for (; !obj->structure()->hasReadOnlyOrGetterSetterPropertiesExcludingProto(); obj = asObject(prototype)) {
+            prototype = obj->prototype();
+            if (prototype.isNull()) {
+                if (slot.isStrictMode())
+                    throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
+                return;
+            }
+        }
+    }
+
+    for (; ; obj = asObject(prototype)) {
+        unsigned attributes;
+        JSCell* specificValue;
+        size_t offset = obj->structure()->get(globalData, propertyName, attributes, specificValue);
+        if (offset != WTF::notFound) {
+            if (attributes & ReadOnly) {
+                if (slot.isStrictMode())
+                    throwError(exec, createTypeError(exec, StrictModeReadonlyPropertyWriteError));
+                return;
+            }
+
+            JSValue gs = obj->getDirectOffset(offset);
+            if (gs.isGetterSetter()) {
+                JSObject* setterFunc = asGetterSetter(gs)->setter();        
+                if (!setterFunc) {
+                    if (slot.isStrictMode())
+                        throwError(exec, createTypeError(exec, "setting a property that has only a getter"));
+                    return;
+                }
+                
+                CallData callData;
+                CallType callType = setterFunc->methodTable()->getCallData(setterFunc, callData);
+                MarkedArgumentBuffer args;
+                args.append(value);
+
+                // If this is WebCore's global object then we need to substitute the shell.
+                call(exec, setterFunc, callType, callData, *this, args);
+                return;
+            }
+
+            // If there's an existing property on the object or one of its 
+            // prototypes it should be replaced, so break here.
+            break;
+        }
+
+        prototype = obj->prototype();
+        if (prototype.isNull())
+            break;
+    }
+    
+    if (slot.isStrictMode())
+        throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
+    return;
+}
+
 char* JSValue::description()
 {
     static const size_t size = 128;

Modified: trunk/Source/_javascript_Core/runtime/JSValue.h (109176 => 109177)


--- trunk/Source/_javascript_Core/runtime/JSValue.h	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/Source/_javascript_Core/runtime/JSValue.h	2012-02-29 01:39:15 UTC (rev 109177)
@@ -221,6 +221,7 @@
         JSValue get(ExecState*, unsigned propertyName) const;
         JSValue get(ExecState*, unsigned propertyName, PropertySlot&) const;
         void put(ExecState*, const Identifier& propertyName, JSValue, PutPropertySlot&);
+        void putToPrimitive(ExecState*, const Identifier& propertyName, JSValue, PutPropertySlot&);
         void put(ExecState*, unsigned propertyName, JSValue);
 
         JSObject* toThisObject(ExecState*) const;

Modified: trunk/Source/_javascript_Core/runtime/PropertySlot.cpp (109176 => 109177)


--- trunk/Source/_javascript_Core/runtime/PropertySlot.cpp	2012-02-29 01:31:55 UTC (rev 109176)
+++ trunk/Source/_javascript_Core/runtime/PropertySlot.cpp	2012-02-29 01:39:15 UTC (rev 109177)
@@ -34,11 +34,7 @@
 
     CallData callData;
     CallType callType = m_data.getterFunc->methodTable()->getCallData(m_data.getterFunc, callData);
-    
-    // Only objects can have accessor properties.
-    // If the base is WebCore's global object then we need to substitute the shell.
-    ASSERT(m_slotBase.isObject());
-    return call(exec, m_data.getterFunc, callType, callData, m_thisValue.toThisObject(exec), exec->emptyList());
+    return call(exec, m_data.getterFunc, callType, callData, m_thisValue.isObject() ? m_thisValue.toThisObject(exec) : m_thisValue, exec->emptyList());
 }
 
 } // namespace JSC
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to