blob: 9db1d880a6e40c2e6c6f5367dca73825611a2652 [file] [log] [blame]
/*
* Copyright (C) 2014-2019 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "TypeSet.h"
#include "InspectorProtocolObjects.h"
#include "JSCInlines.h"
#include <wtf/text/CString.h>
#include <wtf/text/WTFString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/Vector.h>
namespace JSC {
TypeSet::TypeSet()
: m_isOverflown(false)
, m_seenTypes(TypeNothing)
{
}
void TypeSet::addTypeInformation(RuntimeType type, RefPtr<StructureShape>&& passedNewShape, Structure* structure, bool sawPolyProtoStructure)
{
m_seenTypes = m_seenTypes | type;
if (structure && passedNewShape && !runtimeTypeIsPrimitive(type)) {
Ref<StructureShape> newShape = passedNewShape.releaseNonNull();
// FIXME: TypeSet should be able to cache poly proto chains
// just by caching the prototype chain:
// https://bugs.webkit.org/show_bug.cgi?id=177627
if (sawPolyProtoStructure || !m_structureSet.contains(structure)) {
if (!sawPolyProtoStructure) {
ConcurrentJSLocker locker(m_lock);
m_structureSet.add(structure);
}
// Make one more pass making sure that:
// - We don't have two instances of the same shape. (Same shapes may have different Structures).
// - We don't have two shapes that share the same prototype chain. If these shapes share the same
// prototype chain, they will be merged into one shape.
String hash = newShape->propertyHash();
for (auto& seenShape : m_structureHistory) {
if (seenShape->propertyHash() == hash)
return;
if (seenShape->hasSamePrototypeChain(newShape.get())) {
seenShape = StructureShape::merge(seenShape.copyRef(), WTFMove(newShape));
return;
}
}
if (m_structureHistory.size() < 100) {
m_structureHistory.append(WTFMove(newShape));
return;
}
if (!m_isOverflown)
m_isOverflown = true;
}
}
}
void TypeSet::invalidateCache(VM& vm)
{
ConcurrentJSLocker locker(m_lock);
auto keepMarkedStructuresFilter = [&] (Structure* structure) -> bool {
return vm.heap.isMarked(structure);
};
m_structureSet.genericFilter(keepMarkedStructuresFilter);
}
String TypeSet::dumpTypes() const
{
if (m_seenTypes == TypeNothing)
return "(Unreached Statement)"_s;
StringBuilder seen;
if (m_seenTypes & TypeFunction)
seen.appendLiteral("Function ");
if (m_seenTypes & TypeUndefined)
seen.appendLiteral("Undefined ");
if (m_seenTypes & TypeNull)
seen.appendLiteral("Null ");
if (m_seenTypes & TypeBoolean)
seen.appendLiteral("Boolean ");
if (m_seenTypes & TypeAnyInt)
seen.appendLiteral("AnyInt ");
if (m_seenTypes & TypeNumber)
seen.appendLiteral("Number ");
if (m_seenTypes & TypeString)
seen.appendLiteral("String ");
if (m_seenTypes & TypeObject)
seen.appendLiteral("Object ");
if (m_seenTypes & TypeSymbol)
seen.appendLiteral("Symbol ");
for (const auto& shape : m_structureHistory) {
seen.append(shape->m_constructorName);
seen.append(' ');
}
if (m_structureHistory.size())
seen.appendLiteral("\nStructures:[ ");
for (const auto& shape : m_structureHistory) {
seen.append(shape->stringRepresentation());
seen.append(' ');
}
if (m_structureHistory.size())
seen.append(']');
if (m_structureHistory.size()) {
seen.appendLiteral("\nLeast Common Ancestor: ");
seen.append(leastCommonAncestor());
}
return seen.toString();
}
bool TypeSet::doesTypeConformTo(RuntimeTypeMask test) const
{
// 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).
// We are <= to those types if ANDing with the bitstring doesn't zero out any of our bits.
// For example:
// 0b0110 (seen)
// 0b1111 (test)
// ------ (AND)
// 0b0110 == seen
// 0b0110 (seen)
// 0b0010 (test)
// ------ (AND)
// 0b0010 != seen
return m_seenTypes != TypeNothing && (m_seenTypes & test) == m_seenTypes;
}
String TypeSet::displayName() const
{
if (m_seenTypes == TypeNothing)
return emptyString();
if (m_structureHistory.size() && doesTypeConformTo(TypeObject | TypeNull | TypeUndefined)) {
String ctorName = leastCommonAncestor();
if (doesTypeConformTo(TypeObject))
return ctorName;
if (doesTypeConformTo(TypeObject | TypeNull | TypeUndefined))
return ctorName + '?';
}
// 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.
// Therefore, more specific types must be checked first.
if (doesTypeConformTo(TypeFunction))
return "Function"_s;
if (doesTypeConformTo(TypeUndefined))
return "Undefined"_s;
if (doesTypeConformTo(TypeNull))
return "Null"_s;
if (doesTypeConformTo(TypeBoolean))
return "Boolean"_s;
if (doesTypeConformTo(TypeAnyInt))
return "Integer"_s;
if (doesTypeConformTo(TypeNumber | TypeAnyInt))
return "Number"_s;
if (doesTypeConformTo(TypeString))
return "String"_s;
if (doesTypeConformTo(TypeSymbol))
return "Symbol"_s;
if (doesTypeConformTo(TypeBigInt))
return "BigInt"_s;
if (doesTypeConformTo(TypeNull | TypeUndefined))
return "(?)"_s;
if (doesTypeConformTo(TypeFunction | TypeNull | TypeUndefined))
return "Function?"_s;
if (doesTypeConformTo(TypeBoolean | TypeNull | TypeUndefined))
return "Boolean?"_s;
if (doesTypeConformTo(TypeAnyInt | TypeNull | TypeUndefined))
return "Integer?"_s;
if (doesTypeConformTo(TypeNumber | TypeAnyInt | TypeNull | TypeUndefined))
return "Number?"_s;
if (doesTypeConformTo(TypeString | TypeNull | TypeUndefined))
return "String?"_s;
if (doesTypeConformTo(TypeSymbol | TypeNull | TypeUndefined))
return "Symbol?"_s;
if (doesTypeConformTo(TypeBigInt | TypeNull | TypeUndefined))
return "BigInt?"_s;
if (doesTypeConformTo(TypeObject | TypeFunction | TypeString))
return "Object"_s;
if (doesTypeConformTo(TypeObject | TypeFunction | TypeString | TypeNull | TypeUndefined))
return "Object?"_s;
return "(many)"_s;
}
String TypeSet::leastCommonAncestor() const
{
return StructureShape::leastCommonAncestor(m_structureHistory);
}
Ref<JSON::ArrayOf<Inspector::Protocol::Runtime::StructureDescription>> TypeSet::allStructureRepresentations() const
{
auto description = JSON::ArrayOf<Inspector::Protocol::Runtime::StructureDescription>::create();
for (auto& shape : m_structureHistory)
description->addItem(shape->inspectorRepresentation());
return description;
}
Ref<Inspector::Protocol::Runtime::TypeSet> TypeSet::inspectorTypeSet() const
{
return Inspector::Protocol::Runtime::TypeSet::create()
.setIsFunction((m_seenTypes & TypeFunction) != TypeNothing)
.setIsUndefined((m_seenTypes & TypeUndefined) != TypeNothing)
.setIsNull((m_seenTypes & TypeNull) != TypeNothing)
.setIsBoolean((m_seenTypes & TypeBoolean) != TypeNothing)
.setIsInteger((m_seenTypes & TypeAnyInt) != TypeNothing)
.setIsNumber((m_seenTypes & TypeNumber) != TypeNothing)
.setIsString((m_seenTypes & TypeString) != TypeNothing)
.setIsObject((m_seenTypes & TypeObject) != TypeNothing)
.setIsSymbol((m_seenTypes & TypeSymbol) != TypeNothing)
.setIsBigInt((m_seenTypes & TypeBigInt) != TypeNothing)
.release();
}
String TypeSet::toJSONString() const
{
// This returns a JSON string representing an Object with the following properties:
// displayTypeName: 'String'
// primitiveTypeNames: 'Array<String>'
// structures: 'Array<JSON<StructureShape>>'
StringBuilder json;
json.append('{');
json.appendLiteral("\"displayTypeName\":");
json.appendQuotedJSONString(displayName());
json.append(',');
json.appendLiteral("\"primitiveTypeNames\":");
json.append('[');
bool hasAnItem = false;
if (m_seenTypes & TypeUndefined) {
hasAnItem = true;
json.appendLiteral("\"Undefined\"");
}
if (m_seenTypes & TypeNull) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
json.appendLiteral("\"Null\"");
}
if (m_seenTypes & TypeBoolean) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
json.appendLiteral("\"Boolean\"");
}
if (m_seenTypes & TypeAnyInt) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
json.appendLiteral("\"Integer\"");
}
if (m_seenTypes & TypeNumber) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
json.appendLiteral("\"Number\"");
}
if (m_seenTypes & TypeString) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
json.appendLiteral("\"String\"");
}
if (m_seenTypes & TypeSymbol) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
json.appendLiteral("\"Symbol\"");
}
json.append(']');
json.append(',');
json.appendLiteral("\"structures\":");
json.append('[');
hasAnItem = false;
for (size_t i = 0; i < m_structureHistory.size(); i++) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
json.append(m_structureHistory[i]->toJSONString());
}
json.append(']');
json.append('}');
return json.toString();
}
StructureShape::StructureShape()
: m_final(false)
, m_isInDictionaryMode(false)
, m_proto(nullptr)
, m_propertyHash(nullptr)
{
}
void StructureShape::markAsFinal()
{
ASSERT(!m_final);
m_final = true;
}
void StructureShape::addProperty(UniquedStringImpl& uid)
{
ASSERT(!m_final);
m_fields.add(&uid);
}
String StructureShape::propertyHash()
{
ASSERT(m_final);
if (m_propertyHash)
return *m_propertyHash;
StringBuilder builder;
builder.append(':');
builder.append(m_constructorName);
builder.append(':');
for (auto& key : m_fields) {
String property = key.get();
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.
builder.append(property);
}
if (m_proto) {
builder.append(':');
builder.appendLiteral("__proto__");
builder.append(m_proto->propertyHash());
}
m_propertyHash = makeUnique<String>(builder.toString());
return *m_propertyHash;
}
String StructureShape::leastCommonAncestor(const Vector<Ref<StructureShape>>& shapes)
{
if (shapes.isEmpty())
return emptyString();
StructureShape* origin = shapes[0].ptr();
for (size_t i = 1; i < shapes.size(); i++) {
bool foundLUB = false;
while (!foundLUB) {
StructureShape* check = shapes[i].ptr();
String curCtorName = origin->m_constructorName;
while (check) {
if (check->m_constructorName == curCtorName) {
foundLUB = true;
break;
}
check = check->m_proto.get();
}
if (!foundLUB) {
// This is unlikely to happen, because we usually bottom out at "Object", but there are some sets of Objects
// that may cause this behavior. We fall back to "Object" because it's our version of Top.
if (!origin->m_proto)
return "Object"_s;
origin = origin->m_proto.get();
}
}
if (origin->m_constructorName == "Object")
break;
}
return origin->m_constructorName;
}
String StructureShape::stringRepresentation()
{
StringBuilder representation;
RefPtr<StructureShape> curShape = this;
representation.append('{');
while (curShape) {
for (auto& field : curShape->m_fields)
representation.append(StringView { field.get() }, ", ");
if (curShape->m_proto)
representation.append("__proto__ [", curShape->m_proto->m_constructorName, "], ");
curShape = curShape->m_proto;
}
if (representation.length() >= 3)
representation.resize(representation.length() - 2);
representation.append('}');
return representation.toString();
}
String StructureShape::toJSONString() const
{
// This returns a JSON string representing an Object with the following properties:
// constructorName: 'String'
// fields: 'Array<String>'
// optionalFields: 'Array<String>'
// proto: 'JSON<StructureShape> | null'
StringBuilder json;
json.append('{');
json.appendLiteral("\"constructorName\":");
json.appendQuotedJSONString(m_constructorName);
json.append(',');
json.appendLiteral("\"isInDictionaryMode\":");
if (m_isInDictionaryMode)
json.appendLiteral("true");
else
json.appendLiteral("false");
json.append(',');
json.appendLiteral("\"fields\":");
json.append('[');
bool hasAnItem = false;
for (auto it = m_fields.begin(), end = m_fields.end(); it != end; ++it) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
String fieldName((*it).get());
json.appendQuotedJSONString(fieldName);
}
json.append(']');
json.append(',');
json.appendLiteral("\"optionalFields\":");
json.append('[');
hasAnItem = false;
for (auto it = m_optionalFields.begin(), end = m_optionalFields.end(); it != end; ++it) {
if (hasAnItem)
json.append(',');
hasAnItem = true;
String fieldName((*it).get());
json.appendQuotedJSONString(fieldName);
}
json.append(']');
json.append(',');
json.appendLiteral("\"proto\":");
if (m_proto)
json.append(m_proto->toJSONString());
else
json.appendLiteral("null");
json.append('}');
return json.toString();
}
Ref<Inspector::Protocol::Runtime::StructureDescription> StructureShape::inspectorRepresentation()
{
auto base = Inspector::Protocol::Runtime::StructureDescription::create().release();
Ref<Inspector::Protocol::Runtime::StructureDescription> currentObject = base.copyRef();
RefPtr<StructureShape> currentShape(this);
while (currentShape) {
auto fields = JSON::ArrayOf<String>::create();
auto optionalFields = JSON::ArrayOf<String>::create();
for (const auto& field : currentShape->m_fields)
fields->addItem(field.get());
for (const auto& field : currentShape->m_optionalFields)
optionalFields->addItem(field.get());
currentObject->setFields(&fields.get());
currentObject->setOptionalFields(&optionalFields.get());
currentObject->setConstructorName(currentShape->m_constructorName);
currentObject->setIsImprecise(currentShape->m_isInDictionaryMode);
if (currentShape->m_proto) {
auto nextObject = Inspector::Protocol::Runtime::StructureDescription::create().release();
currentObject->setPrototypeStructure(&nextObject.get());
currentObject = WTFMove(nextObject);
}
currentShape = currentShape->m_proto;
}
return base;
}
bool StructureShape::hasSamePrototypeChain(const StructureShape& otherRef)
{
const StructureShape* self = this;
const StructureShape* other = &otherRef;
while (self && other) {
if (self->m_constructorName != other->m_constructorName)
return false;
self = self->m_proto.get();
other = other->m_proto.get();
}
return !self && !other;
}
Ref<StructureShape> StructureShape::merge(Ref<StructureShape>&& a, Ref<StructureShape>&& b)
{
ASSERT(a->hasSamePrototypeChain(b.get()));
auto merged = StructureShape::create();
for (const auto& field : a->m_fields) {
if (b->m_fields.contains(field))
merged->m_fields.add(field);
else
merged->m_optionalFields.add(field);
}
for (const auto& field : b->m_fields) {
if (!merged->m_fields.contains(field)) {
auto addResult = merged->m_optionalFields.add(field);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
}
}
for (const auto& field : a->m_optionalFields)
merged->m_optionalFields.add(field);
for (const auto& field : b->m_optionalFields)
merged->m_optionalFields.add(field);
ASSERT(a->m_constructorName == b->m_constructorName);
merged->setConstructorName(a->m_constructorName);
if (a->m_proto) {
RELEASE_ASSERT(b->m_proto);
merged->setProto(StructureShape::merge(*a->m_proto, *b->m_proto));
}
merged->markAsFinal();
return merged;
}
void StructureShape::enterDictionaryMode()
{
m_isInDictionaryMode = true;
}
} // namespace JSC