X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/9dae56ea45a0f5f8136a5c93d6f3a7f99399ca73..14957cd040308e3eeec43d26bae5d76da13fcd85:/API/tests/testapi.c?ds=sidebyside diff --git a/API/tests/testapi.c b/API/tests/testapi.c index 48c8583..e82d41d 100644 --- a/API/tests/testapi.c +++ b/API/tests/testapi.c @@ -25,10 +25,17 @@ #include "JavaScriptCore.h" #include "JSBasePrivate.h" +#include "JSContextRefPrivate.h" +#include "JSObjectRefPrivate.h" #include +#define ASSERT_DISABLED 0 #include #include +#if OS(WINDOWS) +#include +#endif + #if COMPILER(MSVC) #include @@ -40,12 +47,14 @@ static double nan(const char*) #endif -static JSGlobalContextRef context = 0; - +static JSGlobalContextRef context; +static int failed; static void assertEqualsAsBoolean(JSValueRef value, bool expectedValue) { - if (JSValueToBoolean(context, value) != expectedValue) + if (JSValueToBoolean(context, value) != expectedValue) { fprintf(stderr, "assertEqualsAsBoolean failed: %p, %d\n", value, expectedValue); + failed = 1; + } } static void assertEqualsAsNumber(JSValueRef value, double expectedValue) @@ -55,8 +64,10 @@ static void assertEqualsAsNumber(JSValueRef value, double expectedValue) // FIXME - On i386 the isnan(double) macro tries to map to the isnan(float) function, // causing a build break with -Wshorten-64-to-32 enabled. The issue is known by the appropriate team. // After that's resolved, we can remove these casts - if (number != expectedValue && !(isnan((float)number) && isnan((float)expectedValue))) + if (number != expectedValue && !(isnan((float)number) && isnan((float)expectedValue))) { fprintf(stderr, "assertEqualsAsNumber failed: %p, %lf\n", value, expectedValue); + failed = 1; + } } static void assertEqualsAsUTF8String(JSValueRef value, const char* expectedValue) @@ -68,12 +79,17 @@ static void assertEqualsAsUTF8String(JSValueRef value, const char* expectedValue JSStringGetUTF8CString(valueAsString, jsBuffer, jsSize); unsigned i; - for (i = 0; jsBuffer[i]; i++) - if (jsBuffer[i] != expectedValue[i]) + for (i = 0; jsBuffer[i]; i++) { + if (jsBuffer[i] != expectedValue[i]) { fprintf(stderr, "assertEqualsAsUTF8String failed at character %d: %c(%d) != %c(%d)\n", i, jsBuffer[i], jsBuffer[i], expectedValue[i], expectedValue[i]); - - if (jsSize < strlen(jsBuffer) + 1) + failed = 1; + } + } + + if (jsSize < strlen(jsBuffer) + 1) { fprintf(stderr, "assertEqualsAsUTF8String failed: jsSize was too small\n"); + failed = 1; + } free(jsBuffer); JSStringRelease(valueAsString); @@ -94,16 +110,30 @@ static void assertEqualsAsCharactersPtr(JSValueRef value, const char* expectedVa CFStringGetCharacters(expectedValueAsCFString, CFRangeMake(0, cfLength), cfBuffer); CFRelease(expectedValueAsCFString); - if (memcmp(jsBuffer, cfBuffer, cfLength * sizeof(UniChar)) != 0) + if (memcmp(jsBuffer, cfBuffer, cfLength * sizeof(UniChar)) != 0) { fprintf(stderr, "assertEqualsAsCharactersPtr failed: jsBuffer != cfBuffer\n"); + failed = 1; + } - if (jsLength != (size_t)cfLength) + if (jsLength != (size_t)cfLength) { fprintf(stderr, "assertEqualsAsCharactersPtr failed: jsLength(%ld) != cfLength(%ld)\n", jsLength, cfLength); - + failed = 1; + } + free(cfBuffer); JSStringRelease(valueAsString); } +static bool timeZoneIsPST() +{ + char timeZoneName[70]; + struct tm gtm; + memset(>m, 0, sizeof(gtm)); + strftime(timeZoneName, sizeof(timeZoneName), "%Z", >m); + + return 0 == strcmp("PST", timeZoneName); +} + static JSValueRef jsGlobalValue; // non-stack value for testing JSValueProtect() /* MyObject pseudo-class */ @@ -115,6 +145,7 @@ static bool MyObject_hasProperty(JSContextRef context, JSObjectRef object, JSStr if (JSStringIsEqualToUTF8CString(propertyName, "alwaysOne") || JSStringIsEqualToUTF8CString(propertyName, "cantFind") + || JSStringIsEqualToUTF8CString(propertyName, "throwOnGet") || JSStringIsEqualToUTF8CString(propertyName, "myPropertyName") || JSStringIsEqualToUTF8CString(propertyName, "hasPropertyLie") || JSStringIsEqualToUTF8CString(propertyName, "0")) { @@ -141,12 +172,20 @@ static JSValueRef MyObject_getProperty(JSContextRef context, JSObjectRef object, return JSValueMakeUndefined(context); } + if (JSStringIsEqualToUTF8CString(propertyName, "hasPropertyLie")) { + return 0; + } + + if (JSStringIsEqualToUTF8CString(propertyName, "throwOnGet")) { + return JSEvaluateScript(context, JSStringCreateWithUTF8CString("throw 'an exception'"), object, JSStringCreateWithUTF8CString("test script"), 1, exception); + } + if (JSStringIsEqualToUTF8CString(propertyName, "0")) { *exception = JSValueMakeNumber(context, 1); return JSValueMakeNumber(context, 1); } - return NULL; + return JSValueMakeNull(context); } static bool MyObject_setProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) @@ -159,6 +198,10 @@ static bool MyObject_setProperty(JSContextRef context, JSObjectRef object, JSStr if (JSStringIsEqualToUTF8CString(propertyName, "cantSet")) return true; // pretend we set the property in order to swallow it + if (JSStringIsEqualToUTF8CString(propertyName, "throwOnSet")) { + JSEvaluateScript(context, JSStringCreateWithUTF8CString("throw 'an exception'"), object, JSStringCreateWithUTF8CString("test script"), 1, exception); + } + return false; } @@ -171,7 +214,7 @@ static bool MyObject_deleteProperty(JSContextRef context, JSObjectRef object, JS return true; if (JSStringIsEqualToUTF8CString(propertyName, "throwOnDelete")) { - *exception = JSValueMakeNumber(context, 2); + JSEvaluateScript(context, JSStringCreateWithUTF8CString("throw 'an exception'"), object, JSStringCreateWithUTF8CString("test script"), 1, exception); return false; } @@ -201,6 +244,11 @@ static JSValueRef MyObject_callAsFunction(JSContextRef context, JSObjectRef obje UNUSED_PARAM(thisObject); UNUSED_PARAM(exception); + if (argumentCount > 0 && JSValueIsString(context, arguments[0]) && JSStringIsEqualToUTF8CString(JSValueToStringCopy(context, arguments[0], 0), "throwOnCall")) { + JSEvaluateScript(context, JSStringCreateWithUTF8CString("throw 'an exception'"), object, JSStringCreateWithUTF8CString("test script"), 1, exception); + return JSValueMakeUndefined(context); + } + if (argumentCount > 0 && JSValueIsStrictEqual(context, arguments[0], JSValueMakeNumber(context, 0))) return JSValueMakeNumber(context, 1); @@ -212,6 +260,11 @@ static JSObjectRef MyObject_callAsConstructor(JSContextRef context, JSObjectRef UNUSED_PARAM(context); UNUSED_PARAM(object); + if (argumentCount > 0 && JSValueIsString(context, arguments[0]) && JSStringIsEqualToUTF8CString(JSValueToStringCopy(context, arguments[0], 0), "throwOnConstruct")) { + JSEvaluateScript(context, JSStringCreateWithUTF8CString("throw 'an exception'"), object, JSStringCreateWithUTF8CString("test script"), 1, exception); + return object; + } + if (argumentCount > 0 && JSValueIsStrictEqual(context, arguments[0], JSValueMakeNumber(context, 0))) return JSValueToObject(context, JSValueMakeNumber(context, 1), exception); @@ -223,6 +276,11 @@ static bool MyObject_hasInstance(JSContextRef context, JSObjectRef constructor, UNUSED_PARAM(context); UNUSED_PARAM(constructor); + if (JSValueIsString(context, possibleValue) && JSStringIsEqualToUTF8CString(JSValueToStringCopy(context, possibleValue, 0), "throwOnHasInstance")) { + JSEvaluateScript(context, JSStringCreateWithUTF8CString("throw 'an exception'"), constructor, JSStringCreateWithUTF8CString("test script"), 1, exception); + return false; + } + JSStringRef numberString = JSStringCreateWithUTF8CString("Number"); JSObjectRef numberConstructor = JSValueToObject(context, JSObjectGetProperty(context, JSContextGetGlobalObject(context), numberString, exception), exception); JSStringRelease(numberString); @@ -250,11 +308,22 @@ static JSValueRef MyObject_convertToType(JSContextRef context, JSObjectRef objec } // string conversion -- forward to default object class - return NULL; + return JSValueMakeNull(context); +} + +static bool MyObject_set_nullGetForwardSet(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) +{ + UNUSED_PARAM(ctx); + UNUSED_PARAM(object); + UNUSED_PARAM(propertyName); + UNUSED_PARAM(value); + UNUSED_PARAM(exception); + return false; // Forward to parent class. } static JSStaticValue evilStaticValues[] = { { "nullGetSet", 0, 0, kJSPropertyAttributeNone }, + { "nullGetForwardSet", 0, MyObject_set_nullGetForwardSet, kJSPropertyAttributeNone }, { 0, 0, 0, 0 } }; @@ -297,6 +366,227 @@ static JSClassRef MyObject_class(JSContextRef context) return jsClass; } +static JSValueRef PropertyCatchalls_getProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) +{ + UNUSED_PARAM(context); + UNUSED_PARAM(object); + UNUSED_PARAM(propertyName); + UNUSED_PARAM(exception); + + if (JSStringIsEqualToUTF8CString(propertyName, "x")) { + static size_t count; + if (count++ < 5) + return NULL; + + // Swallow all .x gets after 5, returning null. + return JSValueMakeNull(context); + } + + if (JSStringIsEqualToUTF8CString(propertyName, "y")) { + static size_t count; + if (count++ < 5) + return NULL; + + // Swallow all .y gets after 5, returning null. + return JSValueMakeNull(context); + } + + if (JSStringIsEqualToUTF8CString(propertyName, "z")) { + static size_t count; + if (count++ < 5) + return NULL; + + // Swallow all .y gets after 5, returning null. + return JSValueMakeNull(context); + } + + return NULL; +} + +static bool PropertyCatchalls_setProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) +{ + UNUSED_PARAM(context); + UNUSED_PARAM(object); + UNUSED_PARAM(propertyName); + UNUSED_PARAM(value); + UNUSED_PARAM(exception); + + if (JSStringIsEqualToUTF8CString(propertyName, "x")) { + static size_t count; + if (count++ < 5) + return false; + + // Swallow all .x sets after 4. + return true; + } + + return false; +} + +static void PropertyCatchalls_getPropertyNames(JSContextRef context, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) +{ + UNUSED_PARAM(context); + UNUSED_PARAM(object); + + static size_t count; + static const char* numbers[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + // Provide a property of a different name every time. + JSStringRef propertyName = JSStringCreateWithUTF8CString(numbers[count++ % 10]); + JSPropertyNameAccumulatorAddName(propertyNames, propertyName); + JSStringRelease(propertyName); +} + +JSClassDefinition PropertyCatchalls_definition = { + 0, + kJSClassAttributeNone, + + "PropertyCatchalls", + NULL, + + NULL, + NULL, + + NULL, + NULL, + NULL, + PropertyCatchalls_getProperty, + PropertyCatchalls_setProperty, + NULL, + PropertyCatchalls_getPropertyNames, + NULL, + NULL, + NULL, + NULL, +}; + +static JSClassRef PropertyCatchalls_class(JSContextRef context) +{ + UNUSED_PARAM(context); + + static JSClassRef jsClass; + if (!jsClass) + jsClass = JSClassCreate(&PropertyCatchalls_definition); + + return jsClass; +} + +static bool EvilExceptionObject_hasInstance(JSContextRef context, JSObjectRef constructor, JSValueRef possibleValue, JSValueRef* exception) +{ + UNUSED_PARAM(context); + UNUSED_PARAM(constructor); + + JSStringRef hasInstanceName = JSStringCreateWithUTF8CString("hasInstance"); + JSValueRef hasInstance = JSObjectGetProperty(context, constructor, hasInstanceName, exception); + JSStringRelease(hasInstanceName); + if (!hasInstance) + return false; + JSObjectRef function = JSValueToObject(context, hasInstance, exception); + JSValueRef result = JSObjectCallAsFunction(context, function, constructor, 1, &possibleValue, exception); + return result && JSValueToBoolean(context, result); +} + +static JSValueRef EvilExceptionObject_convertToType(JSContextRef context, JSObjectRef object, JSType type, JSValueRef* exception) +{ + UNUSED_PARAM(object); + UNUSED_PARAM(exception); + JSStringRef funcName; + switch (type) { + case kJSTypeNumber: + funcName = JSStringCreateWithUTF8CString("toNumber"); + break; + case kJSTypeString: + funcName = JSStringCreateWithUTF8CString("toStringExplicit"); + break; + default: + return JSValueMakeNull(context); + break; + } + + JSValueRef func = JSObjectGetProperty(context, object, funcName, exception); + JSStringRelease(funcName); + JSObjectRef function = JSValueToObject(context, func, exception); + if (!function) + return JSValueMakeNull(context); + JSValueRef value = JSObjectCallAsFunction(context, function, object, 0, NULL, exception); + if (!value) { + JSStringRef errorString = JSStringCreateWithUTF8CString("convertToType failed"); + JSValueRef errorStringRef = JSValueMakeString(context, errorString); + JSStringRelease(errorString); + return errorStringRef; + } + return value; +} + +JSClassDefinition EvilExceptionObject_definition = { + 0, + kJSClassAttributeNone, + + "EvilExceptionObject", + NULL, + + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + EvilExceptionObject_hasInstance, + EvilExceptionObject_convertToType, +}; + +static JSClassRef EvilExceptionObject_class(JSContextRef context) +{ + UNUSED_PARAM(context); + + static JSClassRef jsClass; + if (!jsClass) + jsClass = JSClassCreate(&EvilExceptionObject_definition); + + return jsClass; +} + +JSClassDefinition EmptyObject_definition = { + 0, + kJSClassAttributeNone, + + NULL, + NULL, + + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static JSClassRef EmptyObject_class(JSContextRef context) +{ + UNUSED_PARAM(context); + + static JSClassRef jsClass; + if (!jsClass) + jsClass = JSClassCreate(&EmptyObject_definition); + + return jsClass; +} + + static JSValueRef Base_get(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { UNUSED_PARAM(object); @@ -454,14 +744,27 @@ static JSClassRef Derived_class(JSContextRef context) return jsClass; } -static JSValueRef print_callAsFunction(JSContextRef context, JSObjectRef functionObject, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +static JSClassRef Derived2_class(JSContextRef context) +{ + static JSClassRef jsClass; + if (!jsClass) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.parentClass = Derived_class(context); + jsClass = JSClassCreate(&definition); + } + return jsClass; +} + +static JSValueRef print_callAsFunction(JSContextRef ctx, JSObjectRef functionObject, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { UNUSED_PARAM(functionObject); UNUSED_PARAM(thisObject); UNUSED_PARAM(exception); + + ASSERT(JSContextGetGlobalContext(ctx) == context); if (argumentCount > 0) { - JSStringRef string = JSValueToStringCopy(context, arguments[0], NULL); + JSStringRef string = JSValueToStringCopy(ctx, arguments[0], NULL); size_t sizeUTF8 = JSStringGetMaximumUTF8CStringSize(string); char* stringUTF8 = (char*)malloc(sizeUTF8); JSStringGetUTF8CString(string, stringUTF8, sizeUTF8); @@ -470,7 +773,7 @@ static JSValueRef print_callAsFunction(JSContextRef context, JSObjectRef functio JSStringRelease(string); } - return JSValueMakeUndefined(context); + return JSValueMakeUndefined(ctx); } static JSObjectRef myConstructor_callAsConstructor(JSContextRef context, JSObjectRef constructorObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) @@ -539,6 +842,17 @@ static JSValueRef globalObject_call(JSContextRef ctx, JSObjectRef function, JSOb return JSValueMakeNumber(ctx, 3); } +static JSValueRef functionGC(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + UNUSED_PARAM(function); + UNUSED_PARAM(thisObject); + UNUSED_PARAM(argumentCount); + UNUSED_PARAM(arguments); + UNUSED_PARAM(exception); + JSGarbageCollect(context); + return JSValueMakeUndefined(context); +} + static JSStaticValue globalObject_staticValues[] = { { "globalStaticValue", globalObject_get, globalObject_set, kJSPropertyAttributeNone }, { 0, 0, 0, 0 } @@ -546,6 +860,7 @@ static JSStaticValue globalObject_staticValues[] = { static JSStaticFunction globalObject_staticFunctions[] = { { "globalStaticFunction", globalObject_call, kJSPropertyAttributeNone }, + { "gc", functionGC, kJSPropertyAttributeNone }, { 0, 0, 0 } }; @@ -558,8 +873,79 @@ static void testInitializeFinalize() ASSERT(JSObjectGetPrivate(o) == (void*)3); } +static JSValueRef jsNumberValue = NULL; + +static JSObjectRef aHeapRef = NULL; + +static void makeGlobalNumberValue(JSContextRef context) { + JSValueRef v = JSValueMakeNumber(context, 420); + JSValueProtect(context, v); + jsNumberValue = v; + v = NULL; +} + +static bool assertTrue(bool value, const char* message) +{ + if (!value) { + if (message) + fprintf(stderr, "assertTrue failed: '%s'\n", message); + else + fprintf(stderr, "assertTrue failed.\n"); + failed = 1; + } + return value; +} + +static bool checkForCycleInPrototypeChain() +{ + bool result = true; + JSGlobalContextRef context = JSGlobalContextCreate(0); + JSObjectRef object1 = JSObjectMake(context, /* jsClass */ 0, /* data */ 0); + JSObjectRef object2 = JSObjectMake(context, /* jsClass */ 0, /* data */ 0); + JSObjectRef object3 = JSObjectMake(context, /* jsClass */ 0, /* data */ 0); + + JSObjectSetPrototype(context, object1, JSValueMakeNull(context)); + ASSERT(JSValueIsNull(context, JSObjectGetPrototype(context, object1))); + + // object1 -> object1 + JSObjectSetPrototype(context, object1, object1); + result &= assertTrue(JSValueIsNull(context, JSObjectGetPrototype(context, object1)), "It is possible to assign self as a prototype"); + + // object1 -> object2 -> object1 + JSObjectSetPrototype(context, object2, object1); + ASSERT(JSValueIsStrictEqual(context, JSObjectGetPrototype(context, object2), object1)); + JSObjectSetPrototype(context, object1, object2); + result &= assertTrue(JSValueIsNull(context, JSObjectGetPrototype(context, object1)), "It is possible to close a prototype chain cycle"); + + // object1 -> object2 -> object3 -> object1 + JSObjectSetPrototype(context, object2, object3); + ASSERT(JSValueIsStrictEqual(context, JSObjectGetPrototype(context, object2), object3)); + JSObjectSetPrototype(context, object1, object2); + ASSERT(JSValueIsStrictEqual(context, JSObjectGetPrototype(context, object1), object2)); + JSObjectSetPrototype(context, object3, object1); + result &= assertTrue(!JSValueIsStrictEqual(context, JSObjectGetPrototype(context, object3), object1), "It is possible to close a prototype chain cycle"); + + JSValueRef exception; + JSStringRef code = JSStringCreateWithUTF8CString("o = { }; p = { }; o.__proto__ = p; p.__proto__ = o"); + JSStringRef file = JSStringCreateWithUTF8CString(""); + result &= assertTrue(!JSEvaluateScript(context, code, /* thisObject*/ 0, file, 1, &exception) + , "An exception should be thrown"); + + JSStringRelease(code); + JSStringRelease(file); + JSGlobalContextRelease(context); + return result; +} + int main(int argc, char* argv[]) { +#if OS(WINDOWS) + // Cygwin calls ::SetErrorMode(SEM_FAILCRITICALERRORS), which we will inherit. This is bad for + // testing/debugging, as it causes the post-mortem debugger not to be invoked. We reset the + // error mode here to work around Cygwin's behavior. See . + ::SetErrorMode(0); +#endif + const char *scriptPath = "testapi.js"; if (argc > 1) { scriptPath = argv[1]; @@ -584,6 +970,7 @@ int main(int argc, char* argv[]) JSGlobalContextRetain(context); JSGlobalContextRelease(context); + ASSERT(JSContextGetGlobalContext(context) == context); JSReportExtraMemoryCost(context, 0); JSReportExtraMemoryCost(context, 1); @@ -651,13 +1038,139 @@ int main(int argc, char* argv[]) ASSERT(JSValueGetType(context, jsCFEmptyString) == kJSTypeString); ASSERT(JSValueGetType(context, jsCFEmptyStringWithCharacters) == kJSTypeString); + JSObjectRef propertyCatchalls = JSObjectMake(context, PropertyCatchalls_class(context), NULL); + JSStringRef propertyCatchallsString = JSStringCreateWithUTF8CString("PropertyCatchalls"); + JSObjectSetProperty(context, globalObject, propertyCatchallsString, propertyCatchalls, kJSPropertyAttributeNone, NULL); + JSStringRelease(propertyCatchallsString); + JSObjectRef myObject = JSObjectMake(context, MyObject_class(context), NULL); JSStringRef myObjectIString = JSStringCreateWithUTF8CString("MyObject"); JSObjectSetProperty(context, globalObject, myObjectIString, myObject, kJSPropertyAttributeNone, NULL); JSStringRelease(myObjectIString); - JSValueRef exception; + JSObjectRef EvilExceptionObject = JSObjectMake(context, EvilExceptionObject_class(context), NULL); + JSStringRef EvilExceptionObjectIString = JSStringCreateWithUTF8CString("EvilExceptionObject"); + JSObjectSetProperty(context, globalObject, EvilExceptionObjectIString, EvilExceptionObject, kJSPropertyAttributeNone, NULL); + JSStringRelease(EvilExceptionObjectIString); + + JSObjectRef EmptyObject = JSObjectMake(context, EmptyObject_class(context), NULL); + JSStringRef EmptyObjectIString = JSStringCreateWithUTF8CString("EmptyObject"); + JSObjectSetProperty(context, globalObject, EmptyObjectIString, EmptyObject, kJSPropertyAttributeNone, NULL); + JSStringRelease(EmptyObjectIString); + + JSStringRef lengthStr = JSStringCreateWithUTF8CString("length"); + JSObjectRef aStackRef = JSObjectMakeArray(context, 0, 0, 0); + aHeapRef = aStackRef; + JSObjectSetProperty(context, aHeapRef, lengthStr, JSValueMakeNumber(context, 10), 0, 0); + JSStringRef privatePropertyName = JSStringCreateWithUTF8CString("privateProperty"); + if (!JSObjectSetPrivateProperty(context, myObject, privatePropertyName, aHeapRef)) { + printf("FAIL: Could not set private property.\n"); + failed = 1; + } else + printf("PASS: Set private property.\n"); + aStackRef = 0; + if (JSObjectSetPrivateProperty(context, aHeapRef, privatePropertyName, aHeapRef)) { + printf("FAIL: JSObjectSetPrivateProperty should fail on non-API objects.\n"); + failed = 1; + } else + printf("PASS: Did not allow JSObjectSetPrivateProperty on a non-API object.\n"); + if (JSObjectGetPrivateProperty(context, myObject, privatePropertyName) != aHeapRef) { + printf("FAIL: Could not retrieve private property.\n"); + failed = 1; + } else + printf("PASS: Retrieved private property.\n"); + if (JSObjectGetPrivateProperty(context, aHeapRef, privatePropertyName)) { + printf("FAIL: JSObjectGetPrivateProperty should return NULL when called on a non-API object.\n"); + failed = 1; + } else + printf("PASS: JSObjectGetPrivateProperty return NULL.\n"); + + if (JSObjectGetProperty(context, myObject, privatePropertyName, 0) == aHeapRef) { + printf("FAIL: Accessed private property through ordinary property lookup.\n"); + failed = 1; + } else + printf("PASS: Cannot access private property through ordinary property lookup.\n"); + + JSGarbageCollect(context); + for (int i = 0; i < 10000; i++) + JSObjectMake(context, 0, 0); + + aHeapRef = JSValueToObject(context, JSObjectGetPrivateProperty(context, myObject, privatePropertyName), 0); + if (JSValueToNumber(context, JSObjectGetProperty(context, aHeapRef, lengthStr, 0), 0) != 10) { + printf("FAIL: Private property has been collected.\n"); + failed = 1; + } else + printf("PASS: Private property does not appear to have been collected.\n"); + JSStringRelease(lengthStr); + + if (!JSObjectSetPrivateProperty(context, myObject, privatePropertyName, 0)) { + printf("FAIL: Could not set private property to NULL.\n"); + failed = 1; + } else + printf("PASS: Set private property to NULL.\n"); + if (JSObjectGetPrivateProperty(context, myObject, privatePropertyName)) { + printf("FAIL: Could not retrieve private property.\n"); + failed = 1; + } else + printf("PASS: Retrieved private property.\n"); + + JSStringRef validJSON = JSStringCreateWithUTF8CString("{\"aProperty\":true}"); + JSValueRef jsonObject = JSValueMakeFromJSONString(context, validJSON); + JSStringRelease(validJSON); + if (!JSValueIsObject(context, jsonObject)) { + printf("FAIL: Did not parse valid JSON correctly\n"); + failed = 1; + } else + printf("PASS: Parsed valid JSON string.\n"); + JSStringRef propertyName = JSStringCreateWithUTF8CString("aProperty"); + assertEqualsAsBoolean(JSObjectGetProperty(context, JSValueToObject(context, jsonObject, 0), propertyName, 0), true); + JSStringRelease(propertyName); + JSStringRef invalidJSON = JSStringCreateWithUTF8CString("fail!"); + if (JSValueMakeFromJSONString(context, invalidJSON)) { + printf("FAIL: Should return null for invalid JSON data\n"); + failed = 1; + } else + printf("PASS: Correctly returned null for invalid JSON data.\n"); + JSValueRef exception; + JSStringRef str = JSValueCreateJSONString(context, jsonObject, 0, 0); + if (!JSStringIsEqualToUTF8CString(str, "{\"aProperty\":true}")) { + printf("FAIL: Did not correctly serialise with indent of 0.\n"); + failed = 1; + } else + printf("PASS: Correctly serialised with indent of 0.\n"); + JSStringRelease(str); + + str = JSValueCreateJSONString(context, jsonObject, 4, 0); + if (!JSStringIsEqualToUTF8CString(str, "{\n \"aProperty\": true\n}")) { + printf("FAIL: Did not correctly serialise with indent of 4.\n"); + failed = 1; + } else + printf("PASS: Correctly serialised with indent of 4.\n"); + JSStringRelease(str); + JSStringRef src = JSStringCreateWithUTF8CString("({get a(){ throw '';}})"); + JSValueRef unstringifiableObj = JSEvaluateScript(context, src, NULL, NULL, 1, NULL); + + str = JSValueCreateJSONString(context, unstringifiableObj, 4, 0); + if (str) { + printf("FAIL: Didn't return null when attempting to serialize unserializable value.\n"); + JSStringRelease(str); + failed = 1; + } else + printf("PASS: returned null when attempting to serialize unserializable value.\n"); + + str = JSValueCreateJSONString(context, unstringifiableObj, 4, &exception); + if (str) { + printf("FAIL: Didn't return null when attempting to serialize unserializable value.\n"); + JSStringRelease(str); + failed = 1; + } else + printf("PASS: returned null when attempting to serialize unserializable value.\n"); + if (!exception) { + printf("FAIL: Did not set exception on serialisation error\n"); + failed = 1; + } else + printf("PASS: set exception on serialisation error\n"); // Conversions that throw exceptions exception = NULL; ASSERT(NULL == JSValueToObject(context, jsNull, &exception)); @@ -758,10 +1271,12 @@ int main(int argc, char* argv[]) CFRelease(cfEmptyString); jsGlobalValue = JSObjectMake(context, NULL, NULL); + makeGlobalNumberValue(context); JSValueProtect(context, jsGlobalValue); JSGarbageCollect(context); ASSERT(JSValueIsObject(context, jsGlobalValue)); JSValueUnprotect(context, jsGlobalValue); + JSValueUnprotect(context, jsNumberValue); JSStringRef goodSyntax = JSStringCreateWithUTF8CString("x = 1;"); JSStringRef badSyntax = JSStringCreateWithUTF8CString("x := 1;"); @@ -846,7 +1361,7 @@ int main(int argc, char* argv[]) JSStringRelease(functionBody); string = JSValueToStringCopy(context, function, NULL); - assertEqualsAsUTF8String(JSValueMakeString(context, string), "function foo(foo) {return foo;}"); + assertEqualsAsUTF8String(JSValueMakeString(context, string), "function foo(foo) { return foo;\n}"); JSStringRelease(string); JSStringRef print = JSStringCreateWithUTF8CString("print"); @@ -865,11 +1380,21 @@ int main(int argc, char* argv[]) ASSERT(!JSObjectSetPrivate(myConstructor, (void*)1)); ASSERT(!JSObjectGetPrivate(myConstructor)); + string = JSStringCreateWithUTF8CString("Base"); + JSObjectRef baseConstructor = JSObjectMakeConstructor(context, Base_class(context), NULL); + JSObjectSetProperty(context, globalObject, string, baseConstructor, kJSPropertyAttributeNone, NULL); + JSStringRelease(string); + string = JSStringCreateWithUTF8CString("Derived"); JSObjectRef derivedConstructor = JSObjectMakeConstructor(context, Derived_class(context), NULL); JSObjectSetProperty(context, globalObject, string, derivedConstructor, kJSPropertyAttributeNone, NULL); JSStringRelease(string); + string = JSStringCreateWithUTF8CString("Derived2"); + JSObjectRef derived2Constructor = JSObjectMakeConstructor(context, Derived2_class(context), NULL); + JSObjectSetProperty(context, globalObject, string, derived2Constructor, kJSPropertyAttributeNone, NULL); + JSStringRelease(string); + o = JSObjectMake(context, NULL, NULL); JSObjectSetProperty(context, o, jsOneIString, JSValueMakeNumber(context, 1), kJSPropertyAttributeNone, NULL); JSObjectSetProperty(context, o, jsCFIString, JSValueMakeNumber(context, 1), kJSPropertyAttributeDontEnum, NULL); @@ -898,7 +1423,8 @@ int main(int argc, char* argv[]) JSValueRef argumentsDateValues[] = { JSValueMakeNumber(context, 0) }; o = JSObjectMakeDate(context, 1, argumentsDateValues, NULL); - assertEqualsAsUTF8String(o, "Wed Dec 31 1969 16:00:00 GMT-0800 (PST)"); + if (timeZoneIsPST()) + assertEqualsAsUTF8String(o, "Wed Dec 31 1969 16:00:00 GMT-0800 (PST)"); string = JSStringCreateWithUTF8CString("an error message"); JSValueRef argumentsErrorValues[] = { JSValueMakeString(context, string) }; @@ -953,13 +1479,21 @@ int main(int argc, char* argv[]) ASSERT(JSValueIsEqual(context, v, o, NULL)); JSStringRelease(script); + // Verify that creating a constructor for a class with no static functions does not trigger + // an assert inside putDirect or lead to a crash during GC. + nullDefinition = kJSClassDefinitionEmpty; + nullClass = JSClassCreate(&nullDefinition); + myConstructor = JSObjectMakeConstructor(context, nullClass, 0); + JSClassRelease(nullClass); + char* scriptUTF8 = createStringWithContentsOfFile(scriptPath); - if (!scriptUTF8) + if (!scriptUTF8) { printf("FAIL: Test script could not be loaded.\n"); - else { + failed = 1; + } else { script = JSStringCreateWithUTF8CString(scriptUTF8); result = JSEvaluateScript(context, script, NULL, NULL, 1, &exception); - if (JSValueIsUndefined(context, result)) + if (result && JSValueIsUndefined(context, result)) printf("PASS: Test script executed successfully.\n"); else { printf("FAIL: Test script returned unexpected value:\n"); @@ -968,6 +1502,7 @@ int main(int argc, char* argv[]) CFShow(exceptionCF); CFRelease(exceptionCF); JSStringRelease(exceptionIString); + failed = 1; } JSStringRelease(script); free(scriptUTF8); @@ -978,6 +1513,7 @@ int main(int argc, char* argv[]) v = NULL; o = NULL; globalObject = NULL; + myConstructor = NULL; JSStringRelease(jsEmptyIString); JSStringRelease(jsOneIString); @@ -991,6 +1527,34 @@ int main(int argc, char* argv[]) JSGlobalContextRelease(context); JSClassRelease(globalObjectClass); + // Test for an infinite prototype chain that used to be created. This test + // passes if the call to JSObjectHasProperty() does not hang. + + JSClassDefinition prototypeLoopClassDefinition = kJSClassDefinitionEmpty; + prototypeLoopClassDefinition.staticFunctions = globalObject_staticFunctions; + JSClassRef prototypeLoopClass = JSClassCreate(&prototypeLoopClassDefinition); + JSGlobalContextRef prototypeLoopContext = JSGlobalContextCreateInGroup(NULL, prototypeLoopClass); + + JSStringRef nameProperty = JSStringCreateWithUTF8CString("name"); + JSObjectHasProperty(prototypeLoopContext, JSContextGetGlobalObject(prototypeLoopContext), nameProperty); + + JSGlobalContextRelease(prototypeLoopContext); + JSClassRelease(prototypeLoopClass); + + printf("PASS: Infinite prototype chain does not occur.\n"); + + if (checkForCycleInPrototypeChain()) + printf("PASS: A cycle in a prototype chain can't be created.\n"); + else { + printf("FAIL: A cycle in a prototype chain can be created.\n"); + failed = true; + } + + if (failed) { + printf("FAIL: Some tests failed.\n"); + return 1; + } + printf("PASS: Program exited normally.\n"); return 0; }