2 * Copyright (C) 2014, 2015 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "InspectorProtocolObjects.h"
30 #include "JSCJSValue.h"
31 #include "JSCJSValueInlines.h"
32 #include <wtf/text/CString.h>
33 #include <wtf/text/WTFString.h>
34 #include <wtf/text/StringBuilder.h>
35 #include <wtf/Vector.h>
40 : m_seenTypes(TypeNothing)
41 , m_isOverflown(false)
45 void TypeSet::addTypeInformation(RuntimeType type, PassRefPtr<StructureShape> prpNewShape, Structure* structure)
47 RefPtr<StructureShape> newShape = prpNewShape;
48 m_seenTypes = m_seenTypes | type;
50 if (structure && newShape && !runtimeTypeIsPrimitive(type)) {
51 if (!m_structureSet.contains(structure)) {
52 m_structureSet.add(structure);
53 // Make one more pass making sure that:
54 // - We don't have two instances of the same shape. (Same shapes may have different Structures).
55 // - We don't have two shapes that share the same prototype chain. If these shapes share the same
56 // prototype chain, they will be merged into one shape.
58 String hash = newShape->propertyHash();
59 for (size_t i = 0; i < m_structureHistory.size(); i++) {
60 RefPtr<StructureShape>& seenShape = m_structureHistory.at(i);
61 if (seenShape->propertyHash() == hash) {
65 if (seenShape->hasSamePrototypeChain(newShape)) {
66 seenShape = StructureShape::merge(seenShape, newShape);
73 if (m_structureHistory.size() < 100)
74 m_structureHistory.append(newShape);
75 else if (!m_isOverflown)
82 void TypeSet::invalidateCache()
84 auto keepMarkedStructuresFilter = [] (Structure* structure) -> bool { return Heap::isMarked(structure); };
85 m_structureSet.genericFilter(keepMarkedStructuresFilter);
88 String TypeSet::dumpTypes() const
90 if (m_seenTypes == TypeNothing)
91 return ASCIILiteral("(Unreached Statement)");
95 if (m_seenTypes & TypeFunction)
96 seen.appendLiteral("Function ");
97 if (m_seenTypes & TypeUndefined)
98 seen.appendLiteral("Undefined ");
99 if (m_seenTypes & TypeNull)
100 seen.appendLiteral("Null ");
101 if (m_seenTypes & TypeBoolean)
102 seen.appendLiteral("Boolean ");
103 if (m_seenTypes & TypeMachineInt)
104 seen.appendLiteral("MachineInt ");
105 if (m_seenTypes & TypeNumber)
106 seen.appendLiteral("Number ");
107 if (m_seenTypes & TypeString)
108 seen.appendLiteral("String ");
109 if (m_seenTypes & TypeObject)
110 seen.appendLiteral("Object ");
111 if (m_seenTypes & TypeSymbol)
112 seen.appendLiteral("Symbol ");
114 for (size_t i = 0; i < m_structureHistory.size(); i++) {
115 RefPtr<StructureShape> shape = m_structureHistory.at(i);
116 seen.append(shape->m_constructorName);
120 if (m_structureHistory.size())
121 seen.appendLiteral("\nStructures:[ ");
122 for (size_t i = 0; i < m_structureHistory.size(); i++) {
123 seen.append(m_structureHistory.at(i)->stringRepresentation());
126 if (m_structureHistory.size())
129 if (m_structureHistory.size()) {
130 seen.appendLiteral("\nLeast Common Ancestor: ");
131 seen.append(leastCommonAncestor());
134 return seen.toString();
137 bool TypeSet::doesTypeConformTo(RuntimeTypeMask test) const
139 // This function checks if our seen types conform to the types described by the test bitstring. (i.e we haven't seen more types than test).
140 // We are <= to those types if ANDing with the bitstring doesn't zero out any of our bits.
154 return m_seenTypes != TypeNothing && (m_seenTypes & test) == m_seenTypes;
157 String TypeSet::displayName() const
159 if (m_seenTypes == TypeNothing)
160 return emptyString();
162 if (m_structureHistory.size() && doesTypeConformTo(TypeObject | TypeNull | TypeUndefined)) {
163 String ctorName = leastCommonAncestor();
165 if (doesTypeConformTo(TypeObject))
167 if (doesTypeConformTo(TypeObject | TypeNull | TypeUndefined))
168 return ctorName + '?';
171 // The order of these checks are important. For example, if a value is only a function, it conforms to TypeFunction, but it also conforms to TypeFunction | TypeNull.
172 // Therefore, more specific types must be checked first.
174 if (doesTypeConformTo(TypeFunction))
175 return ASCIILiteral("Function");
176 if (doesTypeConformTo(TypeUndefined))
177 return ASCIILiteral("Undefined");
178 if (doesTypeConformTo(TypeNull))
179 return ASCIILiteral("Null");
180 if (doesTypeConformTo(TypeBoolean))
181 return ASCIILiteral("Boolean");
182 if (doesTypeConformTo(TypeMachineInt))
183 return ASCIILiteral("Integer");
184 if (doesTypeConformTo(TypeNumber | TypeMachineInt))
185 return ASCIILiteral("Number");
186 if (doesTypeConformTo(TypeString))
187 return ASCIILiteral("String");
188 if (doesTypeConformTo(TypeSymbol))
189 return ASCIILiteral("Symbol");
191 if (doesTypeConformTo(TypeNull | TypeUndefined))
192 return ASCIILiteral("(?)");
194 if (doesTypeConformTo(TypeFunction | TypeNull | TypeUndefined))
195 return ASCIILiteral("Function?");
196 if (doesTypeConformTo(TypeBoolean | TypeNull | TypeUndefined))
197 return ASCIILiteral("Boolean?");
198 if (doesTypeConformTo(TypeMachineInt | TypeNull | TypeUndefined))
199 return ASCIILiteral("Integer?");
200 if (doesTypeConformTo(TypeNumber | TypeMachineInt | TypeNull | TypeUndefined))
201 return ASCIILiteral("Number?");
202 if (doesTypeConformTo(TypeString | TypeNull | TypeUndefined))
203 return ASCIILiteral("String?");
204 if (doesTypeConformTo(TypeSymbol | TypeNull | TypeUndefined))
205 return ASCIILiteral("Symbol?");
207 if (doesTypeConformTo(TypeObject | TypeFunction | TypeString))
208 return ASCIILiteral("Object");
209 if (doesTypeConformTo(TypeObject | TypeFunction | TypeString | TypeNull | TypeUndefined))
210 return ASCIILiteral("Object?");
212 return ASCIILiteral("(many)");
215 String TypeSet::leastCommonAncestor() const
217 return StructureShape::leastCommonAncestor(m_structureHistory);
220 Ref<Inspector::Protocol::Array<Inspector::Protocol::Runtime::StructureDescription>> TypeSet::allStructureRepresentations() const
222 auto description = Inspector::Protocol::Array<Inspector::Protocol::Runtime::StructureDescription>::create();
224 for (size_t i = 0; i < m_structureHistory.size(); i++)
225 description->addItem(m_structureHistory.at(i)->inspectorRepresentation());
230 Ref<Inspector::Protocol::Runtime::TypeSet> TypeSet::inspectorTypeSet() const
232 return Inspector::Protocol::Runtime::TypeSet::create()
233 .setIsFunction((m_seenTypes & TypeFunction) != TypeNothing)
234 .setIsUndefined((m_seenTypes & TypeUndefined) != TypeNothing)
235 .setIsNull((m_seenTypes & TypeNull) != TypeNothing)
236 .setIsBoolean((m_seenTypes & TypeBoolean) != TypeNothing)
237 .setIsInteger((m_seenTypes & TypeMachineInt) != TypeNothing)
238 .setIsNumber((m_seenTypes & TypeNumber) != TypeNothing)
239 .setIsString((m_seenTypes & TypeString) != TypeNothing)
240 .setIsObject((m_seenTypes & TypeObject) != TypeNothing)
241 .setIsSymbol((m_seenTypes & TypeSymbol) != TypeNothing)
245 String TypeSet::toJSONString() const
247 // This returns a JSON string representing an Object with the following properties:
248 // displayTypeName: 'String'
249 // primitiveTypeNames: 'Array<String>'
250 // structures: 'Array<JSON<StructureShape>>'
255 json.appendLiteral("\"displayTypeName\":");
257 json.append(displayName());
261 json.appendLiteral("\"primitiveTypeNames\":");
263 bool hasAnItem = false;
264 if (m_seenTypes & TypeUndefined) {
266 json.appendLiteral("\"Undefined\"");
268 if (m_seenTypes & TypeNull) {
272 json.appendLiteral("\"Null\"");
274 if (m_seenTypes & TypeBoolean) {
278 json.appendLiteral("\"Boolean\"");
280 if (m_seenTypes & TypeMachineInt) {
284 json.appendLiteral("\"Integer\"");
286 if (m_seenTypes & TypeNumber) {
290 json.appendLiteral("\"Number\"");
292 if (m_seenTypes & TypeString) {
296 json.appendLiteral("\"String\"");
298 if (m_seenTypes & TypeSymbol) {
302 json.appendLiteral("\"Symbol\"");
308 json.appendLiteral("\"structures\":");
311 for (size_t i = 0; i < m_structureHistory.size(); i++) {
315 json.append(m_structureHistory[i]->toJSONString());
320 return json.toString();
323 StructureShape::StructureShape()
325 , m_propertyHash(nullptr)
327 , m_isInDictionaryMode(false)
331 void StructureShape::markAsFinal()
337 void StructureShape::addProperty(UniquedStringImpl& uid)
343 String StructureShape::propertyHash()
347 return *m_propertyHash;
349 StringBuilder builder;
351 builder.append(m_constructorName);
353 for (auto& key : m_fields) {
354 String property = key.get();
355 property.replace(":", "\\:"); // Ensure that hash({"foo:", "bar"}) != hash({"foo", ":bar"}) because we're using colons as a separator and colons are legal characters in field names in JS.
356 builder.append(property);
361 builder.appendLiteral("__proto__");
362 builder.append(m_proto->propertyHash());
365 m_propertyHash = std::make_unique<String>(builder.toString());
366 return *m_propertyHash;
369 String StructureShape::leastCommonAncestor(const Vector<RefPtr<StructureShape>> shapes)
372 return emptyString();
374 RefPtr<StructureShape> origin = shapes.at(0);
375 for (size_t i = 1; i < shapes.size(); i++) {
376 bool foundLUB = false;
378 RefPtr<StructureShape> check = shapes.at(i);
379 String curCtorName = origin->m_constructorName;
381 if (check->m_constructorName == curCtorName) {
385 check = check->m_proto;
388 origin = origin->m_proto;
389 // All Objects must share the 'Object' Prototype. Therefore, at the very least, we should always converge on 'Object' before reaching a null prototype.
390 RELEASE_ASSERT(origin);
394 if (origin->m_constructorName == "Object")
398 return origin->m_constructorName;
401 String StructureShape::stringRepresentation()
403 StringBuilder representation;
404 RefPtr<StructureShape> curShape = this;
406 representation.append('{');
408 for (auto it = curShape->m_fields.begin(), end = curShape->m_fields.end(); it != end; ++it) {
409 String prop((*it).get());
410 representation.append(prop);
411 representation.appendLiteral(", ");
414 if (curShape->m_proto) {
415 representation.appendLiteral("__proto__ [");
416 representation.append(curShape->m_proto->m_constructorName);
417 representation.appendLiteral("], ");
420 curShape = curShape->m_proto;
423 if (representation.length() >= 3)
424 representation.resize(representation.length() - 2);
426 representation.append('}');
428 return representation.toString();
431 String StructureShape::toJSONString() const
433 // This returns a JSON string representing an Object with the following properties:
434 // constructorName: 'String'
435 // fields: 'Array<String>'
436 // optionalFields: 'Array<String>'
437 // proto: 'JSON<StructureShape> | null'
442 json.appendLiteral("\"constructorName\":");
444 json.append(m_constructorName);
448 json.appendLiteral("\"isInDictionaryMode\":");
449 if (m_isInDictionaryMode)
450 json.appendLiteral("true");
452 json.appendLiteral("false");
455 json.appendLiteral("\"fields\":");
457 bool hasAnItem = false;
458 for (auto it = m_fields.begin(), end = m_fields.end(); it != end; ++it) {
463 String fieldName((*it).get());
465 json.append(fieldName);
471 json.appendLiteral("\"optionalFields\":");
474 for (auto it = m_optionalFields.begin(), end = m_optionalFields.end(); it != end; ++it) {
479 String fieldName((*it).get());
481 json.append(fieldName);
487 json.appendLiteral("\"proto\":");
489 json.append(m_proto->toJSONString());
491 json.appendLiteral("null");
495 return json.toString();
498 Ref<Inspector::Protocol::Runtime::StructureDescription> StructureShape::inspectorRepresentation()
500 auto base = Inspector::Protocol::Runtime::StructureDescription::create().release();
501 Ref<Inspector::Protocol::Runtime::StructureDescription> currentObject = base.copyRef();
502 RefPtr<StructureShape> currentShape(this);
504 while (currentShape) {
505 auto fields = Inspector::Protocol::Array<String>::create();
506 auto optionalFields = Inspector::Protocol::Array<String>::create();
507 for (auto field : currentShape->m_fields)
508 fields->addItem(field.get());
509 for (auto field : currentShape->m_optionalFields)
510 optionalFields->addItem(field.get());
512 currentObject->setFields(&fields.get());
513 currentObject->setOptionalFields(&optionalFields.get());
514 currentObject->setConstructorName(currentShape->m_constructorName);
515 currentObject->setIsImprecise(currentShape->m_isInDictionaryMode);
517 if (currentShape->m_proto) {
518 auto nextObject = Inspector::Protocol::Runtime::StructureDescription::create().release();
519 currentObject->setPrototypeStructure(&nextObject.get());
520 currentObject = WTFMove(nextObject);
523 currentShape = currentShape->m_proto;
529 bool StructureShape::hasSamePrototypeChain(PassRefPtr<StructureShape> prpOther)
531 RefPtr<StructureShape> self = this;
532 RefPtr<StructureShape> other = prpOther;
533 while (self && other) {
534 if (self->m_constructorName != other->m_constructorName)
536 self = self->m_proto;
537 other = other->m_proto;
540 return !self && !other;
543 PassRefPtr<StructureShape> StructureShape::merge(const PassRefPtr<StructureShape> prpA, const PassRefPtr<StructureShape> prpB)
545 RefPtr<StructureShape> a = prpA;
546 RefPtr<StructureShape> b = prpB;
547 ASSERT(a->hasSamePrototypeChain(b));
549 RefPtr<StructureShape> merged = StructureShape::create();
550 for (auto field : a->m_fields) {
551 if (b->m_fields.contains(field))
552 merged->m_fields.add(field);
554 merged->m_optionalFields.add(field);
557 for (auto field : b->m_fields) {
558 if (!merged->m_fields.contains(field)) {
559 auto addResult = merged->m_optionalFields.add(field);
560 ASSERT_UNUSED(addResult, addResult.isNewEntry);
564 for (auto field : a->m_optionalFields)
565 merged->m_optionalFields.add(field);
566 for (auto field : b->m_optionalFields)
567 merged->m_optionalFields.add(field);
569 ASSERT(a->m_constructorName == b->m_constructorName);
570 merged->setConstructorName(a->m_constructorName);
573 RELEASE_ASSERT(b->m_proto);
574 merged->setProto(StructureShape::merge(a->m_proto, b->m_proto));
577 merged->markAsFinal();
579 return merged.release();
582 void StructureShape::enterDictionaryMode()
584 m_isInDictionaryMode = true;