Title: [111162] trunk/Source/_javascript_Core
Revision
111162
Author
[email protected]
Date
2012-03-19 01:12:05 -0700 (Mon, 19 Mar 2012)

Log Message

JSCallbackFunction::toStringCallback/valueOfCallback do not handle 0 return value from convertToType
https://bugs.webkit.org/show_bug.cgi?id=81468 <rdar://problem/11034745>

Reviewed by Oliver Hunt.

The API specifies that convertToType may opt not to handle a conversion:
    "@result The objects's converted value, or NULL if the object was not converted."
In which case, it would propagate first up the JSClass hierarchy, calling its superclass's
conversion functions, and failing that call the JSObject::defaultValue function.

Unfortunately this behaviour was removed in bug#69677/bug#69858, and instead we now rely on
the toStringCallback/valueOfCallback function introduced in bug#69156. Even after a fix in
bug#73368, these will return the result from the first convertToType they find, regardless
of whether this result is null, and if no convertToType method is found in the api class
hierarchy (possible if toStringCallback/valueOfCallback was accessed off the prototype
chain), they will also return a null pointer. This is unsafe.

It would be easy to make the approach based around toStringCallback/valueOfCallback continue
to walk the api class hierarchy, but making the fallback to defaultValue would be problematic
(since defaultValue calls toStringCallback/valueOfCallback, this would infinitely recurse).
Making the fallback work with toString/valueOf methods attached to api objects is probably
not the right thing to do – instead, we should just implement the defaultValue trap for api
objects.

In addition, this bug highlights that fact that JSCallbackFunction::call will allow a hard
null to be returned from C to _javascript_ - this is not okay. Handle with an exception.

* API/JSCallbackFunction.cpp:
(JSC::JSCallbackFunction::call):
    - Should be null checking the return value.
(JSC):
    - Remove toStringCallback/valueOfCallback.
* API/JSCallbackFunction.h:
(JSCallbackFunction):
    - Remove toStringCallback/valueOfCallback.
* API/JSCallbackObject.h:
(JSCallbackObject):
    - Add defaultValue mthods to JSCallbackObject.
* API/JSCallbackObjectFunctions.h:
(JSC::::defaultValue):
    - Add defaultValue mthods to JSCallbackObject.
* API/JSClassRef.cpp:
(OpaqueJSClass::prototype):
    - Remove toStringCallback/valueOfCallback.
* API/tests/testapi.js:
    - Revert this test, now we no longer artificially introduce a toString method onto the api object.

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/API/JSCallbackFunction.cpp (111161 => 111162)


--- trunk/Source/_javascript_Core/API/JSCallbackFunction.cpp	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/API/JSCallbackFunction.cpp	2012-03-19 08:12:05 UTC (rev 111162)
@@ -30,7 +30,6 @@
 #include "APICast.h"
 #include "CodeBlock.h"
 #include "ExceptionHelpers.h"
-#include "JSCallbackObject.h"
 #include "JSFunction.h"
 #include "FunctionPrototype.h"
 #include <runtime/JSGlobalObject.h>
@@ -76,6 +75,10 @@
     if (exception)
         throwError(exec, toJS(exec, exception));
 
+    // result must be a valid JSValue.
+    if (!result)
+        return throwVMTypeError(exec);
+
     return JSValue::encode(toJS(exec, result));
 }
 
@@ -85,35 +88,4 @@
     return CallTypeHost;
 }
 
-JSValueRef JSCallbackFunction::toStringCallback(JSContextRef ctx, JSObjectRef, JSObjectRef thisObject, size_t, const JSValueRef[], JSValueRef* exception)
-{
-    JSObject* object = toJS(thisObject);
-    if (object->inherits(&JSCallbackObject<JSNonFinalObject>::s_info)) {
-        for (JSClassRef jsClass = jsCast<JSCallbackObject<JSNonFinalObject>*>(object)->classRef(); jsClass; jsClass = jsClass->parentClass)
-            if (jsClass->convertToType)
-                return jsClass->convertToType(ctx, thisObject, kJSTypeString, exception);
-    } else if (object->inherits(&JSCallbackObject<JSGlobalObject>::s_info)) {
-        for (JSClassRef jsClass = jsCast<JSCallbackObject<JSGlobalObject>*>(object)->classRef(); jsClass; jsClass = jsClass->parentClass)
-            if (jsClass->convertToType)
-                return jsClass->convertToType(ctx, thisObject, kJSTypeString, exception);
-    }
-    return 0;
-}
-
-JSValueRef JSCallbackFunction::valueOfCallback(JSContextRef ctx, JSObjectRef, JSObjectRef thisObject, size_t, const JSValueRef[], JSValueRef* exception)
-{
-    JSObject* object = toJS(thisObject);
-    if (object->inherits(&JSCallbackObject<JSNonFinalObject>::s_info)) {
-        for (JSClassRef jsClass = jsCast<JSCallbackObject<JSNonFinalObject>*>(object)->classRef(); jsClass; jsClass = jsClass->parentClass)
-            if (jsClass->convertToType)
-                return jsClass->convertToType(ctx, thisObject, kJSTypeNumber, exception);
-    } else if (object->inherits(&JSCallbackObject<JSGlobalObject>::s_info)) {
-        for (JSClassRef jsClass = jsCast<JSCallbackObject<JSGlobalObject>*>(object)->classRef(); jsClass; jsClass = jsClass->parentClass)
-            if (jsClass->convertToType)
-                return jsClass->convertToType(ctx, thisObject, kJSTypeNumber, exception);
-    }
-    return 0;
-}
-
-
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/API/JSCallbackFunction.h (111161 => 111162)


