Objective-C API external object graphs don't handle generational collection properly
[WebKit-https.git] / Source / JavaScriptCore / API / JSVirtualMachine.mm
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
41 static NSMapTable *globalWrapperCache = 0;
42
43 static std::mutex& wrapperCacheMutex()
44 {
45     static NeverDestroyed<std::mutex> mutex;
46
47     return mutex;
48 }
49
50 static 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
58 static 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
135 static 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
230 JSContextGroupRef 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
265 void 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
288 void 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