From 3c88ad59f5996c862bc63de06ee6349fffd5dc04 Mon Sep 17 00:00:00 2001 From: Jens Ayton Date: Sun, 27 Jun 2010 23:52:37 +0000 Subject: [PATCH] Cleaned up some invalid JS attributes. Fixed JS namespace pollution with call() and inspect(), and renamed call() to callObjC(). Mission variable names may no longer begin with underscores. Script names may no longer begin or end with underscores or whitespace. The missionVariables object may now be enumerated with for-in. git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@3636 127b21dd-08f5-0310-b4b7-95ae10353056 --- DebugOXP/Resources/debugConfig.plist | 2 +- DebugOXP/Resources/oolite-debug-console.js | 5 +- src/Core/Debug/OOJSConsole.m | 26 +++++- src/Core/Scripting/OOJSMission.m | 4 + src/Core/Scripting/OOJSMissionVariables.m | 94 ++++++++++++++++++---- src/Core/Scripting/OOJSScript.m | 15 +++- src/Core/Scripting/OOJSSystemInfo.m | 2 +- src/Core/Scripting/OOJSWorldScripts.m | 2 +- 8 files changed, 124 insertions(+), 26 deletions(-) diff --git a/DebugOXP/Resources/debugConfig.plist b/DebugOXP/Resources/debugConfig.plist index 1236a819..c5be6290 100644 --- a/DebugOXP/Resources/debugConfig.plist +++ b/DebugOXP/Resources/debugConfig.plist @@ -106,7 +106,7 @@ "test" = "mission.runScreen({model:PARAM})"; // ":time " -- time a JavaScript expression. - "time" = "eval(\"this._profileFunc = function() {\" + PARAM + \" };\"); console.profile(this._profileFunc, this);"; + "time" = "eval(\"this._profileFunc = function() { \" + PARAM + \" };\"); console.profile(this._profileFunc, this);"; // For calling old-school scripting methods (on player), as in ":: gui_screen_string", or ":: playSound: boop.ogg" ":" = "performLegacyCommand(PARAM)"; diff --git a/DebugOXP/Resources/oolite-debug-console.js b/DebugOXP/Resources/oolite-debug-console.js index 35632623..12c06376 100644 --- a/DebugOXP/Resources/oolite-debug-console.js +++ b/DebugOXP/Resources/oolite-debug-console.js @@ -491,11 +491,10 @@ function consoleMessage() // Add inspect() method to all entities, to show inspector palette (Mac OS X only; no effect on other platforms). -Object.getPrototypeOf(Entity).inspect = function inspect() +Entity.inspect = function inspect() { debugConsole.inspectEntity(this); } -// Add call() method to all entities (calls an Objective-C method directly), now only available with debug OXP to avoid abuse. -Object.getPrototypeOf(Entity).call = debugConsole.__callObjCMethod; +debugConsole.__setUpCallObjC(Object.prototype); diff --git a/src/Core/Debug/OOJSConsole.m b/src/Core/Debug/OOJSConsole.m index 11c367e2..bdd7e7fb 100644 --- a/src/Core/Debug/OOJSConsole.m +++ b/src/Core/Debug/OOJSConsole.m @@ -71,6 +71,7 @@ static JSBool ConsoleClearConsole(JSContext *context, JSObject *this, uintN argc static JSBool ConsoleScriptStack(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult); static JSBool ConsoleInspectEntity(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult); static JSBool ConsoleCallObjCMethod(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult); +static JSBool ConsoleSetUpCallObjC(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult); static JSBool ConsoleIsExecutableJavaScript(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult); static JSBool ConsoleDisplayMessagesInClass(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult); static JSBool ConsoleSetDisplayMessagesInClass(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult); @@ -93,7 +94,7 @@ static JSBool PerformProfiling(JSContext *context, NSString *nominalFunction, ui static JSClass sConsoleClass = { "Console", - JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS, + JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty @@ -178,7 +179,7 @@ static JSFunctionSpec sConsoleMethods[] = { "clearConsole", ConsoleClearConsole, 0 }, { "scriptStack", ConsoleScriptStack, 0 }, { "inspectEntity", ConsoleInspectEntity, 1 }, - { "__callObjCMethod", ConsoleCallObjCMethod, 1 }, + { "__setUpCallObjC", ConsoleSetUpCallObjC, 1, JSPROP_READONLY }, { "isExecutableJavaScript", ConsoleIsExecutableJavaScript, 2 }, { "displayMessagesInClass", ConsoleDisplayMessagesInClass, 1 }, { "setDisplayMessagesInClass", ConsoleSetDisplayMessagesInClass, 2 }, @@ -667,7 +668,7 @@ static JSBool ConsoleInspectEntity(JSContext *context, JSObject *this, uintN arg } -// function __callObjCMethod(selector : String [, ...]) : Object +// function callObjC(selector : String [, ...]) : Object static JSBool ConsoleCallObjCMethod(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult) { OOJS_NATIVE_ENTER(context) @@ -691,6 +692,25 @@ static JSBool ConsoleCallObjCMethod(JSContext *context, JSObject *this, uintN ar } +// function __setUpCallObjC(object) -- object is expected to be Object.prototye. +static JSBool ConsoleSetUpCallObjC(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult) +{ + OOJS_NATIVE_ENTER(context) + + if (EXPECT_NOT(!JSVAL_IS_OBJECT(argv[0]))) + { + OOReportJSBadArguments(context, @"Console", @"__setUpCallObjC", argc, argv, nil, @"Object.prototype"); + return NO; + } + + JSObject *obj = JSVAL_TO_OBJECT(argv[0]); + JS_DefineFunction(context, obj, "callObjC", ConsoleCallObjCMethod, 1, JSPROP_PERMANENT | JSPROP_READONLY); + return YES; + + OOJS_NATIVE_EXIT +} + + // function isExecutableJavaScript(this : Object, string : String) : Boolean static JSBool ConsoleIsExecutableJavaScript(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult) { diff --git a/src/Core/Scripting/OOJSMission.m b/src/Core/Scripting/OOJSMission.m index b25bf2f2..5e850471 100644 --- a/src/Core/Scripting/OOJSMission.m +++ b/src/Core/Scripting/OOJSMission.m @@ -291,6 +291,8 @@ static JSBool MissionRunScreen(JSContext *context, JSObject *this, uintN argc, j return NO; } + OOJSPauseTimeLimiter(); + if (function != JSVAL_NULL) { sCallbackScript = [[[OOJSScript currentlyRunningScript] weakRefUnderlyingObject] retain]; @@ -358,6 +360,8 @@ static JSBool MissionRunScreen(JSContext *context, JSObject *this, uintN argc, j [player setMissionTitle:nil]; [player setMissionMusic:nil]; + OOJSResumeTimeLimiter(); + *outResult = JSVAL_TRUE; return YES; diff --git a/src/Core/Scripting/OOJSMissionVariables.m b/src/Core/Scripting/OOJSMissionVariables.m index b51b96f7..e1757ffd 100644 --- a/src/Core/Scripting/OOJSMissionVariables.m +++ b/src/Core/Scripting/OOJSMissionVariables.m @@ -34,34 +34,47 @@ MA 02110-1301, USA. static NSString *KeyForName(JSContext *context, jsval name) { - return [@"mission_" stringByAppendingString:[NSString stringWithJavaScriptValue:name inContext:context]]; + NSCParameterAssert(JSVAL_IS_STRING(name)); + + NSString *key = [NSString stringWithJavaScriptString:JSVAL_TO_STRING(name)]; + if ([key hasPrefix:@"_"]) return nil; + return [@"mission_" stringByAppendingString:key]; } static JSBool MissionVariablesDeleteProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue); static JSBool MissionVariablesGetProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue); static JSBool MissionVariablesSetProperty(JSContext *context, JSObject *this, jsval name, jsval *value); +static JSBool MissionVariablesEnumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp); -static JSClass sMissionVariablesClass = +static JSExtendedClass sMissionVariablesClass = { - "MissionVariables", - JSCLASS_IS_ANONYMOUS, + { + "MissionVariables", + JSCLASS_NEW_ENUMERATE | JSCLASS_IS_EXTENDED, + + JS_PropertyStub, + MissionVariablesDeleteProperty, + MissionVariablesGetProperty, + MissionVariablesSetProperty, + (JSEnumerateOp)MissionVariablesEnumerate, + JS_ResolveStub, + JS_ConvertStub, + JS_FinalizeStub + }, - JS_PropertyStub, - MissionVariablesDeleteProperty, - MissionVariablesGetProperty, - MissionVariablesSetProperty, - JS_EnumerateStub, - JS_ResolveStub, - JS_ConvertStub, - JS_FinalizeStub + NULL, + NULL, + NULL, + + JSCLASS_NO_RESERVED_MEMBERS }; void InitOOJSMissionVariables(JSContext *context, JSObject *global) { - JS_DefineObject(context, global, "missionVariables", &sMissionVariablesClass, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineObject(context, global, "missionVariables", &sMissionVariablesClass.base, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); } @@ -90,8 +103,10 @@ static JSBool MissionVariablesGetProperty(JSContext *context, JSObject *this, js if (JSVAL_IS_STRING(name)) { - NSString *key = KeyForName(context, name); - id value = [player missionVariableForKey:key]; + NSString *key = KeyForName(context, name); + if (key == nil) return YES; + + id value = [player missionVariableForKey:key]; *outValue = JSVAL_VOID; if ([value isKindOfClass:[NSString class]]) // Currently there should only be strings, but we may want to change this. @@ -142,3 +157,52 @@ static JSBool MissionVariablesSetProperty(JSContext *context, JSObject *this, js OOJS_NATIVE_EXIT } + + +static JSBool MissionVariablesEnumerate(JSContext *context, JSObject *object, JSIterateOp enumOp, jsval *state, jsid *idp) +{ + OOJS_NATIVE_ENTER(context) + + NSEnumerator *mvarEnumerator = JSVAL_TO_PRIVATE(*state); + + switch (enumOp) + { + case JSENUMERATE_INIT: + { + // -allKeys implicitly makes a copy, which is good since the enumerating code might mutate. + NSArray *mvars = [[[PlayerEntity sharedPlayer] missionVariables] allKeys]; + mvarEnumerator = [[mvars objectEnumerator] retain]; + *state = PRIVATE_TO_JSVAL(mvarEnumerator); + if (idp != NULL) + { + *idp = INT_TO_JSVAL([mvars count]); + } + return YES; + } + + case JSENUMERATE_NEXT: + { + id next = [mvarEnumerator nextObject]; + if (next != nil) + { + NSCAssert1([next hasPrefix:@"mission_"] || next == nil, @"Mission variable key without \"mission_\" prefix: %@.", next); + next = [next substringFromIndex:8]; + + jsval val = [next javaScriptValueInContext:context]; + return JS_ValueToId(context, val, idp); + } + // else: + *state = JSVAL_NULL; + // Fall through. + } + + case JSENUMERATE_DESTROY: + { + [mvarEnumerator release]; + if (idp != NULL) return JS_ValueToId(context, JSVAL_VOID, idp); + return YES; + } + } + + OOJS_NATIVE_EXIT +} diff --git a/src/Core/Scripting/OOJSScript.m b/src/Core/Scripting/OOJSScript.m index 32ba8268..bc108b8a 100644 --- a/src/Core/Scripting/OOJSScript.m +++ b/src/Core/Scripting/OOJSScript.m @@ -69,6 +69,8 @@ static NSData *CompiledScriptData(JSContext *context, JSScript *script); static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data); #endif +static NSString *StrippedName(NSString *string); + static JSClass sScriptClass = { @@ -202,7 +204,7 @@ static JSFunctionSpec sScriptMethods[] = { // Get display attributes from script DESTROY(name); - name = [[[self propertyNamed:@"name"] description] copy]; + name = [StrippedName([[self propertyNamed:@"name"] description]) copy]; if (name == nil) { name = [[self scriptNameFromPath:path] retain]; @@ -582,7 +584,7 @@ static JSFunctionSpec sScriptMethods[] = if (0 == [theName length]) theName = path; - return [theName stringByAppendingString:@".anon-script"]; + return StrippedName([theName stringByAppendingString:@".anon-script"]); } @end @@ -708,3 +710,12 @@ static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data) return result; } #endif + + +static NSString *StrippedName(NSString *string) +{ + static NSCharacterSet *invalidSet = nil; + if (invalidSet == nil) invalidSet = [[NSCharacterSet characterSetWithCharactersInString:@"_ \t\n\r\v"] retain]; + + return [string stringByTrimmingCharactersInSet:invalidSet]; +} diff --git a/src/Core/Scripting/OOJSSystemInfo.m b/src/Core/Scripting/OOJSSystemInfo.m index fd1c000d..eabed0a7 100644 --- a/src/Core/Scripting/OOJSSystemInfo.m +++ b/src/Core/Scripting/OOJSSystemInfo.m @@ -50,7 +50,7 @@ static JSExtendedClass sSystemInfoClass = { { "SystemInfo", - JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_PRIVATE | JSCLASS_IS_EXTENDED, + JSCLASS_HAS_PRIVATE | JSCLASS_IS_EXTENDED, JS_PropertyStub, SystemInfoDeleteProperty, diff --git a/src/Core/Scripting/OOJSWorldScripts.m b/src/Core/Scripting/OOJSWorldScripts.m index a998e6df..a5c2b328 100644 --- a/src/Core/Scripting/OOJSWorldScripts.m +++ b/src/Core/Scripting/OOJSWorldScripts.m @@ -38,7 +38,7 @@ static JSBool GetWorldScriptNames(JSContext *context, JSObject *this, jsval name static JSClass sWorldScriptsClass = { "WorldScripts", - JSCLASS_IS_ANONYMOUS, + 0, JS_PropertyStub, JS_PropertyStub,