--- trunk/Source/_javascript_Core/API/JSCallbackFunction.h	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/API/JSCallbackFunction.h	2012-03-19 08:12:05 UTC (rev 111162)
@@ -55,9 +55,6 @@
         return Structure::create(globalData, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); 
     }
 
-    static JSValueRef toStringCallback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
-    static JSValueRef valueOfCallback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
-
 private:
     static CallType getCallData(JSCell*, CallData&);
 

Modified: trunk/Source/_javascript_Core/API/JSCallbackObject.h (111161 => 111162)


--- trunk/Source/_javascript_Core/API/JSCallbackObject.h	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/API/JSCallbackObject.h	2012-03-19 08:12:05 UTC (rev 111162)
@@ -175,6 +175,8 @@
 
     static void destroy(JSCell*);
 
+    static JSValue defaultValue(const JSObject*, ExecState*, PreferredPrimitiveType);
+
     static bool getOwnPropertySlot(JSCell*, ExecState*, const Identifier&, PropertySlot&);
     static bool getOwnPropertyDescriptor(JSObject*, ExecState*, const Identifier&, PropertyDescriptor&);
     

Modified: trunk/Source/_javascript_Core/API/JSCallbackObjectFunctions.h (111161 => 111162)


--- trunk/Source/_javascript_Core/API/JSCallbackObjectFunctions.h	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/API/JSCallbackObjectFunctions.h	2012-03-19 08:12:05 UTC (rev 111162)
@@ -184,6 +184,30 @@
 }
 
 template <class Parent>
+JSValue JSCallbackObject<Parent>::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint)
+{
+    const JSCallbackObject* thisObject = jsCast<const JSCallbackObject*>(object);
+    JSContextRef ctx = toRef(exec);
+    JSObjectRef thisRef = toRef(thisObject);
+    ::JSType jsHint = hint == PreferString ? kJSTypeString : kJSTypeNumber;
+
+    for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) {
+        if (JSObjectConvertToTypeCallback convertToType = jsClass->convertToType) {
+            JSValueRef exception = 0;
+            JSValueRef result = convertToType(ctx, thisRef, jsHint, &exception);
+            if (exception) {
+                throwError(exec, toJS(exec, exception));
+                return jsUndefined();
+            }
+            if (result)
+                return toJS(exec, result);
+        }
+    }
+    
+    return Parent::defaultValue(object, exec, hint);
+}
+
+template <class Parent>
 bool JSCallbackObject<Parent>::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
 {
     JSCallbackObject* thisObject = jsCast<JSCallbackObject*>(object);

Modified: trunk/Source/_javascript_Core/API/JSClassRef.cpp (111161 => 111162)


--- trunk/Source/_javascript_Core/API/JSClassRef.cpp	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/API/JSClassRef.cpp	2012-03-19 08:12:05 UTC (rev 111162)
@@ -204,17 +204,6 @@
      *  DerivedClass  |  DerivedClassPrototype
      */
 
-    if (convertToType) {
-        if (!prototypeClass)
-            prototypeClass = OpaqueJSClass::create(&kJSClassDefinitionEmpty).leakRef();
-        if (!prototypeClass->m_staticFunctions)
-            prototypeClass->m_staticFunctions = adoptPtr(new OpaqueJSClassStaticFunctionsTable);
-        const Identifier& toString = exec->propertyNames().toString;
-        const Identifier& valueOf = exec->propertyNames().valueOf;
-        prototypeClass->m_staticFunctions->add(StringImpl::create(toString.characters(), toString.length()), adoptPtr(new StaticFunctionEntry(&JSCallbackFunction::toStringCallback, 0)));
-        prototypeClass->m_staticFunctions->add(StringImpl::create(valueOf.characters(), valueOf.length()), adoptPtr(new StaticFunctionEntry(&JSCallbackFunction::valueOfCallback, 0)));
-    }
-
     if (!prototypeClass)
         return 0;
 

Modified: trunk/Source/_javascript_Core/API/tests/testapi.c (111161 => 111162)


--- trunk/Source/_javascript_Core/API/tests/testapi.c	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/API/tests/testapi.c	2012-03-19 08:12:05 UTC (rev 111162)
@@ -311,6 +311,16 @@
     return JSValueMakeNull(context);
 }
 
