blob: 93a30d075759182bc9421ebb711f7ab2bca38733 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library dart2js.abstract_value_domain;
import '../constants/values.dart' show ConstantValue, PrimitiveConstantValue;
import '../elements/entities.dart';
import '../elements/names.dart';
import '../elements/types.dart' show DartType;
import '../ir/static_type.dart';
import '../serialization/serialization.dart';
import '../universe/selector.dart';
import '../universe/world_builder.dart';
import '../world.dart';
/// Enum-like values used for reporting known and unknown truth values.
class AbstractBool {
final bool _value;
const AbstractBool._(this._value);
bool get isDefinitelyTrue => _value == true;
bool get isPotentiallyTrue => _value != false;
bool get isDefinitelyFalse => _value == false;
bool get isPotentiallyFalse => _value != true;
/// A value of `Abstract.True` is used when the property is known _always_ to
/// be true.
static const AbstractBool True = const AbstractBool._(true);
/// A value of `Abstract.False` is used when the property is known _never_ to
/// be true.
static const AbstractBool False = const AbstractBool._(false);
/// A value of `Abstract.Maybe` is used when the property might or might not
/// be true.
static const AbstractBool Maybe = const AbstractBool._(null);
static AbstractBool trueOrMaybe(bool value) => value ? True : Maybe;
static AbstractBool trueOrFalse(bool value) => value ? True : False;
static AbstractBool maybeOrFalse(bool value) => value ? Maybe : False;
static AbstractBool strengthen(AbstractBool a, AbstractBool b) {
//TODO(coam): Assert arguments a and b are consistent
return a.isDefinitelyTrue ? True : (a.isDefinitelyFalse ? False : b);
}
AbstractBool operator &(AbstractBool other) {
if (isDefinitelyTrue) return other;
if (other.isDefinitelyTrue) return this;
if (isDefinitelyFalse || other.isDefinitelyFalse) return False;
return Maybe;
}
AbstractBool operator |(AbstractBool other) {
if (isDefinitelyFalse) return other;
if (other.isDefinitelyFalse) return this;
if (isDefinitelyTrue || other.isDefinitelyTrue) return True;
return Maybe;
}
AbstractBool operator ~() {
if (isDefinitelyTrue) return AbstractBool.False;
if (isDefinitelyFalse) return AbstractBool.True;
return AbstractBool.Maybe;
}
@override
String toString() =>
'AbstractBool.${_value == null ? 'Maybe' : (_value ? 'True' : 'False')}';
}
/// Strategy for the abstraction of runtime values used by the global type
/// inference.
abstract class AbstractValueStrategy {
/// Creates the abstract value domain for [closedWorld].
AbstractValueDomain createDomain(JClosedWorld closedWorld);
/// Creates the [SelectorConstraintsStrategy] used by the backend enqueuer.
SelectorConstraintsStrategy createSelectorStrategy();
}
/// A value in an abstraction of runtime values.
abstract class AbstractValue {}
/// A pair of an AbstractValue and a precision flag. See
/// [AbstractValueDomain.createFromStaticType] for semantics of [isPrecise].
class AbstractValueWithPrecision {
final AbstractValue abstractValue;
final bool isPrecise;
const AbstractValueWithPrecision(this.abstractValue, this.isPrecise);
@override
String toString() =>
'AbstractValueWithPrecision($abstractValue, isPrecise: $isPrecise)';
}
/// A system that implements an abstraction over runtime values.
abstract class AbstractValueDomain {
/// The [AbstractValue] that represents an unknown runtime value.
AbstractValue get dynamicType;
/// The [AbstractValue] that represents a non-null subtype of `Type` at
/// runtime.
AbstractValue get typeType;
/// The [AbstractValue] that represents a non-null subtype of `Function` at
/// runtime.
AbstractValue get functionType;
/// The [AbstractValue] that represents a non-null subtype of `bool` at
/// runtime.
AbstractValue get boolType;
/// The [AbstractValue] that represents a non-null subtype of `int` at
/// runtime.
AbstractValue get intType;
/// The [AbstractValue] that represents a non-null subtype of `double` at
/// runtime.
AbstractValue get numNotIntType;
/// The [AbstractValue] that represents a non-null subtype of `num` at
/// runtime.
AbstractValue get numType;
/// The [AbstractValue] that represents a non-null subtype of `String` at
/// runtime.
AbstractValue get stringType;
/// The [AbstractValue] that represents a non-null subtype of `List` at
/// runtime.
AbstractValue get listType;
/// The [AbstractValue] that represents a non-null subtype of `Set` at
/// runtime.
AbstractValue get setType;
/// The [AbstractValue] that represents a non-null subtype of `Map` at
/// runtime.
AbstractValue get mapType;
/// The [AbstractValue] that represents a non-null value at runtime.
AbstractValue get nonNullType;
/// The [AbstractValue] that represents the `null` at runtime.
AbstractValue get nullType;
/// The [AbstractValue] that represents a non-null growable JavaScript array
/// at runtime.
AbstractValue get growableListType;
/// The [AbstractValue] that represents a non-null fixed size JavaScript array
/// at runtime.
AbstractValue get fixedListType;
/// The [AbstractValue] that represents the union of [growableListType] and
/// [fixedListType], i.e. JavaScript arrays that may have their elements
/// assigned.
AbstractValue get mutableArrayType;
/// The [AbstractValue] that represents a non-null 31-bit unsigned integer at
/// runtime.
AbstractValue get uint31Type;
/// The [AbstractValue] that represents a non-null 32-bit unsigned integer at
/// runtime.
AbstractValue get uint32Type;
/// The [AbstractValue] that represents a non-null unsigned integer at
/// runtime.
AbstractValue get positiveIntType;
/// The [AbstractValue] that represents a non-null constant list literal at
/// runtime.
AbstractValue get constListType;
/// The [AbstractValue] that represents a non-null constant set literal at
/// runtime.
AbstractValue get constSetType;
/// The [AbstractValue] that represents a non-null constant map literal at
/// runtime.
AbstractValue get constMapType;
/// The [AbstractValue] that represents the empty set of runtime values.
AbstractValue get emptyType;
/// The [AbstractValue] that represents a non-null instance at runtime of the
/// `Iterable` class used for the `sync*` implementation.
AbstractValue get syncStarIterableType;
/// The [AbstractValue] that represents a non-null instance at runtime of the
/// `Future` class used for the `async` implementation.
AbstractValue get asyncFutureType;
/// The [AbstractValue] that represents a non-null instance at runtime of the
/// `Stream` class used for the `async*` implementation.
AbstractValue get asyncStarStreamType;
/// Returns an [AbstractValue] and a precision flag.
///
/// Creates an [AbstractValue] corresponding to an expression of the given
/// static [type] and [classRelation], and an `isPrecise` flag that is `true`
/// if the [AbstractValue] precisely represents the [type], or `false` if the
/// [AbstractValue] is an approximation.
///
/// If `isPrecise` is `true`, then the abstract value is equivalent to [type].
///
/// If `isPrecise` is `false` then the abstract value contains all types in
/// [type] but might contain more. Two different Dart types can have the same
/// approximation, so care must be taken not to make subtype judgements from
/// imprecise abstract values.
///
/// The type `T` where `T` is a type parameter would be modelled by an
/// imprecise abstract value type since we don't know how `T` is
/// instantiated. It might be approximated by the bound of `T`. `List<T>`
/// where `T` is a type parameter would be modelled as imprecise for the same
/// reason.
///
/// If the abstract value domain does not track generic type parameters (e.g.
/// the current implementation of type-masks), then `List<int>` would need to
/// be modelled by an imprecise abstract value. `List<dynamic>` might be the
/// imprecise approximation.
///
/// In the context of a run-time type check, an imprecise abstract value can
/// be used to compute an output type by narrowing the input type, but only a
/// precise abstract value could be used to remove the check on the basis of
/// the input's abstract type. The check can only be removed with additional
/// reasoning, for example, that a dominating check uses the same type
/// expression.
///
/// [nullable] determines if the type in weak or legacy mode should be
/// interpreted as nullable. This is passed as `false` for is-tests and `true`
/// for as-checks and other contexts (e.g. parameter checks).
AbstractValueWithPrecision createFromStaticType(DartType type,
{ClassRelation classRelation = ClassRelation.subtype, bool nullable});
/// Creates an [AbstractValue] for a non-null exact instance of [cls].
AbstractValue createNonNullExact(ClassEntity cls);
/// Creates an [AbstractValue] for a potentially null exact instance of [cls].
AbstractValue createNullableExact(ClassEntity cls);
/// Creates an [AbstractValue] for a non-null instance that extends [cls].
AbstractValue createNonNullSubclass(ClassEntity cls);
/// Creates an [AbstractValue] for a non-null instance that implements [cls].
AbstractValue createNonNullSubtype(ClassEntity cls);
/// Creates an [AbstractValue] for a potentially null instance that implements
/// [cls].
AbstractValue createNullableSubtype(ClassEntity cls);
/// Returns an [AbstractBool] that describes whether [value] is a native typed
/// array or `null` at runtime.
AbstractBool isTypedArray(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] could be a native
/// typed array at runtime.
AbstractBool couldBeTypedArray(covariant AbstractValue value);
/// Returns the version of the abstract [value] that excludes `null`.
AbstractValue excludeNull(covariant AbstractValue value);
/// Returns the version of the abstract [value] that includes `null`.
AbstractValue includeNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] contains
/// instances of [cls] at runtime.
AbstractBool containsType(covariant AbstractValue value, ClassEntity cls);
/// Returns an [AbstractBool] that describes whether [value] only contains
/// subtypes of [cls] or `null` at runtime.
AbstractBool containsOnlyType(covariant AbstractValue value, ClassEntity cls);
/// Returns an [AbstractBool] that describes whether [value] is an instance of
/// [cls] or `null` at runtime.
// TODO(johnniwinther): Merge this with [isInstanceOf].
AbstractBool isInstanceOfOrNull(
covariant AbstractValue value, ClassEntity cls);
/// Returns an [AbstractBool] that describes whether [value] is known to be an
/// instance of [cls] at runtime.
AbstractBool isInstanceOf(AbstractValue value, ClassEntity cls);
/// Returns an [AbstractBool] that describes whether [value] is empty set of
/// runtime values.
AbstractBool isEmpty(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a non-null
/// exact class at runtime.
AbstractBool isExact(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is an exact class
/// or `null` at runtime.
AbstractBool isExactOrNull(covariant AbstractValue value);
/// Returns the [ClassEntity] if this [value] is a non-null instance of an
/// exact class at runtime, and `null` otherwise.
ClassEntity getExactClass(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is `null` at
/// runtime.
AbstractBool isNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// bool, number, string, array or `null` at runtime.
AbstractBool isPrimitive(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// number at runtime.
AbstractBool isPrimitiveNumber(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// bool at runtime.
AbstractBool isPrimitiveBoolean(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// array at runtime.
AbstractBool isPrimitiveArray(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// string, array, native HTML list or `null` at runtime.
AbstractBool isIndexablePrimitive(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a fixed-size
/// or constant JavaScript array or `null` at runtime.
AbstractBool isFixedArray(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a growable
/// JavaScript array or `null` at runtime.
AbstractBool isExtendableArray(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a mutable
/// JavaScript array or `null` at runtime.
AbstractBool isMutableArray(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a mutable
/// JavaScript array, native HTML list or `null` at runtime.
AbstractBool isMutableIndexable(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// array or `null` at runtime.
AbstractBool isArray(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// string at runtime.
AbstractBool isPrimitiveString(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is an interceptor
/// at runtime.
AbstractBool isInterceptor(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a non-null
/// integer value at runtime.
AbstractBool isInteger(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a non-null 32
/// bit unsigned integer value at runtime.
AbstractBool isUInt32(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a non-null 31
/// bit unsigned integer value at runtime.
AbstractBool isUInt31(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a non-null
/// unsigned integer value at runtime.
AbstractBool isPositiveInteger(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is an unsigned
/// integer value or `null` at runtime.
AbstractBool isPositiveIntegerOrNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is an integer
/// value or `null` at runtime.
AbstractBool isIntegerOrNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a non-null
/// JavaScript number at runtime.
AbstractBool isNumber(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// number or `null` at runtime.
AbstractBool isNumberOrNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// bool at runtime.
AbstractBool isBoolean(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// bool or `null` at runtime.
AbstractBool isBooleanOrNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// string at runtime.
AbstractBool isString(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// string or `null` at runtime.
AbstractBool isStringOrNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] a JavaScript
/// primitive, possible `null`.
AbstractBool isPrimitiveOrNull(covariant AbstractValue value);
/// Return an [AbstractBool] that describes whether [value] is a JavaScript
/// 'truthy' value at runtime. This is effectively symbolically evaluating the
/// abstract value as a JavaScript condition.
AbstractBool isTruthy(covariant AbstractValue value);
/// Returns [AbstractValue] for the runtime values contained in either [a] or
/// [b].
AbstractValue union(covariant AbstractValue a, covariant AbstractValue b);
/// Returns [AbstractValue] for the runtime values contained in at least one
/// of [values].
AbstractValue unionOfMany(Iterable<AbstractValue> values);
/// Returns [AbstractValue] for the runtime values that [a] and [b] have in
/// common.
AbstractValue intersection(
covariant AbstractValue a, covariant AbstractValue b);
/// Returns an [AbstractBool] that describes whether [a] and [b] have no
/// runtime values in common.
AbstractBool areDisjoint(
covariant AbstractValue a, covariant AbstractValue b);
/// Returns an [AbstractBool] that describes whether [a] contains all non-null
/// runtime values.
AbstractBool containsAll(covariant AbstractValue a);
/// Computes the [AbstractValue] corresponding to the constant [value].
AbstractValue computeAbstractValueForConstant(ConstantValue value);
/// Returns `true` if [value] represents a container value at runtime.
bool isContainer(covariant AbstractValue value);
/// Creates a container value specialization of [originalValue] with the
/// inferred [element] runtime value and inferred runtime [length].
///
/// The [allocationNode] is used to identify this particular map allocation.
/// The [allocationElement] is used only for debugging.
AbstractValue createContainerValue(
AbstractValue originalValue,
Object allocationNode,
MemberEntity allocationElement,
AbstractValue elementType,
int length);
/// Returns the element type of [value] if it represents a container value
/// at runtime. Returns [dynamicType] otherwise.
AbstractValue getContainerElementType(AbstractValue value);
/// Return the known length of [value] if it represents a container value
/// at runtime. Returns `null` if the length is unknown or if [value] doesn't
/// represent a container value at runtime.
int getContainerLength(AbstractValue value);
/// Returns `true` if [value] represents a set value at runtime.
bool isSet(covariant AbstractValue value);
/// Creates a set value specialization of [originalValue] with the inferred
/// [elementType] runtime value.
///
/// The [allocationNode] is used to identify this particular set allocation.
/// The [allocationElement] is used only for debugging.
AbstractValue createSetValue(
AbstractValue originalValue,
Object allocationNode,
MemberEntity allocationElement,
AbstractValue elementType);
/// Returns the element type of [value] if it represents a set value at
/// runtime. Returns [dynamicType] otherwise.
AbstractValue getSetElementType(AbstractValue value);
/// Returns `true` if [value] represents a map value at runtime.
bool isMap(covariant AbstractValue value);
/// Creates a map value specialization of [originalValue] with the inferred
/// [key] and [value] runtime values.
///
/// The [allocationNode] is used to identify this particular map allocation.
/// The [allocationElement] is used only for debugging.
AbstractValue createMapValue(
AbstractValue originalValue,
Object allocationNode,
MemberEntity allocationElement,
AbstractValue key,
AbstractValue value);
/// Returns the key type of [value] if it represents a map value at runtime.
/// Returns [dynamicType] otherwise.
AbstractValue getMapKeyType(AbstractValue value);
/// Returns the value type of [value] if it represents a map value at runtime.
/// Returns [dynamicType] otherwise.
AbstractValue getMapValueType(AbstractValue value);
/// Returns `true` if [value] represents a dictionary value, that is, a map
/// with strings as keys, at runtime.
bool isDictionary(covariant AbstractValue value);
/// Creates a dictionary value specialization of [originalValue] with the
/// inferred [key] and [value] runtime values.
///
/// The [allocationNode] is used to identify this particular map allocation.
/// The [allocationElement] is used only for debugging.
AbstractValue createDictionaryValue(
AbstractValue originalValue,
Object allocationNode,
MemberEntity allocationElement,
AbstractValue key,
AbstractValue value,
Map<String, AbstractValue> mappings);
/// Returns `true` if [value] is a dictionary value which contains [key] as
/// a key.
bool containsDictionaryKey(AbstractValue value, String key);
/// Returns the value type for [key] in [value] if it represents a dictionary
/// value at runtime. Returns [dynamicType] otherwise.
AbstractValue getDictionaryValueForKey(AbstractValue value, String key);
/// Returns `true` if [specialization] is a specialization of
/// [generalization].
///
/// Specializations are created through [createPrimitiveValue],
/// [createMapValue], [createDictionaryValue] and [createContainerValue].
bool isSpecializationOf(
AbstractValue specialization, AbstractValue generalization);
/// Returns the value of which [value] is a specialization. Return `null` if
/// [value] is not a specialization.
///
/// Specializations are created through [createPrimitiveValue],
/// [createMapValue], [createDictionaryValue] and [createContainerValue].
AbstractValue getGeneralization(AbstractValue value);
/// Return the object identifying the allocation of [value] if it is an
/// allocation based specialization. Otherwise returns `null`.
///
/// Allocation based specializations are created through [createMapValue],
/// [createDictionaryValue] and [createContainerValue]
Object getAllocationNode(AbstractValue value);
/// Return the allocation element of [value] if it is an allocation based
/// specialization. Otherwise returns `null`.
///
/// Allocation based specializations are created through [createMapValue],
/// [createDictionaryValue] and [createContainerValue]
MemberEntity getAllocationElement(AbstractValue value);
/// Returns `true` if [value] a known primitive JavaScript value at runtime.
bool isPrimitiveValue(covariant AbstractValue value);
/// Creates a primitive value specialization of [originalValue] with the
/// inferred primitive constant [value].
AbstractValue createPrimitiveValue(
AbstractValue originalValue, PrimitiveConstantValue value);
/// Returns the primitive JavaScript value of [value] if it represents a
/// primitive JavaScript value at runtime, value at runtime. Returns `null`
/// otherwise.
PrimitiveConstantValue getPrimitiveValue(covariant AbstractValue value);
/// Compute the type of all potential receivers of the set of live [members].
AbstractValue computeReceiver(Iterable<MemberEntity> members);
/// Returns an [AbstractBool] that describes whether [member] is a potential
/// target when being invoked on a [receiver]. [name] is used to ensure
/// library privacy is taken into account.
AbstractBool isTargetingMember(
AbstractValue receiver, MemberEntity member, Name name);
/// Returns an [AbstractBool] that describes whether [selector] invoked on a
/// [receiver] can hit a [noSuchMethod].
AbstractBool needsNoSuchMethodHandling(
AbstractValue receiver, Selector selector);
/// Returns the [AbstractValue] for the [parameterType] of a native
/// method. May return `null`, for example, if [parameterType] is not modelled
/// precisely by an [AbstractValue].
AbstractValue getAbstractValueForNativeMethodParameterType(DartType type);
/// Returns an [AbstractBool] that describes if the set of runtime values of
/// [subset] are known to all be in the set of runtime values of [superset].
AbstractBool isIn(AbstractValue subset, AbstractValue superset);
/// Returns the [MemberEntity] that is known to always be hit at runtime
/// [receiver].
///
/// Returns `null` if 0 or more than 1 member can be hit at runtime.
MemberEntity locateSingleMember(AbstractValue receiver, Selector selector);
/// Returns an [AbstractBool] that describes if [value] is known to be an
/// indexable JavaScript value at runtime.
AbstractBool isJsIndexable(covariant AbstractValue value);
/// RReturns an [AbstractBool] that describes if [value] is known to be an
/// indexable or iterable JavaScript value at runtime.
///
/// JavaScript arrays are both indexable and iterable whereas JavaScript
/// strings are indexable but not iterable.
AbstractBool isJsIndexableAndIterable(AbstractValue value);
/// Returns an [AbstractBool] that describes if [value] is known to be a
/// JavaScript indexable of fixed length.
AbstractBool isFixedLengthJsIndexable(AbstractValue value);
/// Returns compact a textual representation for [value] used for debugging.
String getCompactText(AbstractValue value);
/// Deserializes an [AbstractValue] for this domain from [source].
AbstractValue readAbstractValueFromDataSource(DataSource source);
/// Serializes this [value] for this domain to [sink].
void writeAbstractValueToDataSink(DataSink sink, AbstractValue value);
}