blob: acaa020b626827cd0f7316c467df86c00c2e07b0 [file] [log] [blame]
// Copyright (c) 2019, 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 vm.bytecode.recursive_types_validator;
import 'package:kernel/ast.dart' hide MapEntry;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'generics.dart'
show flattenInstantiatorTypeArguments, hasFreeTypeParameters;
/// Detect recursive types and validates that finalized (flattened)
/// representation of generic types is valid (finite).
class RecursiveTypesValidator {
final Set<DartType> _validatedTypes = <DartType>{};
final Set<DartType> _recursiveTypes = <DartType>{};
final Set<Class> _validatedClases = <Class>{};
/// Validates [type].
void validateType(DartType type) {
if (!isValidated(type)) {
final visitor = new _RecursiveTypesVisitor(this);
visitor.visit(type);
_validatedTypes.addAll(visitor.validated);
_recursiveTypes.addAll(visitor.recursive);
}
}
bool isValidated(DartType type) => _validatedTypes.contains(type);
/// Returns true if [type] is recursive.
/// Should be called only after validating [type].
bool isRecursive(DartType type) {
assert(isValidated(type));
return _recursiveTypes.contains(type);
}
void validateClass(Class cls) {
if (_validatedClases.add(cls)) {
try {
validateType(cls.thisType);
} on IllegalRecursiveTypeException catch (e) {
_validatedClases.remove(cls);
throw e;
}
}
}
}
class IllegalRecursiveTypeException {
final DartType type;
IllegalRecursiveTypeException(this.type);
}
class _RecursiveTypesVisitor extends DartTypeVisitor<void> {
final RecursiveTypesValidator validator;
final Set<DartType> validated = <DartType>{};
final Set<DartType> recursive = <DartType>{};
final Set<DartType> _visited = new Set<DartType>();
final List<DartType> _stack = <DartType>[];
_RecursiveTypesVisitor(this.validator);
void visit(DartType type) {
if (validator.isValidated(type)) {
return;
}
if (!_visited.add(type)) {
final int start = _stack.lastIndexOf(type);
_verifyRecursiveType(start, type);
for (int i = start; i < _stack.length; ++i) {
recursive.add(_stack[i]);
}
return;
}
_stack.add(type);
type.accept(this);
_stack.removeLast();
_visited.remove(type);
validated.add(type);
}
@override
void defaultDartType(DartType node) =>
throw 'Unexpected type ${node.runtimeType} $node';
@override
void visitInvalidType(InvalidType node) {}
@override
void visitDynamicType(DynamicType node) {}
@override
void visitVoidType(VoidType node) {}
@override
void visitBottomType(BottomType node) {}
@override
void visitTypeParameterType(TypeParameterType node) {}
@override
void visitInterfaceType(InterfaceType node) {
// Validate class declaration type separately
// to avoid failures due to types in the current _stack.
validator.validateClass(node.classNode);
for (var typeArg in node.typeArguments) {
visit(typeArg);
}
final flatTypeArgs =
flattenInstantiatorTypeArguments(node.classNode, node.typeArguments);
for (var typeArg in flatTypeArgs.getRange(
0, flatTypeArgs.length - node.typeArguments.length)) {
visit(typeArg);
}
}
@override
void visitTypedefType(TypedefType node) => visit(node.unalias);
@override
void visitFunctionType(FunctionType node) {
for (var p in node.positionalParameters) {
visit(p);
}
for (var p in node.namedParameters) {
visit(p.type);
}
visit(node.returnType);
}
void _verifyRecursiveType(int start, DartType type) {
if (type is InterfaceType) {
if (!hasFreeTypeParameters(type.typeArguments)) {
return;
}
for (int i = start + 1; i < _stack.length; ++i) {
final other = _stack[i];
if (other is InterfaceType &&
other.classNode == type.classNode &&
hasFreeTypeParameters(other.typeArguments)) {
if (!listEquals(_eraseTypeParameters(type.typeArguments),
_eraseTypeParameters(other.typeArguments))) {
throw IllegalRecursiveTypeException(type);
}
}
}
} else {
throw 'Unexpected recursive type ${type.runtimeType} $type';
}
}
List<DartType> _eraseTypeParameters(List<DartType> typeArgs) {
return typeArgs
.map((DartType t) =>
const _EraseTypeParametersToDynamic().substituteType(t))
.toList();
}
}
class _EraseTypeParametersToDynamic extends Substitution {
const _EraseTypeParametersToDynamic();
DartType getSubstitute(TypeParameter parameter, bool upperBound) {
return const DynamicType();
}
}