+static JSValueRef MyObject_convertToTypeWrapper(JSContextRef context, JSObjectRef object, JSType type, JSValueRef* exception)
+{
+    UNUSED_PARAM(context);
+    UNUSED_PARAM(object);
+    UNUSED_PARAM(type);
+    UNUSED_PARAM(exception);
+    // Forward to default object class
+    return 0;
+}
+
 static bool MyObject_set_nullGetForwardSet(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
 {
     UNUSED_PARAM(ctx);
@@ -355,14 +365,65 @@
     MyObject_convertToType,
 };
 
+JSClassDefinition MyObject_convertToTypeWrapperDefinition = {
+    0,
+    kJSClassAttributeNone,
+    
+    "MyObject",
+    NULL,
+    
+    NULL,
+    NULL,
+    
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    MyObject_convertToTypeWrapper,
+};
+
+JSClassDefinition MyObject_nullWrapperDefinition = {
+    0,
+    kJSClassAttributeNone,
+    
+    "MyObject",
+    NULL,
+    
+    NULL,
+    NULL,
+    
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+};
+
 static JSClassRef MyObject_class(JSContextRef context)
 {
     UNUSED_PARAM(context);
 
     static JSClassRef jsClass;
-    if (!jsClass)
-        jsClass = JSClassCreate(&MyObject_definition);
-    
+    if (!jsClass) {
+        JSClassRef baseClass = JSClassCreate(&MyObject_definition);
+        MyObject_convertToTypeWrapperDefinition.parentClass = baseClass;
+        JSClassRef wrapperClass = JSClassCreate(&MyObject_convertToTypeWrapperDefinition);
+        MyObject_nullWrapperDefinition.parentClass = wrapperClass;
+        jsClass = JSClassCreate(&MyObject_nullWrapperDefinition);
+    }
+
     return jsClass;
 }
 

Modified: trunk/Source/_javascript_Core/API/tests/testapi.js (111161 => 111162)


--- trunk/Source/_javascript_Core/API/tests/testapi.js	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/API/tests/testapi.js	2012-03-19 08:12:05 UTC (rev 111162)
@@ -155,7 +155,7 @@
 shouldBe("MyObject ? 1 : 0", true); // toBoolean
 shouldBe("+MyObject", 1); // toNumber
 shouldBe("(Object.prototype.toString.call(MyObject))", "[object MyObject]"); // Object.prototype.toString
-shouldBe("(MyObject.toString())", "MyObjectAsString"); // toString
+shouldBe("(MyObject.toString())", "[object MyObject]"); // toString
 shouldBe("String(MyObject)", "MyObjectAsString"); // toString
 shouldBe("MyObject - 0", 1); // toNumber
 shouldBe("MyObject.valueOf()", 1); // valueOf

Modified: trunk/Source/_javascript_Core/ChangeLog (111161 => 111162)


--- trunk/Source/_javascript_Core/ChangeLog	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/ChangeLog	2012-03-19 08:12:05 UTC (rev 111162)
@@ -1,3 +1,52 @@
+2012-03-19  Gavin Barraclough  <[email protected]>
+
+        JSCallbackFunction::toStringCallback/valueOfCallback do not handle 0 return value from convertToType
+        https://bugs.webkit.org/show_bug.cgi?id=81468 <rdar://problem/11034745>
+
+        Reviewed by Oliver Hunt.
+
+        The API specifies that convertToType may opt not to handle a conversion:
+            "@result The objects's converted value, or NULL if the object was not converted."
+        In which case, it would propagate first up the JSClass hierarchy, calling its superclass's
+        conversion functions, and failing that call the JSObject::defaultValue function.
+
+        Unfortunately this behaviour was removed in bug#69677/bug#69858, and instead we now rely on
+        the toStringCallback/valueOfCallback function introduced in bug#69156. Even after a fix in
+        bug#73368, these will return the result from the first convertToType they find, regardless
+        of whether this result is null, and if no convertToType method is found in the api class
+        hierarchy (possible if toStringCallback/valueOfCallback was accessed off the prototype
+        chain), they will also return a null pointer. This is unsafe.
+
+        It would be easy to make the approach based around toStringCallback/valueOfCallback continue
+        to walk the api class hierarchy, but making the fallback to defaultValue would be problematic
+        (since defaultValue calls toStringCallback/valueOfCallback, this would infinitely recurse).
+        Making the fallback work with toString/valueOf methods attached to api objects is probably
+        not the right thing to do – instead, we should just implement the defaultValue trap for api
+        objects.
+
+        In addition, this bug highlights that fact that JSCallbackFunction::call will allow a hard
+        null to be returned from C to _javascript_ - this is not okay. Handle with an exception.
+
+        * API/JSCallbackFunction.cpp:
+        (JSC::JSCallbackFunction::call):
+            - Should be null checking the return value.
+        (JSC):
+            - Remove toStringCallback/valueOfCallback.
+        * API/JSCallbackFunction.h:
+        (JSCallbackFunction):
+            - Remove toStringCallback/valueOfCallback.
+        * API/JSCallbackObject.h:
+        (JSCallbackObject):
+            - Add defaultValue mthods to JSCallbackObject.
+        * API/JSCallbackObjectFunctions.h:
+        (JSC::::defaultValue):
+            - Add defaultValue mthods to JSCallbackObject.
+        * API/JSClassRef.cpp:
+        (OpaqueJSClass::prototype):
+            - Remove toStringCallback/valueOfCallback.
+        * API/tests/testapi.js:
+            - Revert this test, now we no longer artificially introduce a toString method onto the api object.
+
 2012-03-18  Raphael Kubo da Costa  <[email protected]>
 
         [EFL] Include ICU_INCLUDE_DIRS when building.

Modified: trunk/Source/_javascript_Core/runtime/Arguments.cpp (111161 => 111162)


--- trunk/Source/_javascript_Core/runtime/Arguments.cpp	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/runtime/Arguments.cpp	2012-03-19 08:12:05 UTC (rev 111162)
@@ -306,6 +306,7 @@
     bool isArrayIndex;
     unsigned i = propertyName.toArrayIndex(isArrayIndex);
     if (isArrayIndex && i < thisObject->d->numArguments) {
+        object->putDirect(exec->globalData(), propertyName, thisObject->argument(i).get(), 0);
         if (!Base::defineOwnProperty(object, exec, propertyName, descriptor, shouldThrow))
             return false;
 
@@ -331,35 +332,16 @@
                     thisObject->d->deletedArguments[i] = true;
             }
         }
