Line data Source code
1 0 : /*
2 :
3 : OOJSScript.m
4 :
5 : JavaScript support for Oolite
6 : Copyright (C) 2007-2013 David Taylor and Jens Ayton.
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #ifndef OO_CACHE_JS_SCRIPTS
26 0 : #define OO_CACHE_JS_SCRIPTS 1
27 : #endif
28 :
29 :
30 : #import "OOJSScript.h"
31 : #import "OOJavaScriptEngine.h"
32 : #import "OOJSEngineTimeManagement.h"
33 :
34 : #import "OOLogging.h"
35 : #import "OOConstToString.h"
36 : #import "Entity.h"
37 : #import "NSStringOOExtensions.h"
38 : #import "EntityOOJavaScriptExtensions.h"
39 : #import "OOConstToJSString.h"
40 : #import "OOManifestProperties.h"
41 : #import "OOCollectionExtractors.h"
42 : #import "OOPListParsing.h"
43 : #import "OODebugStandards.h"
44 :
45 : #if OO_CACHE_JS_SCRIPTS
46 : #include <jsxdrapi.h>
47 : #import "OOCacheManager.h"
48 : #endif
49 :
50 :
51 0 : typedef struct RunningStack RunningStack;
52 0 : struct RunningStack
53 : {
54 0 : RunningStack *back;
55 0 : OOJSScript *current;
56 : };
57 :
58 :
59 0 : static JSObject *sScriptPrototype;
60 0 : static RunningStack *sRunningStack = NULL;
61 :
62 :
63 : static void AddStackToArrayReversed(NSMutableArray *array, RunningStack *stack);
64 :
65 : static JSScript *LoadScriptWithName(JSContext *context, NSString *path, JSObject *object, JSObject **outScriptObject, NSString **outErrorMessage);
66 :
67 : #if OO_CACHE_JS_SCRIPTS
68 : static NSData *CompiledScriptData(JSContext *context, JSScript *script);
69 : static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data);
70 : #endif
71 :
72 : static NSString *StrippedName(NSString *string);
73 :
74 :
75 : static JSBool ScriptAddProperty(JSContext *context, JSObject *this, jsid propID, jsval *value);
76 :
77 :
78 0 : static JSClass sScriptClass =
79 : {
80 : "Script",
81 : JSCLASS_HAS_PRIVATE,
82 :
83 : ScriptAddProperty,
84 : JS_PropertyStub,
85 : JS_PropertyStub,
86 : JS_StrictPropertyStub,
87 : JS_EnumerateStub,
88 : JS_ResolveStub,
89 : JS_ConvertStub,
90 : OOJSObjectWrapperFinalize
91 : };
92 :
93 :
94 0 : static JSFunctionSpec sScriptMethods[] =
95 : {
96 : // JS name Function min args
97 : { "toString", OOJSObjectWrapperToString, 0, },
98 : { 0 }
99 : };
100 :
101 :
102 : @interface OOJSScript (OOPrivate)
103 :
104 0 : - (NSString *)scriptNameFromPath:(NSString *)path;
105 0 : - (NSDictionary *)defaultPropertiesFromPath:(NSString *)path;
106 :
107 : @end
108 :
109 :
110 : @implementation OOJSScript
111 :
112 : + (id) scriptWithPath:(NSString *)path properties:(NSDictionary *)properties
113 : {
114 : return [[[self alloc] initWithPath:path properties:properties] autorelease];
115 : }
116 :
117 :
118 : - (id) initWithPath:(NSString *)path properties:(NSDictionary *)properties
119 : {
120 : JSContext *context = NULL;
121 : NSString *problem = nil; // Acts as error flag.
122 : JSScript *script = NULL;
123 : JSObject *scriptObject = NULL;
124 : jsval returnValue = JSVAL_VOID;
125 : NSEnumerator *keyEnum = nil;
126 : NSString *key = nil;
127 : id property = nil;
128 :
129 : self = [super init];
130 : if (self == nil) problem = @"allocation failure";
131 : else
132 : {
133 : context = OOJSAcquireContext();
134 :
135 : if (JS_IsExceptionPending(context))
136 : {
137 : JS_ClearPendingException(context);
138 : OOLogERR(@"script.javaScript.load.waitingException", @"Prior to loading script %@, there was a pending JavaScript exception, which has been cleared. This is an internal error, please report it.", path);
139 : }
140 :
141 : // Set up JS object
142 : if (!problem)
143 : {
144 : _jsSelf = JS_NewObject(context, &sScriptClass, sScriptPrototype, NULL);
145 : if (_jsSelf == NULL) problem = @"allocation failure";
146 : }
147 :
148 : if (!problem && !OOJSAddGCObjectRoot(context, &_jsSelf, "Script object"))
149 : {
150 : problem = @"could not add JavaScript root object";
151 : }
152 :
153 : if (!problem && !OOJSAddGCObjectRoot(context, &scriptObject, "Script GC holder"))
154 : {
155 : problem = @"could not add JavaScript root object";
156 : }
157 :
158 : if (!problem)
159 : {
160 : if (!JS_SetPrivate(context, _jsSelf, OOConsumeReference([self weakRetain])))
161 : {
162 : problem = @"could not set private backreference";
163 : }
164 : }
165 :
166 : // Push self on stack of running scripts.
167 : RunningStack stackElement =
168 : {
169 : .back = sRunningStack,
170 : .current = self
171 : };
172 : sRunningStack = &stackElement;
173 :
174 : filePath = [path retain];
175 :
176 : if (!problem)
177 : {
178 : OOLog(@"script.javaScript.willLoad", @"About to load JavaScript %@", path);
179 : script = LoadScriptWithName(context, path, _jsSelf, &scriptObject, &problem);
180 : }
181 : OOLogIndentIf(@"script.javaScript.willLoad");
182 :
183 : // Set default properties from manifest.plist
184 : NSDictionary *defaultProperties = [self defaultPropertiesFromPath:path];
185 : for (keyEnum = [defaultProperties keyEnumerator]; (key = [keyEnum nextObject]); )
186 : {
187 : if ([key isKindOfClass:[NSString class]])
188 : {
189 : property = [defaultProperties objectForKey:key];
190 : if ([key isEqualToString:kLocalManifestProperty])
191 : {
192 : // this must not be editable
193 : [self defineProperty:property named:key];
194 : }
195 : else
196 : {
197 : // can be overwritten by script itself
198 : [self setProperty:property named:key];
199 : }
200 : }
201 : }
202 :
203 : // Set properties. (read-only)
204 : if (!problem && properties != nil)
205 : {
206 : for (keyEnum = [properties keyEnumerator]; (key = [keyEnum nextObject]); )
207 : {
208 : if ([key isKindOfClass:[NSString class]])
209 : {
210 : property = [properties objectForKey:key];
211 : [self defineProperty:property named:key];
212 : }
213 : }
214 : }
215 :
216 : /* Set initial name (in case of script error during initial run).
217 : The "name" ivar is not set here, so the property can be fetched from JS
218 : if we fail during setup. However, the "name" ivar is set later so that
219 : the script object can't be renamed after the initial run. This could
220 : probably also be achieved by fiddling with JS property attributes.
221 : */
222 : jsid nameID = OOJSID("name");
223 : [self setProperty:[self scriptNameFromPath:path] withID:nameID inContext:context];
224 :
225 : // Run the script (allowing it to set up the properties we need, as well as setting up those event handlers)
226 : if (!problem)
227 : {
228 : OOJSStartTimeLimiterWithTimeLimit(kOOJSLongTimeLimit);
229 : if (!JS_ExecuteScript(context, _jsSelf, script, &returnValue))
230 : {
231 : problem = @"could not run script";
232 : }
233 : OOJSStopTimeLimiter();
234 :
235 : // We don't need the script any more - the event handlers hang around as long as the JS object exists.
236 : JS_DestroyScript(context, script);
237 : }
238 :
239 : JS_RemoveObjectRoot(context, &scriptObject);
240 :
241 : sRunningStack = stackElement.back;
242 :
243 : if (!problem)
244 : {
245 : // Get display attributes from script
246 : DESTROY(name);
247 : name = [StrippedName([[self propertyWithID:nameID inContext:context] description]) copy];
248 : if (name == nil)
249 : {
250 : name = [[self scriptNameFromPath:path] retain];
251 : [self setProperty:name withID:nameID inContext:context];
252 : }
253 :
254 : version = [[[self propertyWithID:OOJSID("version") inContext:context] description] copy];
255 : description = [[[self propertyWithID:OOJSID("description") inContext:context] description] copy];
256 :
257 : OOLog(@"script.javaScript.load.success", @"Loaded JavaScript: %@ -- %@", [self displayName], description ? description : (NSString *)@"(no description)");
258 : }
259 :
260 : OOLogOutdentIf(@"script.javaScript.willLoad");
261 :
262 : DESTROY(filePath); // Only used for error reporting during startup.
263 : }
264 :
265 : if (problem)
266 : {
267 : OOLog(@"script.javaScript.load.failed", @"***** Error loading JavaScript script %@ -- %@", path, problem);
268 : JS_ReportPendingException(context);
269 : DESTROY(self);
270 : }
271 :
272 : OOJSRelinquishContext(context);
273 :
274 : if (self != nil)
275 : {
276 : [[NSNotificationCenter defaultCenter] addObserver:self
277 : selector:@selector(javaScriptEngineWillReset:)
278 : name:kOOJavaScriptEngineWillResetNotification
279 : object:[OOJavaScriptEngine sharedEngine]];
280 : }
281 :
282 : return self;
283 : }
284 :
285 :
286 0 : - (void) dealloc
287 : {
288 : [[NSNotificationCenter defaultCenter] removeObserver:self
289 : name:kOOJavaScriptEngineWillResetNotification
290 : object:[OOJavaScriptEngine sharedEngine]];
291 :
292 : DESTROY(name);
293 : DESTROY(description);
294 : DESTROY(version);
295 : DESTROY(filePath);
296 :
297 : if (_jsSelf != NULL)
298 : {
299 : JSContext *context = OOJSAcquireContext();
300 :
301 : OOJSObjectWrapperFinalize(context, _jsSelf); // Release weakref to self
302 : JS_RemoveObjectRoot(context, &_jsSelf); // Unroot jsSelf
303 :
304 : OOJSRelinquishContext(context);
305 : }
306 :
307 : [weakSelf weakRefDrop];
308 :
309 : [super dealloc];
310 : }
311 :
312 :
313 0 : - (NSString *) oo_jsClassName
314 : {
315 : return @"Script";
316 : }
317 :
318 :
319 0 : - (NSString *)descriptionComponents
320 : {
321 : if (_jsSelf != NULL) return [super descriptionComponents];
322 : else return @"invalid script";
323 : }
324 :
325 :
326 0 : - (void) javaScriptEngineWillReset:(NSNotification *)notification
327 : {
328 : // All scripts become invalid when the JS engine resets.
329 : if (_jsSelf != NULL)
330 : {
331 : _jsSelf = NULL;
332 : JSContext *context = OOJSAcquireContext();
333 : JS_RemoveObjectRoot(context, &_jsSelf);
334 : OOJSRelinquishContext(context);
335 : }
336 : }
337 :
338 :
339 : + (OOJSScript *) currentlyRunningScript
340 : {
341 : if (sRunningStack == NULL) return NULL;
342 : return sRunningStack->current;
343 : }
344 :
345 :
346 : + (NSArray *) scriptStack
347 : {
348 : NSMutableArray *result = nil;
349 :
350 : result = [NSMutableArray array];
351 : AddStackToArrayReversed(result, sRunningStack);
352 : return result;
353 : }
354 :
355 :
356 0 : - (id) weakRetain
357 : {
358 : if (weakSelf == nil) weakSelf = [OOWeakReference weakRefWithObject:self];
359 : return [weakSelf retain];
360 : }
361 :
362 :
363 0 : - (void) weakRefDied:(OOWeakReference *)weakRef
364 : {
365 : if (weakRef == weakSelf) weakSelf = nil;
366 : }
367 :
368 :
369 : - (NSString *) name
370 : {
371 : if (name == nil) name = [[self propertyNamed:@"name"] copy];
372 : if (name == nil) return [self scriptNameFromPath:filePath]; // Special case for parse errors during load.
373 : return name;
374 : }
375 :
376 :
377 0 : - (NSString *) scriptDescription
378 : {
379 : return description;
380 : }
381 :
382 :
383 : - (NSString *) version
384 : {
385 : return version;
386 : }
387 :
388 :
389 0 : - (void)runWithTarget:(Entity *)target
390 : {
391 :
392 : }
393 :
394 :
395 : - (BOOL) callMethod:(jsid)methodID
396 : inContext:(JSContext *)context
397 : withArguments:(jsval *)argv count:(intN)argc
398 : result:(jsval *)outResult
399 : {
400 : NSParameterAssert(name != NULL && (argv != NULL || argc == 0) && context != NULL && JS_IsInRequest(context));
401 : if (_jsSelf == NULL) return NO;
402 :
403 : JSObject *root = NULL;
404 : BOOL OK = NO;
405 : jsval method;
406 : jsval ignoredResult = JSVAL_VOID;
407 :
408 : if (outResult == NULL) outResult = &ignoredResult;
409 : OOJSAddGCObjectRoot(context, &root, "OOJSScript method root");
410 :
411 : if (EXPECT(JS_GetMethodById(context, _jsSelf, methodID, &root, &method) && !JSVAL_IS_VOID(method)))
412 : {
413 : #ifndef NDEBUG
414 : if (JS_IsExceptionPending(context))
415 : {
416 : OOLog(@"script.internalBug", @"Exception pending on context before calling method in %s, clearing. This is an internal error, please report it.", __PRETTY_FUNCTION__);
417 : JS_ClearPendingException(context);
418 : }
419 :
420 : OOLog(@"script.javaScript.call", @"Calling [%@].%@()", [self name], OOStringFromJSID(methodID));
421 : OOLogIndentIf(@"script.javaScript.call");
422 : #endif
423 :
424 : // Push self on stack of running scripts.
425 : RunningStack stackElement =
426 : {
427 : .back = sRunningStack,
428 : .current = self
429 : };
430 : sRunningStack = &stackElement;
431 :
432 : // Call the method.
433 : OOJSStartTimeLimiter();
434 : OK = JS_CallFunctionValue(context, _jsSelf, method, argc, argv, outResult);
435 : OOJSStopTimeLimiter();
436 :
437 : if (JS_IsExceptionPending(context))
438 : {
439 : JS_ReportPendingException(context);
440 : OK = NO;
441 : }
442 :
443 : // Pop running scripts stack
444 : sRunningStack = stackElement.back;
445 :
446 : #ifndef NDEBUG
447 : OOLogOutdentIf(@"script.javaScript.call");
448 : #endif
449 : }
450 :
451 : JS_RemoveObjectRoot(context, &root);
452 :
453 : return OK;
454 : }
455 :
456 :
457 : - (id) propertyWithID:(jsid)propID inContext:(JSContext *)context
458 : {
459 : NSParameterAssert(context != NULL && JS_IsInRequest(context));
460 : if (_jsSelf == NULL) return nil;
461 :
462 : jsval jsValue = JSVAL_VOID;
463 : if (JS_GetPropertyById(context, _jsSelf, propID, &jsValue))
464 : {
465 : return OOJSNativeObjectFromJSValue(context, jsValue);
466 : }
467 : return nil;
468 : }
469 :
470 :
471 : - (BOOL) setProperty:(id)value withID:(jsid)propID inContext:(JSContext *)context
472 : {
473 : NSParameterAssert(context != NULL && JS_IsInRequest(context));
474 : if (_jsSelf == NULL) return NO;
475 :
476 : jsval jsValue = OOJSValueFromNativeObject(context, value);
477 : return JS_SetPropertyById(context, _jsSelf, propID, &jsValue);
478 : }
479 :
480 :
481 : - (BOOL) defineProperty:(id)value withID:(jsid)propID inContext:(JSContext *)context
482 : {
483 : NSParameterAssert(context != NULL && JS_IsInRequest(context));
484 : if (_jsSelf == NULL) return NO;
485 :
486 : jsval jsValue = OOJSValueFromNativeObject(context, value);
487 : return JS_DefinePropertyById(context, _jsSelf, propID, jsValue, NULL, NULL, OOJS_PROP_READONLY);
488 : }
489 :
490 :
491 : - (id) propertyNamed:(NSString *)propName
492 : {
493 : if (propName == nil) return nil;
494 : if (_jsSelf == NULL) return nil;
495 :
496 : JSContext *context = OOJSAcquireContext();
497 : id result = [self propertyWithID:OOJSIDFromString(propName) inContext:context];
498 : OOJSRelinquishContext(context);
499 :
500 : return result;
501 : }
502 :
503 :
504 : - (BOOL) setProperty:(id)value named:(NSString *)propName
505 : {
506 : if (value == nil || propName == nil) return NO;
507 : if (_jsSelf == NULL) return NO;
508 :
509 : JSContext *context = OOJSAcquireContext();
510 : BOOL result = [self setProperty:value withID:OOJSIDFromString(propName) inContext:context];
511 : OOJSRelinquishContext(context);
512 :
513 : return result;
514 : }
515 :
516 :
517 : - (BOOL) defineProperty:(id)value named:(NSString *)propName
518 : {
519 : if (value == nil || propName == nil) return NO;
520 : if (_jsSelf == NULL) return NO;
521 :
522 : JSContext *context = OOJSAcquireContext();
523 : BOOL result = [self defineProperty:value withID:OOJSIDFromString(propName) inContext:context];
524 : OOJSRelinquishContext(context);
525 :
526 : return result;
527 : }
528 :
529 :
530 0 : - (jsval)oo_jsValueInContext:(JSContext *)context
531 : {
532 : if (_jsSelf == NULL) return JSVAL_VOID;
533 : return OBJECT_TO_JSVAL(_jsSelf);
534 : }
535 :
536 :
537 : + (void)pushScript:(OOJSScript *)script
538 : {
539 : RunningStack *element = NULL;
540 :
541 : element = malloc(sizeof *element);
542 : if (element == NULL) exit(EXIT_FAILURE);
543 :
544 : element->back = sRunningStack;
545 : element->current = script;
546 : sRunningStack = element;
547 : }
548 :
549 :
550 : + (void)popScript:(OOJSScript *)script
551 : {
552 : RunningStack *element = NULL;
553 :
554 : assert(sRunningStack->current == script);
555 :
556 : element = sRunningStack;
557 : sRunningStack = sRunningStack->back;
558 : free(element);
559 : }
560 :
561 : @end
562 :
563 :
564 : @implementation OOJSScript (OOPrivate)
565 :
566 :
567 :
568 : /* Generate default name for script which doesn't set its name property when
569 : first run.
570 :
571 : The generated name is <name>.anon-script, where <name> is selected as
572 : follows:
573 : * If path is nil (futureproofing), use the address of the script object.
574 : * If the file's name is something other than script.*, use the file name.
575 : * If the containing directory is something other than Config, use the
576 : containing directory's name.
577 : * Otherwise, use the containing directory's parent (which will generally
578 : be an OXP root directory).
579 : * If either of the two previous steps results in an empty string, fall
580 : back on the full path.
581 : */
582 : - (NSString *)scriptNameFromPath:(NSString *)path
583 : {
584 : NSString *lastComponent = nil;
585 : NSString *truncatedPath = nil;
586 : NSString *theName = nil;
587 :
588 : if (path == nil) theName = [NSString stringWithFormat:@"%p", self];
589 : else
590 : {
591 : lastComponent = [path lastPathComponent];
592 : if (![lastComponent hasPrefix:@"script."]) theName = lastComponent;
593 : else
594 : {
595 : truncatedPath = [path stringByDeletingLastPathComponent];
596 : if (NSOrderedSame == [[truncatedPath lastPathComponent] caseInsensitiveCompare:@"Config"])
597 : {
598 : truncatedPath = [truncatedPath stringByDeletingLastPathComponent];
599 : }
600 : if (NSOrderedSame == [[truncatedPath pathExtension] caseInsensitiveCompare:@"oxp"])
601 : {
602 : truncatedPath = [truncatedPath stringByDeletingPathExtension];
603 : }
604 :
605 : lastComponent = [truncatedPath lastPathComponent];
606 : theName = lastComponent;
607 : }
608 : }
609 :
610 : if (0 == [theName length]) theName = path;
611 :
612 : return StrippedName([theName stringByAppendingString:@".anon-script"]);
613 : }
614 :
615 :
616 : - (NSDictionary *) defaultPropertiesFromPath:(NSString *)path
617 : {
618 : // remove file name, remove OXP subfolder, add manifest.plist
619 : NSString *manifestPath = [[[path stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"manifest.plist"];
620 : NSDictionary *manifest = OODictionaryFromFile(manifestPath);
621 : NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:3];
622 : /* __oolite.tmp.* is allocated for OXPs without manifests. Its
623 : * values are meaningless and shouldn't be used here */
624 : if (manifest != nil && ![[manifest oo_stringForKey:kOOManifestIdentifier] hasPrefix:@"__oolite.tmp."])
625 : {
626 : if ([manifest objectForKey:kOOManifestVersion] != nil)
627 : {
628 : [properties setObject:[manifest oo_stringForKey:kOOManifestVersion] forKey:@"version"];
629 : }
630 : if ([manifest objectForKey:kOOManifestIdentifier] != nil)
631 : {
632 : // used for system info
633 : [properties setObject:[manifest oo_stringForKey:kOOManifestIdentifier] forKey:kLocalManifestProperty];
634 : }
635 : if ([manifest objectForKey:kOOManifestAuthor] != nil)
636 : {
637 : [properties setObject:[manifest oo_stringForKey:kOOManifestAuthor] forKey:@"author"];
638 : }
639 : if ([manifest objectForKey:kOOManifestLicense] != nil)
640 : {
641 : [properties setObject:[manifest oo_stringForKey:kOOManifestLicense] forKey:@"license"];
642 : }
643 : }
644 : return properties;
645 : }
646 :
647 : @end
648 :
649 :
650 : @implementation OOScript (JavaScriptEvents)
651 :
652 : - (BOOL) callMethod:(jsid)methodID
653 : inContext:(JSContext *)context
654 : withArguments:(jsval *)argv count:(intN)argc
655 : result:(jsval *)outResult
656 : {
657 : return NO;
658 : }
659 :
660 : @end
661 :
662 :
663 0 : void InitOOJSScript(JSContext *context, JSObject *global)
664 : {
665 : sScriptPrototype = JS_InitClass(context, global, NULL, &sScriptClass, OOJSUnconstructableConstruct, 0, NULL, sScriptMethods, NULL, NULL);
666 : OOJSRegisterObjectConverter(&sScriptClass, OOJSBasicPrivateObjectConverter);
667 : }
668 :
669 :
670 0 : static JSBool ScriptAddProperty(JSContext *context, JSObject *this, jsid propID, jsval *value)
671 : {
672 : // Complain about attempts to set the property tickle.
673 : if (JSID_IS_STRING(propID))
674 : {
675 : JSString *propName = JSID_TO_STRING(propID);
676 : JSBool match;
677 : if (JS_StringEqualsAscii(context, propName, "tickle", &match) && match)
678 : {
679 : OOJSScript *thisScript = OOJSNativeObjectOfClassFromJSObject(context, this, [OOJSScript class]);
680 : OOJSReportWarning(context, @"Script %@ appears to use the tickle() event handler, which is no longer supported.", [thisScript name]);
681 : }
682 : }
683 :
684 : return YES;
685 : }
686 :
687 :
688 0 : static void AddStackToArrayReversed(NSMutableArray *array, RunningStack *stack)
689 : {
690 : if (stack != NULL)
691 : {
692 : AddStackToArrayReversed(array, stack->back);
693 : [array addObject:stack->current];
694 : }
695 : }
696 :
697 :
698 0 : static JSScript *LoadScriptWithName(JSContext *context, NSString *path, JSObject *object, JSObject **outScriptObject, NSString **outErrorMessage)
699 : {
700 : #if OO_CACHE_JS_SCRIPTS
701 : OOCacheManager *cache = nil;
702 : #endif
703 : NSString *fileContents = nil;
704 : NSData *data = nil;
705 : JSScript *script = NULL;
706 :
707 : NSCParameterAssert(outScriptObject != NULL && outErrorMessage != NULL);
708 : *outErrorMessage = nil;
709 :
710 : #if OO_CACHE_JS_SCRIPTS
711 : // Look for cached compiled script
712 : cache = [OOCacheManager sharedCache];
713 : data = [cache objectForKey:path inCache:@"compiled JavaScript scripts"];
714 : if (data != nil)
715 : {
716 : script = ScriptWithCompiledData(context, data);
717 : }
718 : #endif
719 :
720 : if (script == NULL)
721 : {
722 : fileContents = [NSString stringWithContentsOfUnicodeFile:path];
723 :
724 : if (fileContents != nil)
725 : {
726 : #ifndef NDEBUG
727 : /* FIXME: this isn't strictly the right test, since strict
728 : * mode can be enabled with this string within a function
729 : * definition, but it seems unlikely anyone is actually doing
730 : * that here. */
731 : if ([fileContents rangeOfString:@"\"use strict\";"].location == NSNotFound && [fileContents rangeOfString:@"'use strict';"].location == NSNotFound)
732 : {
733 : OOStandardsDeprecated([NSString stringWithFormat:@"Script %@ does not \"use strict\";",path]);
734 : if (OOEnforceStandards())
735 : {
736 : // prepend it anyway
737 : // TODO: some time after 1.82, make this required
738 : fileContents = [@"\"use strict\";\n" stringByAppendingString:fileContents];
739 : }
740 : }
741 : #endif
742 : data = [fileContents utf16DataWithBOM:NO];
743 : }
744 : if (data == nil) *outErrorMessage = @"could not load file";
745 : else
746 : {
747 : script = JS_CompileUCScript(context, object, [data bytes], [data length] / sizeof(unichar), [path UTF8String], 1);
748 : if (script != NULL) *outScriptObject = JS_NewScriptObject(context, script);
749 : else *outErrorMessage = @"compilation failed";
750 : }
751 :
752 : #if OO_CACHE_JS_SCRIPTS
753 : if (script != NULL)
754 : {
755 : // Write compiled script to cache
756 : data = CompiledScriptData(context, script);
757 : [cache setObject:data forKey:path inCache:@"compiled JavaScript scripts"];
758 : }
759 : #endif
760 : }
761 :
762 : return script;
763 : }
764 :
765 :
766 : #if OO_CACHE_JS_SCRIPTS
767 0 : static NSData *CompiledScriptData(JSContext *context, JSScript *script)
768 : {
769 : JSXDRState *xdr = NULL;
770 : NSData *result = nil;
771 : uint32 length;
772 : void *bytes = NULL;
773 :
774 : xdr = JS_XDRNewMem(context, JSXDR_ENCODE);
775 : if (xdr != NULL)
776 : {
777 : if (JS_XDRScript(xdr, &script))
778 : {
779 : bytes = JS_XDRMemGetData(xdr, &length);
780 : if (bytes != NULL)
781 : {
782 : result = [NSData dataWithBytes:bytes length:length];
783 : }
784 : }
785 : JS_XDRDestroy(xdr);
786 : }
787 :
788 : return result;
789 : }
790 :
791 :
792 0 : static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data)
793 : {
794 : JSXDRState *xdr = NULL;
795 : JSScript *result = NULL;
796 :
797 : if (data == nil) return NULL;
798 :
799 : xdr = JS_XDRNewMem(context, JSXDR_DECODE);
800 : if (xdr != NULL)
801 : {
802 : NSUInteger length = [data length];
803 : if (EXPECT_NOT(length > UINT32_MAX)) return NULL;
804 :
805 : JS_XDRMemSetData(xdr, (void *)[data bytes], (uint32_t)length);
806 : if (!JS_XDRScript(xdr, &result)) result = NULL;
807 :
808 : JS_XDRMemSetData(xdr, NULL, 0); // Don't let it be freed by XDRDestroy
809 : JS_XDRDestroy(xdr);
810 : }
811 :
812 : return result;
813 : }
814 : #endif
815 :
816 :
817 0 : static NSString *StrippedName(NSString *string)
818 : {
819 : static NSCharacterSet *invalidSet = nil;
820 : if (invalidSet == nil) invalidSet = [[NSCharacterSet characterSetWithCharactersInString:@"_ \t\n\r\v"] retain];
821 :
822 : return [string stringByTrimmingCharactersInSet:invalidSet];
823 : }
|