ae3102ed0524b8a136eed6869f3b283c0d454483
[WebKit-https.git] / Source / JavaScriptCore / runtime / TypeSet.cpp
1 /*
2  * Copyright (C) 2014, 2015 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 "TypeSet.h"
28
29 #include "InspectorProtocolObjects.h"
30 #include "JSCInlines.h"
31 #include <wtf/text/CString.h>
32 #include <wtf/text/WTFString.h>
33 #include <wtf/text/StringBuilder.h>
34 #include <wtf/Vector.h>
35
36 namespace JSC {
37
38 TypeSet::TypeSet()
39     : m_isOverflown(false)
40     , m_seenTypes(TypeNothing)
41 {
42 }
43
44 void TypeSet::addTypeInformation(RuntimeType type, RefPtr<StructureShape>&& passedNewShape, Structure* structure, bool sawPolyProtoStructure)
45 {
46     m_seenTypes = m_seenTypes | type;
47
48     if (structure && passedNewShape && !runtimeTypeIsPrimitive(type)) {
49         Ref<StructureShape> newShape = passedNewShape.releaseNonNull();
50         // FIXME: TypeSet should be able to cache poly proto chains
51         // just by caching the prototype chain:
52         // https://bugs.webkit.org/show_bug.cgi?id=177627
53         if (sawPolyProtoStructure || !m_structureSet.contains(structure)) {
54             if (!sawPolyProtoStructure) {
55                 ConcurrentJSLocker locker(m_lock);
56                 m_structureSet.add(structure);
57             }
58             // Make one more pass making sure that: 
59             // - We don't have two instances of the same shape. (Same shapes may have different Structures).
60             // - We don't have two shapes that share the same prototype chain. If these shapes share the same 
61             //   prototype chain, they will be merged into one shape.
62             String hash = newShape->propertyHash();
63             for (auto& seenShape : m_structureHistory) {
64                 if (seenShape->propertyHash() == hash)
65                     return;
66                 if (seenShape->hasSamePrototypeChain(newShape.get())) {
67                     seenShape = StructureShape::merge(seenShape.copyRef(), WTFMove(newShape));
68                     return;
69                 }
70             }
71
72             if (m_structureHistory.size() < 100) {
73                 m_structureHistory.append(WTFMove(newShape));
74                 return;
75             }
76             if (!m_isOverflown)
77                 m_isOverflown = true;
78         }
79     }
80 }
81
82 void TypeSet::invalidateCache()
83 {
84     ConcurrentJSLocker locker(m_lock);
85     auto keepMarkedStructuresFilter = [] (Structure* structure) -> bool { return Heap::isMarked(structure); };
86     m_structureSet.genericFilter(keepMarkedStructuresFilter);
87 }
88
89 String TypeSet::dumpTypes() const
90 {
91     if (m_seenTypes == TypeNothing)
92         return "(Unreached Statement)"_s;
93
94     StringBuilder seen;
95
96     if (m_seenTypes & TypeFunction)
97         seen.appendLiteral("Function ");
98     if (m_seenTypes & TypeUndefined)
99         seen.appendLiteral("Undefined ");
100     if (m_seenTypes & TypeNull)
101         seen.appendLiteral("Null ");
102     if (m_seenTypes & TypeBoolean)
103         seen.appendLiteral("Boolean ");
104     if (m_seenTypes & TypeAnyInt)
105         seen.appendLiteral("AnyInt ");
106     if (m_seenTypes & TypeNumber)
107         seen.appendLiteral("Number ");
108     if (m_seenTypes & TypeString)
109         seen.appendLiteral("String ");
110     if (m_seenTypes & TypeObject)
111         seen.appendLiteral("Object ");
112     if (m_seenTypes & TypeSymbol)
113         seen.appendLiteral("Symbol ");
114
115     for (const auto& shape : m_structureHistory) {
116         seen.append(shape->m_constructorName);
117         seen.append(' ');
118     }
119
120     if (m_structureHistory.size()) 
121         seen.appendLiteral("\nStructures:[ ");
122     for (const auto& shape : m_structureHistory) {
123         seen.append(shape->stringRepresentation());
124         seen.append(' ');
125     }
126     if (m_structureHistory.size())
127         seen.append(']');
128
129     if (m_structureHistory.size()) {
130         seen.appendLiteral("\nLeast Common Ancestor: ");
131         seen.append(leastCommonAncestor());
132     }
133
134     return seen.toString();
135 }
136
137 bool TypeSet::doesTypeConformTo(RuntimeTypeMask test) const
138 {
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.
141
142     // For example:
143
144     // 0b0110 (seen)
145     // 0b1111 (test)
146     // ------ (AND)
147     // 0b0110 == seen
148
149     // 0b0110 (seen)
150     // 0b0010 (test)
151     // ------ (AND)
152     // 0b0010 != seen
153
154     return m_seenTypes != TypeNothing && (m_seenTypes & test) == m_seenTypes;
155 }
156
157 String TypeSet::displayName() const
158 {
159     if (m_seenTypes == TypeNothing)
160         return emptyString();
161
162     if (m_structureHistory.size() && doesTypeConformTo(TypeObject | TypeNull | TypeUndefined)) {
163         String ctorName = leastCommonAncestor(); 
164
165         if (doesTypeConformTo(TypeObject))
166             return ctorName;
167         if (doesTypeConformTo(TypeObject | TypeNull | TypeUndefined))
168             return ctorName + '?';
169     }
170
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.
173
174     if (doesTypeConformTo(TypeFunction))
175         return "Function"_s;
176     if (doesTypeConformTo(TypeUndefined))
177         return "Undefined"_s;
178     if (doesTypeConformTo(TypeNull))
179         return "Null"_s;
180     if (doesTypeConformTo(TypeBoolean))
181         return "Boolean"_s;
182     if (doesTypeConformTo(TypeAnyInt))
183         return "Integer"_s;
184     if (doesTypeConformTo(TypeNumber | TypeAnyInt))
185         return "Number"_s;
186     if (doesTypeConformTo(TypeString))
187         return "String"_s;
188     if (doesTypeConformTo(TypeSymbol))
189         return "Symbol"_s;
190
191     if (doesTypeConformTo(TypeNull | TypeUndefined))
192         return "(?)"_s;
193
194     if (doesTypeConformTo(TypeFunction | TypeNull | TypeUndefined))
195         return "Function?"_s;
196     if (doesTypeConformTo(TypeBoolean | TypeNull | TypeUndefined))
197         return "Boolean?"_s;
198     if (doesTypeConformTo(TypeAnyInt | TypeNull | TypeUndefined))
199         return "Integer?"_s;
200     if (doesTypeConformTo(TypeNumber | TypeAnyInt | TypeNull | TypeUndefined))
201         return "Number?"_s;
202     if (doesTypeConformTo(TypeString | TypeNull | TypeUndefined))
203         return "String?"_s;
204     if (doesTypeConformTo(TypeSymbol | TypeNull | TypeUndefined))
205         return "Symbol?"_s;
206    
207     if (doesTypeConformTo(TypeObject | TypeFunction | TypeString))
208         return "Object"_s;
209     if (doesTypeConformTo(TypeObject | TypeFunction | TypeString | TypeNull | TypeUndefined))
210         return "Object?"_s;
211
212     return "(many)"_s;
213 }
214
215 String TypeSet::leastCommonAncestor() const
216 {
217     return StructureShape::leastCommonAncestor(m_structureHistory);
218 }
219
220 Ref<JSON::ArrayOf<Inspector::Protocol::Runtime::StructureDescription>> TypeSet::allStructureRepresentations() const
221 {
222     auto description = JSON::ArrayOf<Inspector::Protocol::Runtime::StructureDescription>::create();
223
224     for (auto& shape : m_structureHistory)
225         description->addItem(shape->inspectorRepresentation());
226
227     return description;
228 }
229
230 Ref<Inspector::Protocol::Runtime::TypeSet> TypeSet::inspectorTypeSet() const
231 {
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 & TypeAnyInt) != 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)
242         .release();
243 }
244
245 String TypeSet::toJSONString() const
246 {
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>>'
251
252     StringBuilder json;
253     json.append('{');
254
255     json.appendLiteral("\"displayTypeName\":");
256     json.append('"');
257     json.append(displayName());
258     json.append('"');
259     json.append(',');
260
261     json.appendLiteral("\"primitiveTypeNames\":");
262     json.append('[');
263     bool hasAnItem = false;
264     if (m_seenTypes & TypeUndefined) {
265         hasAnItem = true;
266         json.appendLiteral("\"Undefined\"");
267     }
268     if (m_seenTypes & TypeNull) {
269         if (hasAnItem)
270             json.append(',');
271         hasAnItem = true;
272         json.appendLiteral("\"Null\"");
273     }
274     if (m_seenTypes & TypeBoolean) {
275         if (hasAnItem)
276             json.append(',');
277         hasAnItem = true;
278         json.appendLiteral("\"Boolean\"");
279     }
280     if (m_seenTypes & TypeAnyInt) {
281         if (hasAnItem)
282             json.append(',');
283         hasAnItem = true;
284         json.appendLiteral("\"Integer\"");
285     }
286     if (m_seenTypes & TypeNumber) {
287         if (hasAnItem)
288             json.append(',');
289         hasAnItem = true;
290         json.appendLiteral("\"Number\"");
291     }
292     if (m_seenTypes & TypeString) {
293         if (hasAnItem)
294             json.append(',');
295         hasAnItem = true;
296         json.appendLiteral("\"String\"");
297     }
298     if (m_seenTypes & TypeSymbol) {
299         if (hasAnItem)
300             json.append(',');
301         hasAnItem = true;
302         json.appendLiteral("\"Symbol\"");
303     }
304     json.append(']');
305
306     json.append(',');
307
308     json.appendLiteral("\"structures\":");
309     json.append('[');
310     hasAnItem = false;
311     for (size_t i = 0; i < m_structureHistory.size(); i++) {
312         if (hasAnItem)
313             json.append(',');
314         hasAnItem = true;
315         json.append(m_structureHistory[i]->toJSONString());
316     }
317     json.append(']');
318
319     json.append('}');
320     return json.toString();
321 }
322
323 StructureShape::StructureShape()
324     : m_proto(nullptr)
325     , m_propertyHash(nullptr)
326     , m_final(false)
327     , m_isInDictionaryMode(false)
328 {
329 }
330
331 void StructureShape::markAsFinal()
332 {
333     ASSERT(!m_final);
334     m_final = true;
335 }
336
337 void StructureShape::addProperty(UniquedStringImpl& uid)
338 {
339     ASSERT(!m_final);
340     m_fields.add(&uid);
341 }
342
343 String StructureShape::propertyHash()
344 {
345     ASSERT(m_final);
346     if (m_propertyHash)
347         return *m_propertyHash;
348
349     StringBuilder builder;
350     builder.append(':');
351     builder.append(m_constructorName);
352     builder.append(':');
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);
357     }
358
359     if (m_proto) {
360         builder.append(':');
361         builder.appendLiteral("__proto__");
362         builder.append(m_proto->propertyHash());
363     }
364
365     m_propertyHash = std::make_unique<String>(builder.toString());
366     return *m_propertyHash;
367 }
368
369 String StructureShape::leastCommonAncestor(const Vector<Ref<StructureShape>>& shapes)
370 {
371     if (shapes.isEmpty())
372         return emptyString();
373
374     StructureShape* origin = shapes[0].ptr();
375     for (size_t i = 1; i < shapes.size(); i++) {
376         bool foundLUB = false;
377         while (!foundLUB) {
378             StructureShape* check = shapes[i].ptr();
379             String curCtorName = origin->m_constructorName;
380             while (check) {
381                 if (check->m_constructorName == curCtorName) {
382                     foundLUB = true;
383                     break;
384                 }
385                 check = check->m_proto.get();
386             }
387             if (!foundLUB) {
388                 // This is unlikely to happen, because we usually bottom out at "Object", but there are some sets of Objects
389                 // that may cause this behavior. We fall back to "Object" because it's our version of Top.
390                 if (!origin->m_proto)
391                     return "Object"_s;
392                 origin = origin->m_proto.get();
393             }
394         }
395
396         if (origin->m_constructorName == "Object")
397             break;
398     }
399
400     return origin->m_constructorName;
401 }
402
403 String StructureShape::stringRepresentation()
404 {
405     StringBuilder representation;
406     RefPtr<StructureShape> curShape = this;
407
408     representation.append('{');
409     while (curShape) {
410         for (auto it = curShape->m_fields.begin(), end = curShape->m_fields.end(); it != end; ++it) {
411             String prop((*it).get());
412             representation.append(prop);
413             representation.appendLiteral(", ");
414         }
415
416         if (curShape->m_proto) {
417             representation.appendLiteral("__proto__ [");
418             representation.append(curShape->m_proto->m_constructorName);
419             representation.appendLiteral("], ");
420         }
421
422         curShape = curShape->m_proto;
423     }
424
425     if (representation.length() >= 3)
426         representation.resize(representation.length() - 2);
427
428     representation.append('}');
429
430     return representation.toString();
431 }
432
433 String StructureShape::toJSONString() const
434 {
435     // This returns a JSON string representing an Object with the following properties:
436     //     constructorName: 'String'
437     //     fields: 'Array<String>'
438     //     optionalFields: 'Array<String>'
439     //     proto: 'JSON<StructureShape> | null'
440
441     StringBuilder json;
442     json.append('{');
443
444     json.appendLiteral("\"constructorName\":");
445     json.append('"');
446     json.append(m_constructorName);
447     json.append('"');
448     json.append(',');
449
450     json.appendLiteral("\"isInDictionaryMode\":");
451     if (m_isInDictionaryMode)
452         json.appendLiteral("true");
453     else
454         json.appendLiteral("false");
455     json.append(',');
456
457     json.appendLiteral("\"fields\":");
458     json.append('[');
459     bool hasAnItem = false;
460     for (auto it = m_fields.begin(), end = m_fields.end(); it != end; ++it) {
461         if (hasAnItem)
462             json.append(',');
463         hasAnItem = true;
464
465         String fieldName((*it).get());
466         json.append('"');
467         json.append(fieldName);
468         json.append('"');
469     }
470     json.append(']');
471     json.append(',');
472
473     json.appendLiteral("\"optionalFields\":");
474     json.append('[');
475     hasAnItem = false;
476     for (auto it = m_optionalFields.begin(), end = m_optionalFields.end(); it != end; ++it) {
477         if (hasAnItem)
478             json.append(',');
479         hasAnItem = true;
480
481         String fieldName((*it).get());
482         json.append('"');
483         json.append(fieldName);
484         json.append('"');
485     }
486     json.append(']');
487     json.append(',');
488
489     json.appendLiteral("\"proto\":");
490     if (m_proto)
491         json.append(m_proto->toJSONString());
492     else
493         json.appendLiteral("null");
494
495     json.append('}');
496
497     return json.toString();
498 }
499
500 Ref<Inspector::Protocol::Runtime::StructureDescription> StructureShape::inspectorRepresentation()
501 {
502     auto base = Inspector::Protocol::Runtime::StructureDescription::create().release();
503     Ref<Inspector::Protocol::Runtime::StructureDescription> currentObject = base.copyRef();
504     RefPtr<StructureShape> currentShape(this);
505
506     while (currentShape) {
507         auto fields = JSON::ArrayOf<String>::create();
508         auto optionalFields = JSON::ArrayOf<String>::create();
509         for (auto field : currentShape->m_fields)
510             fields->addItem(field.get());
511         for (auto field : currentShape->m_optionalFields)
512             optionalFields->addItem(field.get());
513
514         currentObject->setFields(&fields.get());
515         currentObject->setOptionalFields(&optionalFields.get());
516         currentObject->setConstructorName(currentShape->m_constructorName);
517         currentObject->setIsImprecise(currentShape->m_isInDictionaryMode);
518
519         if (currentShape->m_proto) {
520             auto nextObject = Inspector::Protocol::Runtime::StructureDescription::create().release();
521             currentObject->setPrototypeStructure(&nextObject.get());
522             currentObject = WTFMove(nextObject);
523         }
524
525         currentShape = currentShape->m_proto;
526     }
527
528     return base;
529 }
530
531 bool StructureShape::hasSamePrototypeChain(const StructureShape& otherRef)
532 {
533     const StructureShape* self = this;
534     const StructureShape* other = &otherRef;
535     while (self && other) {
536         if (self->m_constructorName != other->m_constructorName)
537             return false;
538         self = self->m_proto.get();
539         other = other->m_proto.get();
540     }
541
542     return !self && !other;
543 }
544
545 Ref<StructureShape> StructureShape::merge(Ref<StructureShape>&& a, Ref<StructureShape>&& b)
546 {
547     ASSERT(a->hasSamePrototypeChain(b.get()));
548
549     auto merged = StructureShape::create();
550     for (auto field : a->m_fields) {
551         if (b->m_fields.contains(field))
552             merged->m_fields.add(field);
553         else
554             merged->m_optionalFields.add(field);
555     }
556
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);
561         }
562     }
563
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);
568
569     ASSERT(a->m_constructorName == b->m_constructorName);
570     merged->setConstructorName(a->m_constructorName);
571
572     if (a->m_proto) {
573         RELEASE_ASSERT(b->m_proto);
574         merged->setProto(StructureShape::merge(*a->m_proto, *b->m_proto));
575     }
576
577     merged->markAsFinal();
578
579     return merged;
580 }
581
582 void StructureShape::enterDictionaryMode()
583 {
584     m_isInDictionaryMode = true;
585 }
586
587 } // namespace JSC