-
         return true;
     }
 
     if (propertyName == exec->propertyNames().length && !thisObject->d->overrodeLength) {
+        thisObject->putDirect(exec->globalData(), propertyName, jsNumber(thisObject->d->numArguments), DontEnum);
         thisObject->d->overrodeLength = true;
-        if (!descriptor.isAccessorDescriptor()) {
-            if (!descriptor.value())
-                descriptor.setValue(jsNumber(thisObject->d->numArguments));
-            if (!descriptor.configurablePresent())
-                descriptor.setConfigurable(true);
-        }
-        if (!descriptor.configurablePresent())
-            descriptor.setConfigurable(true);
-    }
-
-    if (propertyName == exec->propertyNames().callee && !thisObject->d->overrodeCallee) {
+    } else if (propertyName == exec->propertyNames().callee && !thisObject->d->overrodeCallee) {
+        thisObject->putDirect(exec->globalData(), propertyName, thisObject->d->callee.get(), DontEnum);
         thisObject->d->overrodeCallee = true;
-        if (!descriptor.isAccessorDescriptor()) {
-            if (!descriptor.value())
-                descriptor.setValue(thisObject->d->callee.get());
-            if (!descriptor.configurablePresent())
-                descriptor.setConfigurable(true);
-        }
-        if (!descriptor.configurablePresent())
-            descriptor.setConfigurable(true);
-    }
-
-    if (propertyName == exec->propertyNames().caller && thisObject->d->isStrictMode)
+    } else if (propertyName == exec->propertyNames().caller && thisObject->d->isStrictMode)
         thisObject->createStrictModeCallerIfNecessary(exec);
 
     return Base::defineOwnProperty(object, exec, propertyName, descriptor, shouldThrow);

Modified: trunk/Source/_javascript_Core/runtime/JSFunction.cpp (111161 => 111162)


--- trunk/Source/_javascript_Core/runtime/JSFunction.cpp	2012-03-19 08:01:24 UTC (rev 111161)
+++ trunk/Source/_javascript_Core/runtime/JSFunction.cpp	2012-03-19 08:12:05 UTC (rev 111162)
@@ -371,11 +371,6 @@
         PropertySlot slot;
         thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, propertyName, slot);
     } else if (propertyName == exec->propertyNames().arguments || propertyName == exec->propertyNames().length || propertyName == exec->propertyNames().caller) {
-        if (!object->isExtensible()) {
-            if (throwException)
-                throwError(exec, createTypeError(exec, "Attempting to define property on object that is not extensible."));
-            return false;
-        }
         if (descriptor.configurablePresent() && descriptor.configurable()) {
             if (throwException)
                 throwError(exec, createTypeError(exec, "Attempting to configurable attribute of unconfigurable property."));
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to