1 | /* |
2 | * Copyright (C) 2013-2019 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 | #include "JSInjectedScriptHost.h" |
28 | |
29 | #include "ArrayIteratorPrototype.h" |
30 | #include "ArrayPrototype.h" |
31 | #include "BuiltinNames.h" |
32 | #include "Completion.h" |
33 | #include "DateInstance.h" |
34 | #include "DeferGC.h" |
35 | #include "DirectArguments.h" |
36 | #include "Error.h" |
37 | #include "FunctionPrototype.h" |
38 | #include "HeapAnalyzer.h" |
39 | #include "HeapIterationScope.h" |
40 | #include "HeapProfiler.h" |
41 | #include "InjectedScriptHost.h" |
42 | #include "IterationKind.h" |
43 | #include "IteratorOperations.h" |
44 | #include "IteratorPrototype.h" |
45 | #include "JSArray.h" |
46 | #include "JSBoundFunction.h" |
47 | #include "JSCInlines.h" |
48 | #include "JSFunction.h" |
49 | #include "JSGlobalObjectFunctions.h" |
50 | #include "JSInjectedScriptHostPrototype.h" |
51 | #include "JSLock.h" |
52 | #include "JSMap.h" |
53 | #include "JSPromise.h" |
54 | #include "JSPromisePrototype.h" |
55 | #include "JSSet.h" |
56 | #include "JSStringIterator.h" |
57 | #include "JSTypedArrays.h" |
58 | #include "JSWeakMap.h" |
59 | #include "JSWeakSet.h" |
60 | #include "JSWithScope.h" |
61 | #include "MapIteratorPrototype.h" |
62 | #include "MapPrototype.h" |
63 | #include "MarkedSpaceInlines.h" |
64 | #include "ObjectConstructor.h" |
65 | #include "ObjectPrototype.h" |
66 | #include "PreventCollectionScope.h" |
67 | #include "ProxyObject.h" |
68 | #include "RegExpObject.h" |
69 | #include "ScopedArguments.h" |
70 | #include "SetIteratorPrototype.h" |
71 | #include "SetPrototype.h" |
72 | #include "SourceCode.h" |
73 | #include "TypedArrayInlines.h" |
74 | #include <wtf/Function.h> |
75 | #include <wtf/HashFunctions.h> |
76 | #include <wtf/HashMap.h> |
77 | #include <wtf/HashSet.h> |
78 | #include <wtf/HashTraits.h> |
79 | #include <wtf/Lock.h> |
80 | #include <wtf/PrintStream.h> |
81 | #include <wtf/text/StringConcatenate.h> |
82 | |
83 | using namespace JSC; |
84 | |
85 | namespace Inspector { |
86 | |
87 | const ClassInfo JSInjectedScriptHost::s_info = { "InjectedScriptHost" , &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSInjectedScriptHost) }; |
88 | |
89 | JSInjectedScriptHost::JSInjectedScriptHost(VM& vm, Structure* structure, Ref<InjectedScriptHost>&& impl) |
90 | : JSDestructibleObject(vm, structure) |
91 | , m_wrapped(WTFMove(impl)) |
92 | { |
93 | } |
94 | |
95 | void JSInjectedScriptHost::finishCreation(VM& vm) |
96 | { |
97 | Base::finishCreation(vm); |
98 | ASSERT(inherits(vm, info())); |
99 | } |
100 | |
101 | JSObject* JSInjectedScriptHost::createPrototype(VM& vm, JSGlobalObject* globalObject) |
102 | { |
103 | return JSInjectedScriptHostPrototype::create(vm, globalObject, JSInjectedScriptHostPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); |
104 | } |
105 | |
106 | void JSInjectedScriptHost::destroy(JSC::JSCell* cell) |
107 | { |
108 | JSInjectedScriptHost* thisObject = static_cast<JSInjectedScriptHost*>(cell); |
109 | thisObject->JSInjectedScriptHost::~JSInjectedScriptHost(); |
110 | } |
111 | |
112 | JSValue JSInjectedScriptHost::evaluate(JSGlobalObject* globalObject) const |
113 | { |
114 | return globalObject->evalFunction(); |
115 | } |
116 | |
117 | JSValue JSInjectedScriptHost::savedResultAlias(JSGlobalObject* globalObject) const |
118 | { |
119 | auto savedResultAlias = impl().savedResultAlias(); |
120 | if (!savedResultAlias) |
121 | return jsUndefined(); |
122 | return jsString(globalObject->vm(), savedResultAlias.value()); |
123 | } |
124 | |
125 | JSValue JSInjectedScriptHost::evaluateWithScopeExtension(JSGlobalObject* globalObject, CallFrame* callFrame) |
126 | { |
127 | VM& vm = globalObject->vm(); |
128 | auto scope = DECLARE_THROW_SCOPE(vm); |
129 | |
130 | JSValue scriptValue = callFrame->argument(0); |
131 | if (!scriptValue.isString()) |
132 | return throwTypeError(globalObject, scope, "InjectedScriptHost.evaluateWithScopeExtension first argument must be a string."_s ); |
133 | |
134 | String program = asString(scriptValue)->value(globalObject); |
135 | RETURN_IF_EXCEPTION(scope, JSValue()); |
136 | |
137 | NakedPtr<Exception> exception; |
138 | JSObject* scopeExtension = callFrame->argument(1).getObject(); |
139 | JSValue result = JSC::evaluateWithScopeExtension(globalObject, makeSource(program, callFrame->callerSourceOrigin(vm)), scopeExtension, exception); |
140 | if (exception) |
141 | throwException(globalObject, scope, exception); |
142 | |
143 | return result; |
144 | } |
145 | |
146 | JSValue JSInjectedScriptHost::internalConstructorName(JSGlobalObject* globalObject, CallFrame* callFrame) |
147 | { |
148 | if (callFrame->argumentCount() < 1) |
149 | return jsUndefined(); |
150 | |
151 | VM& vm = globalObject->vm(); |
152 | JSObject* object = jsCast<JSObject*>(callFrame->uncheckedArgument(0).toThis(globalObject, NotStrictMode)); |
153 | return jsString(vm, JSObject::calculatedClassName(object)); |
154 | } |
155 | |
156 | JSValue JSInjectedScriptHost::isHTMLAllCollection(JSGlobalObject* globalObject, CallFrame* callFrame) |
157 | { |
158 | if (callFrame->argumentCount() < 1) |
159 | return jsUndefined(); |
160 | |
161 | VM& vm = globalObject->vm(); |
162 | JSValue value = callFrame->uncheckedArgument(0); |
163 | return jsBoolean(impl().isHTMLAllCollection(vm, value)); |
164 | } |
165 | |
166 | JSValue JSInjectedScriptHost::isPromiseRejectedWithNativeGetterTypeError(JSGlobalObject* globalObject, CallFrame* callFrame) |
167 | { |
168 | VM& vm = globalObject->vm(); |
169 | auto scope = DECLARE_THROW_SCOPE(vm); |
170 | |
171 | auto* promise = jsDynamicCast<JSPromise*>(vm, callFrame->argument(0)); |
172 | if (!promise || promise->status(vm) != JSPromise::Status::Rejected) |
173 | return throwTypeError(globalObject, scope, "InjectedScriptHost.isPromiseRejectedWithNativeGetterTypeError first argument must be a rejected Promise."_s ); |
174 | |
175 | bool result = false; |
176 | if (auto* errorInstance = jsDynamicCast<ErrorInstance*>(vm, promise->result(vm))) |
177 | result = errorInstance->isNativeGetterTypeError(); |
178 | return jsBoolean(result); |
179 | } |
180 | |
181 | JSValue JSInjectedScriptHost::subtype(JSGlobalObject* globalObject, CallFrame* callFrame) |
182 | { |
183 | VM& vm = globalObject->vm(); |
184 | if (callFrame->argumentCount() < 1) |
185 | return jsUndefined(); |
186 | |
187 | JSValue value = callFrame->uncheckedArgument(0); |
188 | if (value.isString()) |
189 | return vm.smallStrings.stringString(); |
190 | if (value.isBoolean()) |
191 | return vm.smallStrings.booleanString(); |
192 | if (value.isNumber()) |
193 | return vm.smallStrings.numberString(); |
194 | if (value.isSymbol()) |
195 | return vm.smallStrings.symbolString(); |
196 | |
197 | if (auto* object = jsDynamicCast<JSObject*>(vm, value)) { |
198 | if (object->isErrorInstance()) |
199 | return jsNontrivialString(vm, "error"_s ); |
200 | |
201 | // Consider class constructor functions class objects. |
202 | JSFunction* function = jsDynamicCast<JSFunction*>(vm, value); |
203 | if (function && function->isClassConstructorFunction()) |
204 | return jsNontrivialString(vm, "class"_s ); |
205 | |
206 | if (object->inherits<JSArray>(vm)) |
207 | return jsNontrivialString(vm, "array"_s ); |
208 | if (object->inherits<DirectArguments>(vm) || object->inherits<ScopedArguments>(vm)) |
209 | return jsNontrivialString(vm, "array"_s ); |
210 | |
211 | if (object->inherits<DateInstance>(vm)) |
212 | return jsNontrivialString(vm, "date"_s ); |
213 | if (object->inherits<RegExpObject>(vm)) |
214 | return jsNontrivialString(vm, "regexp"_s ); |
215 | if (object->inherits<ProxyObject>(vm)) |
216 | return jsNontrivialString(vm, "proxy"_s ); |
217 | |
218 | if (object->inherits<JSMap>(vm)) |
219 | return jsNontrivialString(vm, "map"_s ); |
220 | if (object->inherits<JSSet>(vm)) |
221 | return jsNontrivialString(vm, "set"_s ); |
222 | if (object->inherits<JSWeakMap>(vm)) |
223 | return jsNontrivialString(vm, "weakmap"_s ); |
224 | if (object->inherits<JSWeakSet>(vm)) |
225 | return jsNontrivialString(vm, "weakset"_s ); |
226 | |
227 | if (object->inherits<JSStringIterator>(vm)) |
228 | return jsNontrivialString(vm, "iterator"_s ); |
229 | |
230 | if (object->getDirect(vm, vm.propertyNames->builtinNames().arrayIteratorNextIndexPrivateName()) |
231 | || object->getDirect(vm, vm.propertyNames->builtinNames().mapBucketPrivateName()) |
232 | || object->getDirect(vm, vm.propertyNames->builtinNames().setBucketPrivateName())) |
233 | return jsNontrivialString(vm, "iterator"_s ); |
234 | |
235 | if (object->inherits<JSInt8Array>(vm) |
236 | || object->inherits<JSInt16Array>(vm) |
237 | || object->inherits<JSInt32Array>(vm) |
238 | || object->inherits<JSUint8Array>(vm) |
239 | || object->inherits<JSUint8ClampedArray>(vm) |
240 | || object->inherits<JSUint16Array>(vm) |
241 | || object->inherits<JSUint32Array>(vm) |
242 | || object->inherits<JSFloat32Array>(vm) |
243 | || object->inherits<JSFloat64Array>(vm)) |
244 | return jsNontrivialString(vm, "array"_s ); |
245 | } |
246 | |
247 | return impl().subtype(globalObject, value); |
248 | } |
249 | |
250 | JSValue JSInjectedScriptHost::functionDetails(JSGlobalObject* globalObject, CallFrame* callFrame) |
251 | { |
252 | if (callFrame->argumentCount() < 1) |
253 | return jsUndefined(); |
254 | |
255 | VM& vm = globalObject->vm(); |
256 | JSValue value = callFrame->uncheckedArgument(0); |
257 | auto* function = jsDynamicCast<JSFunction*>(vm, value); |
258 | if (!function) |
259 | return jsUndefined(); |
260 | |
261 | // FIXME: <https://webkit.org/b/87192> Web Inspector: Expose function scope / closure data |
262 | |
263 | // FIXME: This should provide better details for JSBoundFunctions. |
264 | |
265 | const SourceCode* sourceCode = function->sourceCode(); |
266 | if (!sourceCode) |
267 | return jsUndefined(); |
268 | |
269 | // In the inspector protocol all positions are 0-based while in SourceCode they are 1-based |
270 | int lineNumber = sourceCode->firstLine().oneBasedInt(); |
271 | if (lineNumber) |
272 | lineNumber -= 1; |
273 | int columnNumber = sourceCode->startColumn().oneBasedInt(); |
274 | if (columnNumber) |
275 | columnNumber -= 1; |
276 | |
277 | String scriptID = String::number(sourceCode->provider()->asID()); |
278 | JSObject* location = constructEmptyObject(globalObject); |
279 | location->putDirect(vm, Identifier::fromString(vm, "scriptId" ), jsString(vm, scriptID)); |
280 | location->putDirect(vm, Identifier::fromString(vm, "lineNumber" ), jsNumber(lineNumber)); |
281 | location->putDirect(vm, Identifier::fromString(vm, "columnNumber" ), jsNumber(columnNumber)); |
282 | |
283 | JSObject* result = constructEmptyObject(globalObject); |
284 | result->putDirect(vm, Identifier::fromString(vm, "location" ), location); |
285 | |
286 | String name = function->name(vm); |
287 | if (!name.isEmpty()) |
288 | result->putDirect(vm, Identifier::fromString(vm, "name" ), jsString(vm, name)); |
289 | |
290 | String displayName = function->displayName(vm); |
291 | if (!displayName.isEmpty()) |
292 | result->putDirect(vm, Identifier::fromString(vm, "displayName" ), jsString(vm, displayName)); |
293 | |
294 | return result; |
295 | } |
296 | |
297 | static JSObject* constructInternalProperty(JSGlobalObject* globalObject, const String& name, JSValue value) |
298 | { |
299 | VM& vm = globalObject->vm(); |
300 | JSObject* result = constructEmptyObject(globalObject); |
301 | result->putDirect(vm, Identifier::fromString(vm, "name" ), jsString(vm, name)); |
302 | result->putDirect(vm, Identifier::fromString(vm, "value" ), value); |
303 | return result; |
304 | } |
305 | |
306 | JSValue JSInjectedScriptHost::getInternalProperties(JSGlobalObject* globalObject, CallFrame* callFrame) |
307 | { |
308 | if (callFrame->argumentCount() < 1) |
309 | return jsUndefined(); |
310 | |
311 | VM& vm = globalObject->vm(); |
312 | auto scope = DECLARE_THROW_SCOPE(vm); |
313 | JSValue value = callFrame->uncheckedArgument(0); |
314 | |
315 | JSValue internalProperties = impl().getInternalProperties(vm, globalObject, value); |
316 | if (internalProperties) |
317 | return internalProperties; |
318 | |
319 | if (JSPromise* promise = jsDynamicCast<JSPromise*>(vm, value)) { |
320 | unsigned index = 0; |
321 | JSArray* array = constructEmptyArray(globalObject, nullptr); |
322 | RETURN_IF_EXCEPTION(scope, JSValue()); |
323 | switch (promise->status(vm)) { |
324 | case JSPromise::Status::Pending: |
325 | scope.release(); |
326 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "status"_s , jsNontrivialString(vm, "pending"_s ))); |
327 | return array; |
328 | case JSPromise::Status::Fulfilled: |
329 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "status"_s , jsNontrivialString(vm, "resolved"_s ))); |
330 | RETURN_IF_EXCEPTION(scope, JSValue()); |
331 | scope.release(); |
332 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "result"_s , promise->result(vm))); |
333 | return array; |
334 | case JSPromise::Status::Rejected: |
335 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "status"_s , jsNontrivialString(vm, "rejected"_s ))); |
336 | RETURN_IF_EXCEPTION(scope, JSValue()); |
337 | scope.release(); |
338 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "result"_s , promise->result(vm))); |
339 | return array; |
340 | } |
341 | // FIXME: <https://webkit.org/b/141664> Web Inspector: ES6: Improved Support for Promises - Promise Reactions |
342 | RELEASE_ASSERT_NOT_REACHED(); |
343 | } |
344 | |
345 | if (JSBoundFunction* boundFunction = jsDynamicCast<JSBoundFunction*>(vm, value)) { |
346 | unsigned index = 0; |
347 | JSArray* array = constructEmptyArray(globalObject, nullptr); |
348 | RETURN_IF_EXCEPTION(scope, JSValue()); |
349 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "targetFunction" , boundFunction->targetFunction())); |
350 | RETURN_IF_EXCEPTION(scope, JSValue()); |
351 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "boundThis" , boundFunction->boundThis())); |
352 | RETURN_IF_EXCEPTION(scope, JSValue()); |
353 | if (boundFunction->boundArgs()) { |
354 | scope.release(); |
355 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "boundArgs" , boundFunction->boundArgsCopy(globalObject))); |
356 | return array; |
357 | } |
358 | return array; |
359 | } |
360 | |
361 | if (ProxyObject* proxy = jsDynamicCast<ProxyObject*>(vm, value)) { |
362 | unsigned index = 0; |
363 | JSArray* array = constructEmptyArray(globalObject, nullptr, 2); |
364 | RETURN_IF_EXCEPTION(scope, JSValue()); |
365 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "target"_s , proxy->target())); |
366 | RETURN_IF_EXCEPTION(scope, JSValue()); |
367 | scope.release(); |
368 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "handler"_s , proxy->handler())); |
369 | return array; |
370 | } |
371 | |
372 | if (JSObject* iteratorObject = jsDynamicCast<JSObject*>(vm, value)) { |
373 | if (iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().arrayIteratorNextIndexPrivateName())) { |
374 | JSValue iteratedValue = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().iteratedObjectPrivateName()); |
375 | JSValue kind = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().arrayIteratorKindPrivateName()); |
376 | |
377 | unsigned index = 0; |
378 | JSArray* array = constructEmptyArray(globalObject, nullptr, 2); |
379 | RETURN_IF_EXCEPTION(scope, JSValue()); |
380 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "array" , iteratedValue)); |
381 | RETURN_IF_EXCEPTION(scope, JSValue()); |
382 | scope.release(); |
383 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "kind" , kind)); |
384 | return array; |
385 | } |
386 | |
387 | if (iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().mapBucketPrivateName())) { |
388 | JSValue iteratedValue = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().iteratedObjectPrivateName()); |
389 | String kind; |
390 | switch (static_cast<IterationKind>(iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().mapIteratorKindPrivateName()).asInt32())) { |
391 | case IterateKey: |
392 | kind = "key"_s ; |
393 | break; |
394 | case IterateValue: |
395 | kind = "value"_s ; |
396 | break; |
397 | case IterateKeyValue: |
398 | kind = "key+value"_s ; |
399 | break; |
400 | } |
401 | unsigned index = 0; |
402 | JSArray* array = constructEmptyArray(globalObject, nullptr, 2); |
403 | RETURN_IF_EXCEPTION(scope, JSValue()); |
404 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "map" , iteratedValue)); |
405 | RETURN_IF_EXCEPTION(scope, JSValue()); |
406 | scope.release(); |
407 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "kind" , jsNontrivialString(vm, kind))); |
408 | return array; |
409 | } |
410 | |
411 | if (iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().setBucketPrivateName())) { |
412 | JSValue iteratedValue = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().iteratedObjectPrivateName()); |
413 | String kind; |
414 | switch (static_cast<IterationKind>(iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().setIteratorKindPrivateName()).asInt32())) { |
415 | case IterateKey: |
416 | kind = "key"_s ; |
417 | break; |
418 | case IterateValue: |
419 | kind = "value"_s ; |
420 | break; |
421 | case IterateKeyValue: |
422 | kind = "key+value"_s ; |
423 | break; |
424 | } |
425 | unsigned index = 0; |
426 | JSArray* array = constructEmptyArray(globalObject, nullptr, 2); |
427 | RETURN_IF_EXCEPTION(scope, JSValue()); |
428 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "set" , iteratedValue)); |
429 | RETURN_IF_EXCEPTION(scope, JSValue()); |
430 | scope.release(); |
431 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "kind" , jsNontrivialString(vm, kind))); |
432 | return array; |
433 | } |
434 | } |
435 | |
436 | if (JSStringIterator* stringIterator = jsDynamicCast<JSStringIterator*>(vm, value)) { |
437 | unsigned index = 0; |
438 | JSArray* array = constructEmptyArray(globalObject, nullptr, 1); |
439 | RETURN_IF_EXCEPTION(scope, JSValue()); |
440 | scope.release(); |
441 | array->putDirectIndex(globalObject, index++, constructInternalProperty(globalObject, "string" , stringIterator->iteratedValue(globalObject))); |
442 | return array; |
443 | } |
444 | |
445 | return jsUndefined(); |
446 | } |
447 | |
448 | JSValue JSInjectedScriptHost::proxyTargetValue(VM& vm, CallFrame* callFrame) |
449 | { |
450 | if (callFrame->argumentCount() < 1) |
451 | return jsUndefined(); |
452 | |
453 | JSValue value = callFrame->uncheckedArgument(0); |
454 | ProxyObject* proxy = jsDynamicCast<ProxyObject*>(vm, value); |
455 | if (!proxy) |
456 | return jsUndefined(); |
457 | |
458 | JSObject* target = proxy->target(); |
459 | while (ProxyObject* proxy = jsDynamicCast<ProxyObject*>(vm, target)) |
460 | target = proxy->target(); |
461 | |
462 | return target; |
463 | } |
464 | |
465 | JSValue JSInjectedScriptHost::weakMapSize(JSGlobalObject* globalObject, CallFrame* callFrame) |
466 | { |
467 | if (callFrame->argumentCount() < 1) |
468 | return jsUndefined(); |
469 | |
470 | VM& vm = globalObject->vm(); |
471 | JSValue value = callFrame->uncheckedArgument(0); |
472 | JSWeakMap* weakMap = jsDynamicCast<JSWeakMap*>(vm, value); |
473 | if (!weakMap) |
474 | return jsUndefined(); |
475 | |
476 | return jsNumber(weakMap->size()); |
477 | } |
478 | |
479 | JSValue JSInjectedScriptHost::weakMapEntries(JSGlobalObject* globalObject, CallFrame* callFrame) |
480 | { |
481 | if (callFrame->argumentCount() < 1) |
482 | return jsUndefined(); |
483 | |
484 | VM& vm = globalObject->vm(); |
485 | auto scope = DECLARE_THROW_SCOPE(vm); |
486 | auto* weakMap = jsDynamicCast<JSWeakMap*>(vm, callFrame->uncheckedArgument(0)); |
487 | if (!weakMap) |
488 | return jsUndefined(); |
489 | |
490 | MarkedArgumentBuffer buffer; |
491 | auto fetchCount = callFrame->argument(1).toInteger(globalObject); |
492 | weakMap->takeSnapshot(buffer, fetchCount >= 0 ? static_cast<unsigned>(fetchCount) : 0); |
493 | ASSERT(!buffer.hasOverflowed()); |
494 | |
495 | JSArray* array = constructEmptyArray(globalObject, nullptr); |
496 | RETURN_IF_EXCEPTION(scope, JSValue()); |
497 | |
498 | for (unsigned index = 0; index < buffer.size(); index += 2) { |
499 | JSObject* entry = constructEmptyObject(globalObject); |
500 | entry->putDirect(vm, Identifier::fromString(vm, "key" ), buffer.at(index)); |
501 | entry->putDirect(vm, Identifier::fromString(vm, "value" ), buffer.at(index + 1)); |
502 | array->putDirectIndex(globalObject, index / 2, entry); |
503 | RETURN_IF_EXCEPTION(scope, JSValue()); |
504 | } |
505 | |
506 | return array; |
507 | } |
508 | |
509 | JSValue JSInjectedScriptHost::weakSetSize(JSGlobalObject* globalObject, CallFrame* callFrame) |
510 | { |
511 | if (callFrame->argumentCount() < 1) |
512 | return jsUndefined(); |
513 | |
514 | VM& vm = globalObject->vm(); |
515 | JSValue value = callFrame->uncheckedArgument(0); |
516 | JSWeakSet* weakSet = jsDynamicCast<JSWeakSet*>(vm, value); |
517 | if (!weakSet) |
518 | return jsUndefined(); |
519 | |
520 | return jsNumber(weakSet->size()); |
521 | } |
522 | |
523 | JSValue JSInjectedScriptHost::weakSetEntries(JSGlobalObject* globalObject, CallFrame* callFrame) |
524 | { |
525 | if (callFrame->argumentCount() < 1) |
526 | return jsUndefined(); |
527 | |
528 | VM& vm = globalObject->vm(); |
529 | auto scope = DECLARE_THROW_SCOPE(vm); |
530 | auto* weakSet = jsDynamicCast<JSWeakSet*>(vm, callFrame->uncheckedArgument(0)); |
531 | if (!weakSet) |
532 | return jsUndefined(); |
533 | |
534 | MarkedArgumentBuffer buffer; |
535 | auto fetchCount = callFrame->argument(1).toInteger(globalObject); |
536 | weakSet->takeSnapshot(buffer, fetchCount >= 0 ? static_cast<unsigned>(fetchCount) : 0); |
537 | ASSERT(!buffer.hasOverflowed()); |
538 | |
539 | JSArray* array = constructEmptyArray(globalObject, nullptr); |
540 | RETURN_IF_EXCEPTION(scope, JSValue()); |
541 | |
542 | for (unsigned index = 0; index < buffer.size(); ++index) { |
543 | JSObject* entry = constructEmptyObject(globalObject); |
544 | entry->putDirect(vm, Identifier::fromString(vm, "value" ), buffer.at(index)); |
545 | array->putDirectIndex(globalObject, index, entry); |
546 | RETURN_IF_EXCEPTION(scope, JSValue()); |
547 | } |
548 | |
549 | return array; |
550 | } |
551 | |
552 | static JSObject* cloneArrayIteratorObject(JSGlobalObject* globalObject, VM& vm, JSObject* iteratorObject, JSValue nextIndex, JSValue iteratedObject) |
553 | { |
554 | ASSERT(iteratorObject->type() == FinalObjectType); |
555 | JSObject* clone = constructEmptyObject(globalObject, ArrayIteratorPrototype::create(vm, globalObject, ArrayIteratorPrototype::createStructure(vm, globalObject, globalObject->iteratorPrototype()))); |
556 | clone->putDirect(vm, vm.propertyNames->builtinNames().iteratedObjectPrivateName(), iteratedObject); |
557 | clone->putDirect(vm, vm.propertyNames->builtinNames().arrayIteratorKindPrivateName(), iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().arrayIteratorKindPrivateName())); |
558 | clone->putDirect(vm, vm.propertyNames->builtinNames().arrayIteratorNextIndexPrivateName(), nextIndex); |
559 | clone->putDirect(vm, vm.propertyNames->builtinNames().arrayIteratorNextPrivateName(), iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().arrayIteratorNextPrivateName())); |
560 | clone->putDirect(vm, vm.propertyNames->builtinNames().arrayIteratorIsDonePrivateName(), iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().arrayIteratorIsDonePrivateName())); |
561 | return clone; |
562 | } |
563 | |
564 | static JSObject* cloneMapIteratorObject(JSGlobalObject* globalObject, VM& vm, JSObject* iteratorObject, JSValue mapBucket, JSValue iteratedObject) |
565 | { |
566 | ASSERT(iteratorObject->type() == FinalObjectType); |
567 | JSObject* clone = constructEmptyObject(globalObject, MapIteratorPrototype::create(vm, globalObject, MapIteratorPrototype::createStructure(vm, globalObject, globalObject->iteratorPrototype()))); |
568 | clone->putDirect(vm, vm.propertyNames->builtinNames().iteratedObjectPrivateName(), iteratedObject); |
569 | clone->putDirect(vm, vm.propertyNames->builtinNames().mapIteratorKindPrivateName(), iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().mapIteratorKindPrivateName())); |
570 | clone->putDirect(vm, vm.propertyNames->builtinNames().mapBucketPrivateName(), mapBucket); |
571 | return clone; |
572 | } |
573 | |
574 | static JSObject* cloneSetIteratorObject(JSGlobalObject* globalObject, VM& vm, JSObject* iteratorObject, JSValue setBucket, JSValue iteratedObject) |
575 | { |
576 | ASSERT(iteratorObject->type() == FinalObjectType); |
577 | JSObject* clone = constructEmptyObject(globalObject, SetIteratorPrototype::create(vm, globalObject, SetIteratorPrototype::createStructure(vm, globalObject, globalObject->iteratorPrototype()))); |
578 | clone->putDirect(vm, vm.propertyNames->builtinNames().iteratedObjectPrivateName(), iteratedObject); |
579 | clone->putDirect(vm, vm.propertyNames->builtinNames().setIteratorKindPrivateName(), iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().setIteratorKindPrivateName())); |
580 | clone->putDirect(vm, vm.propertyNames->builtinNames().setBucketPrivateName(), setBucket); |
581 | return clone; |
582 | } |
583 | |
584 | JSValue JSInjectedScriptHost::iteratorEntries(JSGlobalObject* globalObject, CallFrame* callFrame) |
585 | { |
586 | if (callFrame->argumentCount() < 1) |
587 | return jsUndefined(); |
588 | |
589 | VM& vm = globalObject->vm(); |
590 | auto scope = DECLARE_THROW_SCOPE(vm); |
591 | |
592 | JSValue iterator; |
593 | JSValue value = callFrame->uncheckedArgument(0); |
594 | if (JSStringIterator* stringIterator = jsDynamicCast<JSStringIterator*>(vm, value)) { |
595 | if (globalObject->isStringPrototypeIteratorProtocolFastAndNonObservable()) |
596 | iterator = stringIterator->clone(globalObject); |
597 | } else if (JSObject* iteratorObject = jsDynamicCast<JSObject*>(vm, value)) { |
598 | // Detect an ArrayIterator by checking for one of its unique private properties. |
599 | JSValue iteratedObject = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().iteratedObjectPrivateName()); |
600 | if (JSValue nextIndex = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().arrayIteratorNextIndexPrivateName())) { |
601 | if (isJSArray(iteratedObject)) { |
602 | JSArray* array = jsCast<JSArray*>(iteratedObject); |
603 | if (array->isIteratorProtocolFastAndNonObservable()) |
604 | iterator = cloneArrayIteratorObject(globalObject, vm, iteratorObject, nextIndex, iteratedObject); |
605 | } else if (iteratedObject.isObject() && TypeInfo::isArgumentsType(asObject(iteratedObject)->type())) { |
606 | if (globalObject->isArrayPrototypeIteratorProtocolFastAndNonObservable()) |
607 | iterator = cloneArrayIteratorObject(globalObject, vm, iteratorObject, nextIndex, iteratedObject); |
608 | } |
609 | } else if (JSValue mapBucket = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().mapBucketPrivateName())) { |
610 | if (jsCast<JSMap*>(iteratedObject)->isIteratorProtocolFastAndNonObservable()) |
611 | iterator = cloneMapIteratorObject(globalObject, vm, iteratorObject, mapBucket, iteratedObject); |
612 | } else if (JSValue setBucket = iteratorObject->getDirect(vm, vm.propertyNames->builtinNames().setBucketPrivateName())) { |
613 | if (jsCast<JSSet*>(iteratedObject)->isIteratorProtocolFastAndNonObservable()) |
614 | iterator = cloneSetIteratorObject(globalObject, vm, iteratorObject, setBucket, iteratedObject); |
615 | } |
616 | } |
617 | RETURN_IF_EXCEPTION(scope, { }); |
618 | if (!iterator) |
619 | return jsUndefined(); |
620 | |
621 | IterationRecord iterationRecord = { iterator, iterator.get(globalObject, vm.propertyNames->next) }; |
622 | |
623 | unsigned numberToFetch = 5; |
624 | JSValue numberToFetchArg = callFrame->argument(1); |
625 | double fetchDouble = numberToFetchArg.toInteger(globalObject); |
626 | RETURN_IF_EXCEPTION(scope, { }); |
627 | if (fetchDouble >= 0) |
628 | numberToFetch = static_cast<unsigned>(fetchDouble); |
629 | |
630 | JSArray* array = constructEmptyArray(globalObject, nullptr); |
631 | RETURN_IF_EXCEPTION(scope, { }); |
632 | |
633 | for (unsigned i = 0; i < numberToFetch; ++i) { |
634 | JSValue next = iteratorStep(globalObject, iterationRecord); |
635 | if (UNLIKELY(scope.exception()) || next.isFalse()) |
636 | break; |
637 | |
638 | JSValue nextValue = iteratorValue(globalObject, next); |
639 | RETURN_IF_EXCEPTION(scope, { }); |
640 | |
641 | JSObject* entry = constructEmptyObject(globalObject); |
642 | entry->putDirect(vm, Identifier::fromString(vm, "value" ), nextValue); |
643 | array->putDirectIndex(globalObject, i, entry); |
644 | if (UNLIKELY(scope.exception())) { |
645 | scope.release(); |
646 | iteratorClose(globalObject, iterationRecord); |
647 | break; |
648 | } |
649 | } |
650 | |
651 | return array; |
652 | } |
653 | |
654 | static bool checkForbiddenPrototype(JSGlobalObject* globalObject, JSValue value, JSValue proto) |
655 | { |
656 | if (value == proto) |
657 | return true; |
658 | |
659 | // Check that the prototype chain of proto hasn't been modified to include value. |
660 | return JSObject::defaultHasInstance(globalObject, proto, value); |
661 | } |
662 | |
663 | JSValue JSInjectedScriptHost::queryInstances(JSGlobalObject* globalObject, CallFrame* callFrame) |
664 | { |
665 | if (callFrame->argumentCount() < 1) |
666 | return jsUndefined(); |
667 | |
668 | VM& vm = globalObject->vm(); |
669 | auto scope = DECLARE_THROW_SCOPE(vm); |
670 | |
671 | JSValue prototypeOrConstructor = callFrame->uncheckedArgument(0); |
672 | if (!prototypeOrConstructor.isObject()) |
673 | return throwTypeError(globalObject, scope, "queryInstances first argument must be an object."_s ); |
674 | |
675 | JSObject* object = asObject(prototypeOrConstructor); |
676 | if (object->inherits<ProxyObject>(vm)) |
677 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with a Proxy."_s ); |
678 | |
679 | JSValue prototype = object; |
680 | |
681 | PropertySlot prototypeSlot(object, PropertySlot::InternalMethodType::VMInquiry); |
682 | if (object->getPropertySlot(globalObject, vm.propertyNames->prototype, prototypeSlot)) { |
683 | RETURN_IF_EXCEPTION(scope, { }); |
684 | if (prototypeSlot.isValue()) { |
685 | JSValue prototypeValue = prototypeSlot.getValue(globalObject, vm.propertyNames->prototype); |
686 | if (prototypeValue.isObject()) { |
687 | prototype = prototypeValue; |
688 | object = asObject(prototype); |
689 | } |
690 | } |
691 | } |
692 | |
693 | if (object->inherits<ProxyObject>(vm) || prototype.inherits<ProxyObject>(vm)) |
694 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with a Proxy."_s ); |
695 | |
696 | // FIXME: implement a way of distinguishing between internal and user-created objects. |
697 | if (checkForbiddenPrototype(globalObject, object, globalObject->objectPrototype())) |
698 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with Object."_s ); |
699 | if (checkForbiddenPrototype(globalObject, object, globalObject->functionPrototype())) |
700 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with Function."_s ); |
701 | if (checkForbiddenPrototype(globalObject, object, globalObject->arrayPrototype())) |
702 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with Array."_s ); |
703 | if (checkForbiddenPrototype(globalObject, object, globalObject->mapPrototype())) |
704 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with Map."_s ); |
705 | if (checkForbiddenPrototype(globalObject, object, globalObject->jsSetPrototype())) |
706 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with Set."_s ); |
707 | if (checkForbiddenPrototype(globalObject, object, globalObject->promisePrototype())) |
708 | return throwTypeError(globalObject, scope, "queryInstances cannot be called with Promise."_s ); |
709 | |
710 | sanitizeStackForVM(vm); |
711 | vm.heap.collectNow(Sync, CollectionScope::Full); |
712 | |
713 | JSArray* array = constructEmptyArray(globalObject, nullptr); |
714 | RETURN_IF_EXCEPTION(scope, { }); |
715 | |
716 | { |
717 | HeapIterationScope iterationScope(vm.heap); |
718 | vm.heap.objectSpace().forEachLiveCell(iterationScope, [&] (HeapCell* cell, HeapCell::Kind kind) { |
719 | if (!isJSCellKind(kind)) |
720 | return IterationStatus::Continue; |
721 | |
722 | JSValue value(static_cast<JSCell*>(cell)); |
723 | if (value.inherits<ProxyObject>(vm)) |
724 | return IterationStatus::Continue; |
725 | |
726 | if (JSObject::defaultHasInstance(globalObject, value, prototype)) |
727 | array->putDirectIndex(globalObject, array->length(), value); |
728 | |
729 | return IterationStatus::Continue; |
730 | }); |
731 | } |
732 | |
733 | return array; |
734 | } |
735 | |
736 | class HeapHolderFinder final : public HeapAnalyzer { |
737 | WTF_MAKE_FAST_ALLOCATED; |
738 | public: |
739 | HeapHolderFinder(HeapProfiler& profiler, JSCell* target) |
740 | : HeapAnalyzer() |
741 | , m_target(target) |
742 | { |
743 | ASSERT(!profiler.activeHeapAnalyzer()); |
744 | profiler.setActiveHeapAnalyzer(this); |
745 | profiler.vm().heap.collectNow(Sync, CollectionScope::Full); |
746 | profiler.setActiveHeapAnalyzer(nullptr); |
747 | |
748 | HashSet<JSCell*> queue; |
749 | |
750 | // Filter `m_holders` based on whether they're reachable from a non-Debugger root. |
751 | HashSet<JSCell*> visited; |
752 | for (auto* root : m_rootsToInclude) |
753 | queue.add(root); |
754 | while (auto* from = queue.takeAny()) { |
755 | if (m_rootsToIgnore.contains(from)) |
756 | continue; |
757 | if (!visited.add(from).isNewEntry) |
758 | continue; |
759 | for (auto* to : m_successors.get(from)) |
760 | queue.add(to); |
761 | } |
762 | |
763 | // If a known holder is not an object, also consider all of the holder's holders. |
764 | for (auto* holder : m_holders) |
765 | queue.add(holder); |
766 | while (auto* holder = queue.takeAny()) { |
767 | if (holder->isObject()) |
768 | continue; |
769 | |
770 | for (auto* from : m_predecessors.get(holder)) { |
771 | if (!m_holders.contains(from)) { |
772 | m_holders.add(from); |
773 | queue.add(from); |
774 | } |
775 | } |
776 | } |
777 | |
778 | m_holders.removeIf([&] (auto* holder) { |
779 | return !holder->isObject() || !visited.contains(holder); |
780 | }); |
781 | } |
782 | |
783 | HashSet<JSCell*>& holders() { return m_holders; } |
784 | |
785 | void analyzeEdge(JSCell* from, JSCell* to, SlotVisitor::RootMarkReason reason) override |
786 | { |
787 | ASSERT(to); |
788 | ASSERT(to->vm().heapProfiler()->activeHeapAnalyzer() == this); |
789 | |
790 | auto locker = holdLock(m_mutex); |
791 | |
792 | if (from && from != to) { |
793 | m_successors.ensure(from, [] { |
794 | return HashSet<JSCell*>(); |
795 | }).iterator->value.add(to); |
796 | |
797 | m_predecessors.ensure(to, [] { |
798 | return HashSet<JSCell*>(); |
799 | }).iterator->value.add(from); |
800 | |
801 | if (to == m_target) |
802 | m_holders.add(from); |
803 | } |
804 | |
805 | if (reason == SlotVisitor::RootMarkReason::Debugger) |
806 | m_rootsToIgnore.add(to); |
807 | else if (!from || reason != SlotVisitor::RootMarkReason::None) |
808 | m_rootsToInclude.add(to); |
809 | } |
810 | void analyzePropertyNameEdge(JSCell* from, JSCell* to, UniquedStringImpl*) override { analyzeEdge(from, to, SlotVisitor::RootMarkReason::None); } |
811 | void analyzeVariableNameEdge(JSCell* from, JSCell* to, UniquedStringImpl*) override { analyzeEdge(from, to, SlotVisitor::RootMarkReason::None); } |
812 | void analyzeIndexEdge(JSCell* from, JSCell* to, uint32_t) override { analyzeEdge(from, to, SlotVisitor::RootMarkReason::None); } |
813 | |
814 | void analyzeNode(JSCell*) override { } |
815 | void setOpaqueRootReachabilityReasonForCell(JSCell*, const char*) override { } |
816 | void setWrappedObjectForCell(JSCell*, void*) override { } |
817 | void setLabelForCell(JSCell*, const String&) override { } |
818 | |
819 | #ifndef NDEBUG |
820 | void dump(PrintStream& out) const |
821 | { |
822 | Indentation<4> indent; |
823 | |
824 | HashSet<JSCell*> visited; |
825 | |
826 | Function<void(JSCell*)> visit = [&] (auto* from) { |
827 | auto isFirstVisit = visited.add(from).isNewEntry; |
828 | |
829 | out.print(makeString(indent)); |
830 | |
831 | out.print("[ "_s ); |
832 | if (from == m_target) |
833 | out.print("T "_s ); |
834 | if (m_holders.contains(from)) |
835 | out.print("H "_s ); |
836 | if (m_rootsToIgnore.contains(from)) |
837 | out.print("- "_s ); |
838 | else if (m_rootsToInclude.contains(from)) |
839 | out.print("+ "_s ); |
840 | if (!isFirstVisit) |
841 | out.print("V "_s ); |
842 | out.print("] "_s ); |
843 | |
844 | from->dump(out); |
845 | |
846 | out.println(); |
847 | |
848 | if (isFirstVisit) { |
849 | IndentationScope<4> scope(indent); |
850 | for (auto* to : m_successors.get(from)) |
851 | visit(to); |
852 | } |
853 | }; |
854 | |
855 | for (auto* from : m_rootsToInclude) |
856 | visit(from); |
857 | } |
858 | #endif |
859 | |
860 | private: |
861 | Lock m_mutex; |
862 | HashMap<JSCell*, HashSet<JSCell*>> m_predecessors; |
863 | HashMap<JSCell*, HashSet<JSCell*>> m_successors; |
864 | HashSet<JSCell*> m_rootsToInclude; |
865 | HashSet<JSCell*> m_rootsToIgnore; |
866 | HashSet<JSCell*> m_holders; |
867 | const JSCell* m_target; |
868 | }; |
869 | |
870 | JSValue JSInjectedScriptHost::queryHolders(JSGlobalObject* globalObject, CallFrame* callFrame) |
871 | { |
872 | if (callFrame->argumentCount() < 1) |
873 | return jsUndefined(); |
874 | |
875 | VM& vm = globalObject->vm(); |
876 | auto scope = DECLARE_THROW_SCOPE(vm); |
877 | |
878 | JSValue target = callFrame->uncheckedArgument(0); |
879 | if (!target.isObject()) |
880 | return throwTypeError(globalObject, scope, "queryHolders first argument must be an object."_s ); |
881 | |
882 | JSArray* result = constructEmptyArray(globalObject, nullptr); |
883 | RETURN_IF_EXCEPTION(scope, { }); |
884 | |
885 | { |
886 | DeferGC deferGC(vm.heap); |
887 | PreventCollectionScope preventCollectionScope(vm.heap); |
888 | sanitizeStackForVM(vm); |
889 | |
890 | HeapHolderFinder holderFinder(vm.ensureHeapProfiler(), target.asCell()); |
891 | |
892 | auto holders = copyToVector(holderFinder.holders()); |
893 | std::sort(holders.begin(), holders.end()); |
894 | for (auto* holder : holders) |
895 | result->putDirectIndex(globalObject, result->length(), holder); |
896 | } |
897 | |
898 | return result; |
899 | } |
900 | |
901 | } // namespace Inspector |
902 | |