source: webkit/trunk/Source/WebCore/bindings/objc/WebScriptObject.mm @ 136986

Last change on this file since 136986 was 136986, checked in by ggaren@apple.com, 5 years ago

Crash in JSC::Bindings::RootObject::globalObject() sync'ing notes in Evernote
https://bugs.webkit.org/show_bug.cgi?id=104321
<rdar://problem/12770497>

Reviewed by Sam Weinig.

../JavaScriptCore:

Work around a JSValueUnprotect(NULL) in Evernote.

  • API/JSValueRef.cpp:

(evernoteHackNeeded):
(JSValueUnprotect):

../WebCore:

Missed a null check.

  • bindings/objc/WebScriptObject.mm:

(-[WebScriptObject JSObject]): If our root object has been cleared, don't
try to dereference it. This happens in Evernote during tear-down.

This matches the behavior of other methods in the same class.

(_isSafeScript returns false if the root object has been cleared.)

If we believe _isSafeScript is a good idea, it's probably the right test
to use here (as opposed to just null-checking _rootObject) because this API
gives the client unlimited access to the underlying JavaScript object.

  • Property svn:eol-style set to native
File size: 18.4 KB
Line 
1/*
2 * Copyright (C) 2004, 2006, 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WebScriptObjectPrivate.h"
28
29#import "BindingSecurity.h"
30#import "BridgeJSC.h"
31#import "Console.h"
32#import "DOMInternal.h"
33#import "DOMWindow.h"
34#import "Frame.h"
35#import "JSDOMWindow.h"
36#import "JSDOMWindowCustom.h"
37#import "JSHTMLElement.h"
38#import "JSMainThreadExecState.h"
39#import "JSPluginElementFunctions.h"
40#import "ObjCRuntimeObject.h"
41#import "WebCoreObjCExtras.h"
42#import "objc_instance.h"
43#import "runtime_object.h"
44#import "runtime_root.h"
45#import <JavaScriptCore/APICast.h>
46#import <interpreter/CallFrame.h>
47#import <runtime/InitializeThreading.h>
48#import <runtime/JSGlobalObject.h>
49#import <runtime/JSLock.h>
50#import <runtime/Completion.h>
51#import <runtime/Completion.h>
52#import <wtf/TCSpinLock.h>
53#import <wtf/Threading.h>
54#include <wtf/text/WTFString.h>
55
56using namespace JSC;
57using namespace JSC::Bindings;
58using namespace WebCore;
59
60namespace WebCore {
61
62static NSMapTable* JSWrapperCache;
63static SpinLock spinLock = SPINLOCK_INITIALIZER;
64
65NSObject* getJSWrapper(JSObject* impl)
66{
67    ASSERT(isMainThread());
68    SpinLockHolder holder(&spinLock);
69
70    if (!JSWrapperCache)
71        return nil;
72    NSObject* wrapper = static_cast<NSObject*>(NSMapGet(JSWrapperCache, impl));
73    return wrapper ? [[wrapper retain] autorelease] : nil;
74}
75
76void addJSWrapper(NSObject* wrapper, JSObject* impl)
77{
78    ASSERT(isMainThread());
79    SpinLockHolder holder(&spinLock);
80
81    if (!JSWrapperCache)
82        JSWrapperCache = createWrapperCache();
83    NSMapInsert(JSWrapperCache, impl, wrapper);
84}
85
86void removeJSWrapper(JSObject* impl)
87{
88    SpinLockHolder holder(&spinLock);
89
90    if (!JSWrapperCache)
91        return;
92    NSMapRemove(JSWrapperCache, impl);
93}
94
95static void removeJSWrapperIfRetainCountOne(NSObject* wrapper, JSObject* impl)
96{
97    SpinLockHolder holder(&spinLock);
98
99    if (!JSWrapperCache)
100        return;
101    if ([wrapper retainCount] == 1)
102        NSMapRemove(JSWrapperCache, impl);
103}
104
105id createJSWrapper(JSC::JSObject* object, PassRefPtr<JSC::Bindings::RootObject> origin, PassRefPtr<JSC::Bindings::RootObject> root)
106{
107    if (id wrapper = getJSWrapper(object))
108        return wrapper;
109    return [[[WebScriptObject alloc] _initWithJSObject:object originRootObject:origin rootObject:root] autorelease];
110}
111
112static void addExceptionToConsole(ExecState* exec)
113{
114    JSDOMWindow* window = asJSDOMWindow(exec->dynamicGlobalObject());
115    if (!window || !exec->hadException())
116        return;
117    reportCurrentException(exec);
118}
119
120} // namespace WebCore
121
122@implementation WebScriptObjectPrivate
123
124@end
125
126@implementation WebScriptObject
127
128+ (void)initialize
129{
130    JSC::initializeThreading();
131    WTF::initializeMainThreadToProcessMainThread();
132    WebCoreObjCFinalizeOnMainThread(self);
133}
134
135+ (id)scriptObjectForJSObject:(JSObjectRef)jsObject originRootObject:(RootObject*)originRootObject rootObject:(RootObject*)rootObject
136{
137    if (id domWrapper = createDOMWrapper(toJS(jsObject), originRootObject, rootObject))
138        return domWrapper;
139   
140    return WebCore::createJSWrapper(toJS(jsObject), originRootObject, rootObject);
141}
142
143- (void)_setImp:(JSObject*)imp originRootObject:(PassRefPtr<RootObject>)originRootObject rootObject:(PassRefPtr<RootObject>)rootObject
144{
145    // This function should only be called once, as a (possibly lazy) initializer.
146    ASSERT(!_private->imp);
147    ASSERT(!_private->rootObject);
148    ASSERT(!_private->originRootObject);
149    ASSERT(imp);
150
151    _private->imp = imp;
152    _private->rootObject = rootObject.leakRef();
153    _private->originRootObject = originRootObject.leakRef();
154
155    WebCore::addJSWrapper(self, imp);
156
157    if (_private->rootObject)
158        _private->rootObject->gcProtect(imp);
159}
160
161- (void)_setOriginRootObject:(PassRefPtr<RootObject>)originRootObject andRootObject:(PassRefPtr<RootObject>)rootObject
162{
163    ASSERT(_private->imp);
164
165    if (rootObject)
166        rootObject->gcProtect(_private->imp);
167
168    if (_private->rootObject && _private->rootObject->isValid())
169        _private->rootObject->gcUnprotect(_private->imp);
170
171    if (_private->rootObject)
172        _private->rootObject->deref();
173
174    if (_private->originRootObject)
175        _private->originRootObject->deref();
176
177    _private->rootObject = rootObject.leakRef();
178    _private->originRootObject = originRootObject.leakRef();
179}
180
181- (id)_initWithJSObject:(JSC::JSObject*)imp originRootObject:(PassRefPtr<JSC::Bindings::RootObject>)originRootObject rootObject:(PassRefPtr<JSC::Bindings::RootObject>)rootObject
182{
183    ASSERT(imp);
184
185    self = [super init];
186    _private = [[WebScriptObjectPrivate alloc] init];
187    [self _setImp:imp originRootObject:originRootObject rootObject:rootObject];
188   
189    return self;
190}
191
192- (JSObject*)_imp
193{
194    // Associate the WebScriptObject with the JS wrapper for the ObjC DOM wrapper.
195    // This is done on lazily, on demand.
196    if (!_private->imp && _private->isCreatedByDOMWrapper)
197        [self _initializeScriptDOMNodeImp];
198    return [self _rootObject] ? _private->imp : 0;
199}
200
201- (BOOL)_hasImp
202{
203    return _private->imp != nil;
204}
205
206// Node that DOMNode overrides this method. So you should almost always
207// use this method call instead of _private->rootObject directly.
208- (RootObject*)_rootObject
209{
210    return _private->rootObject && _private->rootObject->isValid() ? _private->rootObject : 0;
211}
212
213- (RootObject *)_originRootObject
214{
215    return _private->originRootObject && _private->originRootObject->isValid() ? _private->originRootObject : 0;
216}
217
218- (BOOL)_isSafeScript
219{
220    RootObject *root = [self _rootObject];
221    if (!root)
222        return false;
223
224    if (!_private->originRootObject)
225        return true;
226
227    if (!_private->originRootObject->isValid())
228        return false;
229
230    // It's not actually correct to call shouldAllowAccessToFrame in this way because
231    // JSDOMWindowBase* isn't the right object to represent the currently executing
232    // JavaScript. Instead, we should use ExecState, like we do elsewhere.
233    JSDOMWindowBase* target = jsCast<JSDOMWindowBase*>(root->globalObject());
234    return BindingSecurity::shouldAllowAccessToDOMWindow(_private->originRootObject->globalObject()->globalExec(), target->impl());
235}
236
237- (oneway void)release
238{
239    // If we're releasing the last reference to this object, remove if from the map.
240    if (_private->imp)
241        WebCore::removeJSWrapperIfRetainCountOne(self, _private->imp);
242
243    [super release];
244}
245
246- (void)dealloc
247{
248    if (WebCoreObjCScheduleDeallocateOnMainThread([WebScriptObject class], self))
249        return;
250
251    if (_private->rootObject && _private->rootObject->isValid())
252        _private->rootObject->gcUnprotect(_private->imp);
253
254    if (_private->rootObject)
255        _private->rootObject->deref();
256
257    if (_private->originRootObject)
258        _private->originRootObject->deref();
259
260    [_private release];
261
262    [super dealloc];
263}
264
265- (void)finalize
266{
267    if (_private->rootObject && _private->rootObject->isValid())
268        _private->rootObject->gcUnprotect(_private->imp);
269
270    if (_private->rootObject)
271        _private->rootObject->deref();
272
273    if (_private->originRootObject)
274        _private->originRootObject->deref();
275
276    [super finalize];
277}
278
279+ (BOOL)throwException:(NSString *)exceptionMessage
280{
281    ObjcInstance::setGlobalException(exceptionMessage);
282    return YES;
283}
284
285static void getListFromNSArray(ExecState *exec, NSArray *array, RootObject* rootObject, MarkedArgumentBuffer& aList)
286{
287    int i, numObjects = array ? [array count] : 0;
288   
289    for (i = 0; i < numObjects; i++) {
290        id anObject = [array objectAtIndex:i];
291        aList.append(convertObjcValueToValue(exec, &anObject, ObjcObjectType, rootObject));
292    }
293}
294
295- (id)callWebScriptMethod:(NSString *)name withArguments:(NSArray *)args
296{
297    if (![self _isSafeScript])
298        return nil;
299
300    // Look up the function object.
301    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
302    JSLockHolder lock(exec);
303    ASSERT(!exec->hadException());
304
305    JSValue function = [self _imp]->get(exec, Identifier(exec, String(name)));
306    CallData callData;
307    CallType callType = getCallData(function, callData);
308    if (callType == CallTypeNone)
309        return nil;
310
311    MarkedArgumentBuffer argList;
312    getListFromNSArray(exec, args, [self _rootObject], argList);
313
314    if (![self _isSafeScript])
315        return nil;
316
317    [self _rootObject]->globalObject()->globalData().timeoutChecker.start();
318    JSValue result = JSMainThreadExecState::call(exec, function, callType, callData, [self _imp], argList);
319    [self _rootObject]->globalObject()->globalData().timeoutChecker.stop();
320
321    if (exec->hadException()) {
322        addExceptionToConsole(exec);
323        result = jsUndefined();
324        exec->clearException();
325    }
326
327    // Convert and return the result of the function call.
328    id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
329
330    return resultObj;
331}
332
333- (id)evaluateWebScript:(NSString *)script
334{
335    if (![self _isSafeScript])
336        return nil;
337   
338    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
339    ASSERT(!exec->hadException());
340
341    JSLockHolder lock(exec);
342   
343    [self _rootObject]->globalObject()->globalData().timeoutChecker.start();
344    JSValue returnValue = JSMainThreadExecState::evaluate(exec, makeSource(String(script)), JSC::JSValue(), 0);
345    [self _rootObject]->globalObject()->globalData().timeoutChecker.stop();
346
347    id resultObj = [WebScriptObject _convertValueToObjcValue:returnValue originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
348   
349    return resultObj;
350}
351
352- (void)setValue:(id)value forKey:(NSString *)key
353{
354    if (![self _isSafeScript])
355        return;
356
357    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
358    ASSERT(!exec->hadException());
359
360    JSLockHolder lock(exec);
361
362    PutPropertySlot slot;
363    [self _imp]->methodTable()->put([self _imp], exec, Identifier(exec, String(key)), convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject]), slot);
364
365    if (exec->hadException()) {
366        addExceptionToConsole(exec);
367        exec->clearException();
368    }
369}
370
371- (id)valueForKey:(NSString *)key
372{
373    if (![self _isSafeScript])
374        return nil;
375
376    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
377    ASSERT(!exec->hadException());
378
379    id resultObj;
380    {
381        // Need to scope this lock to ensure that we release the lock before calling
382        // [super valueForKey:key] which might throw an exception and bypass the JSLock destructor,
383        // leaving the lock permanently held
384        JSLockHolder lock(exec);
385       
386        JSValue result = [self _imp]->get(exec, Identifier(exec, String(key)));
387       
388        if (exec->hadException()) {
389            addExceptionToConsole(exec);
390            result = jsUndefined();
391            exec->clearException();
392        }
393
394        resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
395    }
396   
397    if ([resultObj isKindOfClass:[WebUndefined class]])
398        resultObj = [super valueForKey:key];    // defaults to throwing an exception
399
400    return resultObj;
401}
402
403- (void)removeWebScriptKey:(NSString *)key
404{
405    if (![self _isSafeScript])
406        return;
407
408    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
409    ASSERT(!exec->hadException());
410
411    JSLockHolder lock(exec);
412    [self _imp]->methodTable()->deleteProperty([self _imp], exec, Identifier(exec, String(key)));
413
414    if (exec->hadException()) {
415        addExceptionToConsole(exec);
416        exec->clearException();
417    }
418}
419
420- (BOOL)hasWebScriptKey:(NSString *)key
421{
422    if (![self _isSafeScript])
423        return NO;
424
425    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
426    ASSERT(!exec->hadException());
427
428    JSLockHolder lock(exec);
429    BOOL result = [self _imp]->hasProperty(exec, Identifier(exec, String(key)));
430
431    if (exec->hadException()) {
432        addExceptionToConsole(exec);
433        exec->clearException();
434    }
435
436    return result;
437}
438
439- (NSString *)stringRepresentation
440{
441    if (![self _isSafeScript]) {
442        // This is a workaround for a gcc 3.3 internal compiler error.
443        return @"Undefined";
444    }
445
446    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
447    JSLockHolder lock(exec);
448   
449    id result = convertValueToObjcValue(exec, [self _imp], ObjcObjectType).objectValue;
450
451    NSString *description = [result description];
452
453    return description;
454}
455
456- (id)webScriptValueAtIndex:(unsigned)index
457{
458    if (![self _isSafeScript])
459        return nil;
460
461    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
462    ASSERT(!exec->hadException());
463
464    JSLockHolder lock(exec);
465    JSValue result = [self _imp]->get(exec, index);
466
467    if (exec->hadException()) {
468        addExceptionToConsole(exec);
469        result = jsUndefined();
470        exec->clearException();
471    }
472
473    id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
474
475    return resultObj;
476}
477
478- (void)setWebScriptValueAtIndex:(unsigned)index value:(id)value
479{
480    if (![self _isSafeScript])
481        return;
482
483    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
484    ASSERT(!exec->hadException());
485
486    JSLockHolder lock(exec);
487    [self _imp]->methodTable()->putByIndex([self _imp], exec, index, convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject]), false);
488
489    if (exec->hadException()) {
490        addExceptionToConsole(exec);
491        exec->clearException();
492    }
493}
494
495- (void)setException:(NSString *)description
496{
497    if (![self _rootObject])
498        return;
499    ObjcInstance::setGlobalException(description, [self _rootObject]->globalObject());
500}
501
502- (JSObjectRef)JSObject
503{
504    if (![self _isSafeScript])
505        return 0;
506    ExecState* exec = [self _rootObject]->globalObject()->globalExec();
507   
508    JSLockHolder lock(exec);
509    return toRef([self _imp]);
510}
511
512+ (id)_convertValueToObjcValue:(JSValue)value originRootObject:(RootObject*)originRootObject rootObject:(RootObject*)rootObject
513{
514    if (value.isObject()) {
515        JSObject* object = asObject(value);
516        JSLockHolder lock(rootObject->globalObject()->globalData());
517
518        if (object->inherits(&JSHTMLElement::s_info)) {
519            // Plugin elements cache the instance internally.
520            HTMLElement* el = jsCast<JSHTMLElement*>(object)->impl();
521            ObjcInstance* instance = static_cast<ObjcInstance*>(pluginInstance(el));
522            if (instance)
523                return instance->getObject();
524        } else if (object->inherits(&ObjCRuntimeObject::s_info)) {
525            ObjCRuntimeObject* runtimeObject = static_cast<ObjCRuntimeObject*>(object);
526            ObjcInstance* instance = runtimeObject->getInternalObjCInstance();
527            if (instance)
528                return instance->getObject();
529            return nil;
530        }
531
532        return [WebScriptObject scriptObjectForJSObject:toRef(object) originRootObject:originRootObject rootObject:rootObject];
533    }
534
535    if (value.isString()) {
536        ExecState* exec = rootObject->globalObject()->globalExec();
537        const String& u = asString(value)->value(exec);
538        return [NSString stringWithCharacters:u.characters() length:u.length()];
539    }
540
541    if (value.isNumber())
542        return [NSNumber numberWithDouble:value.asNumber()];
543
544    if (value.isBoolean())
545        return [NSNumber numberWithBool:value.asBoolean()];
546
547    if (value.isUndefined())
548        return [WebUndefined undefined];
549
550    // jsNull is not returned as NSNull because existing applications do not expect
551    // that return value. Return as nil for compatibility. <rdar://problem/4651318> <rdar://problem/4701626>
552    // Other types (e.g., UnspecifiedType) also return as nil.
553    return nil;
554}
555
556@end
557
558@interface WebScriptObject (WebKitCocoaBindings)
559
560- (id)objectAtIndex:(unsigned)index;
561
562@end
563
564@implementation WebScriptObject (WebKitCocoaBindings)
565
566#if 0
567
568// FIXME: We'd like to add this, but we can't do that until this issue is resolved:
569// http://bugs.webkit.org/show_bug.cgi?id=13129: presence of 'count' method on
570// WebScriptObject breaks Democracy player.
571
572- (unsigned)count
573{
574    id length = [self valueForKey:@"length"];
575    if (![length respondsToSelector:@selector(intValue)])
576        return 0;
577    return [length intValue];
578}
579
580#endif
581
582- (id)objectAtIndex:(unsigned)index
583{
584    return [self webScriptValueAtIndex:index];
585}
586
587@end
588
589@implementation WebUndefined
590
591+ (id)allocWithZone:(NSZone *)unusedZone
592{
593    UNUSED_PARAM(unusedZone);
594
595    static WebUndefined *sharedUndefined = 0;
596    if (!sharedUndefined)
597        sharedUndefined = [super allocWithZone:NULL];
598    return sharedUndefined;
599}
600
601- (NSString *)description
602{
603    return @"undefined";
604}
605
606- (id)initWithCoder:(NSCoder *)unusedCoder
607{
608    UNUSED_PARAM(unusedCoder);
609
610    return self;
611}
612
613- (void)encodeWithCoder:(NSCoder *)unusedCoder
614{
615    UNUSED_PARAM(unusedCoder);
616}
617
618- (id)copyWithZone:(NSZone *)unusedZone
619{
620    UNUSED_PARAM(unusedZone);
621
622    return self;
623}
624
625- (id)retain
626{
627    return self;
628}
629
630- (oneway void)release
631{
632}
633
634- (NSUInteger)retainCount
635{
636    return NSUIntegerMax;
637}
638
639- (id)autorelease
640{
641    return self;
642}
643
644- (void)dealloc
645{
646    return;
647    [super dealloc]; // make -Wdealloc-check happy
648}
649
650+ (WebUndefined *)undefined
651{
652    return [WebUndefined allocWithZone:NULL];
653}
654
655@end
Note: See TracBrowser for help on using the repository browser.