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