1/*
2 * Copyright (C) 2013 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 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 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#include "config.h"
27
28#import "JavaScriptCore.h"
29
30#if JSC_OBJC_API_ENABLED
31
32#import "APICast.h"
33#import "JSManagedValueInternal.h"
34#import "JSVirtualMachine.h"
35#import "JSVirtualMachineInternal.h"
36#import "JSWrapperMap.h"
37#import "SlotVisitorInlines.h"
38#import <mutex>
39#import <wtf/NeverDestroyed.h>
40
41static NSMapTable *globalWrapperCache = 0;
42
43static std::mutex& wrapperCacheMutex()
44{
45    static NeverDestroyed<std::mutex> mutex;
46
47    return mutex;
48}
49
50static void initWrapperCache()
51{
52    ASSERT(!globalWrapperCache);
53    NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
54    NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
55    globalWrapperCache = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
56}
57
58static NSMapTable *wrapperCache()
59{
60    if (!globalWrapperCache)
61        initWrapperCache();
62    return globalWrapperCache;
63}
64
65@interface JSVMWrapperCache : NSObject
66+ (void)addWrapper:(JSVirtualMachine *)wrapper forJSContextGroupRef:(JSContextGroupRef)group;
67+ (JSVirtualMachine *)wrapperForJSContextGroupRef:(JSContextGroupRef)group;
68@end
69
70@implementation JSVMWrapperCache
71
72+ (void)addWrapper:(JSVirtualMachine *)wrapper forJSContextGroupRef:(JSContextGroupRef)group
73{
74    std::lock_guard<std::mutex> lock(wrapperCacheMutex());
75    NSMapInsert(wrapperCache(), group, wrapper);
76}
77
78+ (JSVirtualMachine *)wrapperForJSContextGroupRef:(JSContextGroupRef)group
79{
80    std::lock_guard<std::mutex> lock(wrapperCacheMutex());
81    return static_cast<JSVirtualMachine *>(NSMapGet(wrapperCache(), group));
82}
83
84@end
85
86@implementation JSVirtualMachine {
87    JSContextGroupRef m_group;
88    NSMapTable *m_contextCache;
89    NSMapTable *m_externalObjectGraph;
90    NSMapTable *m_externalRememberedSet;
91}
92
93- (instancetype)init
94{
95    JSContextGroupRef group = JSContextGroupCreate();
96    self = [self initWithContextGroupRef:group];
97    // The extra JSContextGroupRetain is balanced here.
98    JSContextGroupRelease(group);
99    return self;
100}
101
102- (instancetype)initWithContextGroupRef:(JSContextGroupRef)group
103{
104    self = [super init];
105    if (!self)
106        return nil;
107
108    m_group = JSContextGroupRetain(group);
109
110    NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
111    NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
112    m_contextCache = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
113
114    NSPointerFunctionsOptions weakIDOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
115    NSPointerFunctionsOptions strongIDOptions = NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality;
116    m_externalObjectGraph = [[NSMapTable alloc] initWithKeyOptions:weakIDOptions valueOptions:strongIDOptions capacity:0];
117
118    NSPointerFunctionsOptions integerOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsIntegerPersonality;
119    m_externalRememberedSet = [[NSMapTable alloc] initWithKeyOptions:weakIDOptions valueOptions:integerOptions capacity:0];
120
121    [JSVMWrapperCache addWrapper:self forJSContextGroupRef:group];
122
123    return self;
124}
125
126- (void)dealloc
127{
128    JSContextGroupRelease(m_group);
129    [m_contextCache release];
130    [m_externalObjectGraph release];
131    [m_externalRememberedSet release];
132    [super dealloc];
133}
134
135static id getInternalObjcObject(id object)
136{
137    if ([object isKindOfClass:[JSManagedValue class]]) {
138        JSValue* value = [static_cast<JSManagedValue *>(object) value];
139        id temp = tryUnwrapObjcObject([value.context JSGlobalContextRef], [value JSValueRef]);
140        if (temp)
141            return temp;
142        return object;
143    }
144
145    if ([object isKindOfClass:[JSValue class]]) {
146        JSValue *value = static_cast<JSValue *>(object);
147        object = tryUnwrapObjcObject([value.context JSGlobalContextRef], [value JSValueRef]);
148    }
149
150    return object;
151}
152
153- (bool)isOldExternalObject:(id)object
154{
155    JSC::VM* vm = toJS(m_group);
156    return vm->heap.slotVisitor().containsOpaqueRoot(object);
157}
158
159- (void)addExternalRememberedObject:(id)object
160{
161    ASSERT([self isOldExternalObject:object]);
162    [m_externalRememberedSet setObject:[NSNumber numberWithBool:true] forKey:object];
163}
164
165- (void)addManagedReference:(id)object withOwner:(id)owner
166{
167    if ([object isKindOfClass:[JSManagedValue class]])
168        [object didAddOwner:owner];
169
170    object = getInternalObjcObject(object);
171    owner = getInternalObjcObject(owner);
172
173    if (!object || !owner)
174        return;
175
176    JSC::JSLockHolder locker(toJS(m_group));
177    if ([self isOldExternalObject:owner] && ![self isOldExternalObject:object])
178        [self addExternalRememberedObject:owner];
179
180    NSMapTable *ownedObjects = [m_externalObjectGraph objectForKey:owner];
181    if (!ownedObjects) {
182        NSPointerFunctionsOptions weakIDOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
183        NSPointerFunctionsOptions integerOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsIntegerPersonality;
184        ownedObjects = [[NSMapTable alloc] initWithKeyOptions:weakIDOptions valueOptions:integerOptions capacity:1];
185
186        [m_externalObjectGraph setObject:ownedObjects forKey:owner];
187        [ownedObjects release];
188    }
189
190    size_t count = reinterpret_cast<size_t>(NSMapGet(ownedObjects, object));
191    NSMapInsert(ownedObjects, object, reinterpret_cast<void*>(count + 1));
192}
193
194- (void)removeManagedReference:(id)object withOwner:(id)owner
195{
196    if ([object isKindOfClass:[JSManagedValue class]])
197        [object didRemoveOwner:owner];
198
199    object = getInternalObjcObject(object);
200    owner = getInternalObjcObject(owner);
201
202    if (!object || !owner)
203        return;
204
205    JSC::JSLockHolder locker(toJS(m_group));
206
207    NSMapTable *ownedObjects = [m_externalObjectGraph objectForKey:owner];
208    if (!ownedObjects)
209        return;
210
211    size_t count = reinterpret_cast<size_t>(NSMapGet(ownedObjects, object));
212    if (count > 1) {
213        NSMapInsert(ownedObjects, object, reinterpret_cast<void*>(count - 1));
214        return;
215    }
216
217    if (count == 1)
218        NSMapRemove(ownedObjects, object);
219
220    if (![ownedObjects count]) {
221        [m_externalObjectGraph removeObjectForKey:owner];
222        [m_externalRememberedSet removeObjectForKey:owner];
223    }
224}
225
226@end
227
228@implementation JSVirtualMachine(Internal)
229
230JSContextGroupRef getGroupFromVirtualMachine(JSVirtualMachine *virtualMachine)
231{
232    return virtualMachine->m_group;
233}
234
235+ (JSVirtualMachine *)virtualMachineWithContextGroupRef:(JSContextGroupRef)group
236{
237    JSVirtualMachine *virtualMachine = [JSVMWrapperCache wrapperForJSContextGroupRef:group];
238    if (!virtualMachine)
239        virtualMachine = [[[JSVirtualMachine alloc] initWithContextGroupRef:group] autorelease];
240    return virtualMachine;
241}
242
243- (JSContext *)contextForGlobalContextRef:(JSGlobalContextRef)globalContext
244{
245    return static_cast<JSContext *>(NSMapGet(m_contextCache, globalContext));
246}
247
248- (void)addContext:(JSContext *)wrapper forGlobalContextRef:(JSGlobalContextRef)globalContext
249{
250    NSMapInsert(m_contextCache, globalContext, wrapper);
251}
252
253- (NSMapTable *)externalObjectGraph
254{
255    return m_externalObjectGraph;
256}
257
258- (NSMapTable *)externalRememberedSet
259{
260    return m_externalRememberedSet;
261}
262
263@end
264
265void scanExternalObjectGraph(JSC::VM& vm, JSC::SlotVisitor& visitor, void* root)
266{
267    @autoreleasepool {
268        JSVirtualMachine *virtualMachine = [JSVMWrapperCache wrapperForJSContextGroupRef:toRef(&vm)];
269        if (!virtualMachine)
270            return;
271        NSMapTable *externalObjectGraph = [virtualMachine externalObjectGraph];
272        Vector<void*> stack;
273        stack.append(root);
274        while (!stack.isEmpty()) {
275            void* nextRoot = stack.last();
276            stack.removeLast();
277            if (visitor.containsOpaqueRootTriState(nextRoot) == TrueTriState)
278                continue;
279            visitor.addOpaqueRoot(nextRoot);
280
281            NSMapTable *ownedObjects = [externalObjectGraph objectForKey:static_cast<id>(nextRoot)];
282            for (id ownedObject in ownedObjects)
283                stack.append(static_cast<void*>(ownedObject));
284        }
285    }
286}
287
288void scanExternalRememberedSet(JSC::VM& vm, JSC::SlotVisitor& visitor)
289{
290    @autoreleasepool {
291        JSVirtualMachine *virtualMachine = [JSVMWrapperCache wrapperForJSContextGroupRef:toRef(&vm)];
292        if (!virtualMachine)
293            return;
294        NSMapTable *externalObjectGraph = [virtualMachine externalObjectGraph];
295        NSMapTable *externalRememberedSet = [virtualMachine externalRememberedSet];
296        for (id key in externalRememberedSet) {
297            NSMapTable *ownedObjects = [externalObjectGraph objectForKey:key];
298            for (id ownedObject in ownedObjects)
299                scanExternalObjectGraph(vm, visitor, ownedObject);
300        }
301        [externalRememberedSet removeAllObjects];
302    }
303}
304
305#endif // JSC_OBJC_API_ENABLED
306