Files
sdk/pkg/kernel/lib/verifier.dart
T
Johnni Winther e30cd0322c [cfe][Contexts] Split VariableDeclaration and VariableStatement
This separates VariableDeclaration from Statement. VariableDeclaration no longer implements Statement and variable declared in a block or in a for-statement are now wrapped by a VariableStatement.

Currently there are two VariableStatement implementations; LegacyVariableStatement for variables in the current model, called LegacyVariable, and VariableInitialization for variables used in the new, still experimental, encoding that supports scope computation.

This CL is a step towards realigning the AST nodes to the new model in which each kind of variable has its own distinct subclass. (LocalVariable, PositionalParameter, NamedParameter, SyntheticVariable, etc.)

Note that it is not the intent to use VariableStatement in ForStatement going forward but that will be handled in a follow-up.

TEST=existing.

Change-Id: I5b309cd62c9b138f95b74fb054686edffa49a393
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502681
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
2026-05-18 05:49:28 -07:00

2270 lines
68 KiB
Dart

// Copyright (c) 2016, 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 kernel.checks;
import 'ast.dart';
import 'target/targets.dart';
import 'transformations/flags.dart';
import 'type_environment.dart' show StatefulStaticTypeContext, TypeEnvironment;
/// Stages at which verification can occur.
///
/// These can be used to enforce different invariants during different stages
/// of the compilation.
enum VerificationStage {
/// Verification after the outline compilation.
outline,
/// Verification after the body, aka full, compilation, but before pre-
/// constant evaluation transformations have been performed.
beforePreConstantEvaluationTransformations,
/// Verification after pre- constant evaluation transformations have been
/// performed but before constant evaluation.
beforeConstantEvaluation,
/// Verification after constant evaluation but before modular transformations
/// have been performed.
afterConstantEvaluation,
/// Verification after modular transformations have been performed.
///
/// This is final stage of a normal compilation.
afterModularTransformations,
/// Verification after global transformations have been performed.
///
/// The global transformation is an additional step performed by some
/// backends which is not triggered by the front end compilation itself.
afterGlobalTransformations;
bool operator <(VerificationStage other) => index < other.index;
bool operator <=(VerificationStage other) => index <= other.index;
bool operator >(VerificationStage other) => index > other.index;
bool operator >=(VerificationStage other) => index >= other.index;
}
/// Interface that defines how the AST is verified.
class Verification {
const Verification();
/// Returns `true` if [node] is allowed to have no file offset.
bool allowNoFileOffset(VerificationStage stage, TreeNode node) {
return node is Library;
}
/// Returns `true` if [node] is allowed to have location with a file offset
/// that is not in the range of the enclosing file.
bool allowInvalidLocation(VerificationStage stage, TreeNode node) {
return false;
}
}
void verifyComponent(
Target target,
VerificationStage stage,
Component component, {
bool skipPlatform = false,
bool Function(Library library)? librarySkipFilter,
}) {
VerifyingVisitor.check(
target,
stage,
component,
skipPlatform: skipPlatform,
librarySkipFilter: librarySkipFilter,
);
}
class VerificationErrorListener {
const VerificationErrorListener();
void reportError(
String details, {
required TreeNode? node,
required Uri? problemUri,
required int? problemOffset,
required TreeNode? context,
required TreeNode? origin,
}) {
throw new VerificationError(context, node, details);
}
}
class VerificationError {
final TreeNode? context;
final TreeNode? node;
final String details;
VerificationError(this.context, this.node, this.details);
@override
String toString() {
Location? location;
try {
location = node?.location ?? context?.location;
} catch (_) {
// TODO(ahe): Fix the compiler instead.
}
if (location != null) {
String file = location.file.toString();
return "$file:${location.line}:${location.column}: Verification error:"
" $details";
} else {
return "Verification error: $details\n"
"Context: '$context'.\n"
"Node: '$node'.";
}
}
}
enum TypedefState { Done, BeingChecked }
/// Checks that a kernel component is well-formed.
///
/// This does not include any kind of type checking.
class VerifyingVisitor extends RecursiveResultVisitor<void> {
final Target target;
Uri? fileUri;
final VerificationErrorListener listener;
final List<TreeNode> treeNodeStack = <TreeNode>[];
final bool skipPlatform;
final bool Function(Library library)? librarySkipFilter;
final Set<Class> classes = new Set<Class>();
final Set<Typedef> typedefs = new Set<Typedef>();
Set<TypeParameter> typeParametersInScope = new Set<TypeParameter>();
Set<StructuralParameter> structuralParametersInScope =
new Set<StructuralParameter>();
Set<VariableDeclaration> variableDeclarationsInScope =
new Set<VariableDeclaration>();
final List<VariableDeclaration> variableStack = <VariableDeclaration>[];
final Map<Typedef, TypedefState> typedefState = <Typedef, TypedefState>{};
final Set<Constant> seenConstants = <Constant>{};
Map<Reference, ExtensionMemberDescriptor>? _extensionsMembers;
Map<Reference, ExtensionTypeMemberDescriptor>? _extensionTypeMembers;
bool classTypeParametersAreInScope = false;
/// The compilation stage at which this verification is performed.
final VerificationStage stage;
AsyncMarker currentAsyncMarker = AsyncMarker.Sync;
bool inCatchBlock = false;
bool inUnevaluatedConstant = false;
bool inConstant = false;
Library? currentLibrary;
Member? currentMember;
Class? currentClass;
Extension? currentExtension;
ExtensionTypeDeclaration? currentExtensionTypeDeclaration;
TreeNode? currentParent;
TreeNode? get currentClassOrExtensionOrMember =>
currentMember ??
currentClass ??
currentExtension ??
currentExtensionTypeDeclaration;
static void check(
Target target,
VerificationStage stage,
Component component, {
required bool skipPlatform,
bool Function(Library library)? librarySkipFilter,
}) {
component.accept(
new VerifyingVisitor(
target,
stage,
skipPlatform: skipPlatform,
librarySkipFilter: librarySkipFilter,
),
);
}
VerifyingVisitor(
this.target,
this.stage, {
required this.skipPlatform,
required this.librarySkipFilter,
VerificationErrorListener this.listener = const VerificationErrorListener(),
});
/// If true, relax certain checks for *outline* mode. For example, don't
/// attempt to validate constructor initializers.
bool get isOutline => stage == VerificationStage.outline;
/// If true, assume that constant evaluation has been performed (with a
/// target that did not opt out of any of the constant inlining) and report
/// a verification error for anything that should have been removed by it.
bool get afterConst => stage >= VerificationStage.afterConstantEvaluation;
/// If true, constant fields and local variables are expected to be inlined.
bool get constantsAreAlwaysInlined =>
target.constantsBackend.alwaysInlineConstants;
/// If true, constant local variables are expected to have been removed.
bool get constantLocalsShouldBeRemoved => !target.constantsBackend.keepLocals;
@override
void defaultTreeNode(TreeNode node) {
enterTreeNode(node);
visitChildren(node);
exitTreeNode(node);
}
@override
void defaultConstantReference(Constant constant) {
if (seenConstants.add(constant)) {
constant.accept(this);
}
}
@override
void defaultConstant(Constant constant) {
constant.visitChildren(this);
}
void problem(
TreeNode? node,
String details, {
TreeNode? context,
TreeNode? origin,
}) {
TreeNode? problemNode = node ?? context ?? currentClassOrExtensionOrMember;
int offset = problemNode?.fileOffset ?? -1;
Location? location = problemNode != null
? _getLocation(problemNode, allowInvalidLocation: true)
: null;
Uri? file = location?.file ?? fileUri;
Uri? uri = file == null ? null : file;
String verifierState = 'Target=${target.name}, $stage: ';
listener.reportError(
'$verifierState$details',
problemUri: uri,
problemOffset: offset,
node: node,
context: context ?? currentClassOrExtensionOrMember,
origin: origin,
);
}
// TODO(cstefantsova): Remove this method when the new variable model is
// supported.
bool _isNewModelVariable(TreeNode node) {
return node is VariableDeclaration && node is! LegacyVariable ||
node is FunctionParameter;
}
TreeNode? enterParent(TreeNode node) {
// TODO(cstefantsova): Support new variable model.
if (!_isNewModelVariable(node) && !identical(node.parent, currentParent)) {
problem(
node,
"Incorrect parent pointer on ${node}:"
" expected ${currentParent},"
" but found: ${node.parent}.",
context: currentParent,
);
}
TreeNode? oldParent = currentParent;
currentParent = node;
return oldParent;
}
void exitParent(TreeNode? oldParent) {
currentParent = oldParent;
}
int enterLocalScope() => variableStack.length;
void exitLocalScope(int stackHeight) {
for (int i = stackHeight; i < variableStack.length; ++i) {
undeclareVariable(variableStack[i]);
}
variableStack.length = stackHeight;
}
/// Calls [f] with [node] set up as the parent node.
void inTreeNode(TreeNode node, void Function() f) {
TreeNode? oldParent = enterParent(node);
f();
exitParent(oldParent);
}
void visitChildren(TreeNode node) {
inTreeNode(node, () => node.visitChildren(this));
}
void visitWithLocalScope(TreeNode node) {
enterTreeNode(node);
int stackHeight = enterLocalScope();
visitChildren(node);
exitLocalScope(stackHeight);
exitTreeNode(node);
}
void declareVariable(VariableDeclaration variable) {
if (variableDeclarationsInScope.contains(variable)) {
problem(variable, "Variable '$variable' declared more than once.");
}
variableDeclarationsInScope.add(variable);
variableStack.add(variable);
}
void undeclareVariable(VariableDeclaration variable) {
variableDeclarationsInScope.remove(variable);
}
void declareTypeParameters(List<TypeParameter> parameters) {
for (int i = 0; i < parameters.length; ++i) {
TypeParameter parameter = parameters[i];
if (identical(parameter.bound, TypeParameter.unsetBoundSentinel)) {
problem(
currentParent,
"Missing bound for type parameter '$parameter'.",
);
}
if (identical(
parameter.defaultType,
TypeParameter.unsetDefaultTypeSentinel,
)) {
problem(
currentParent,
"Missing default type for type parameter '$parameter'.",
);
}
if (!typeParametersInScope.add(parameter)) {
problem(parameter, "Type parameter '$parameter' redeclared.");
}
}
}
void declareStructuralParameters(List<StructuralParameter> parameters) {
for (int i = 0; i < parameters.length; ++i) {
StructuralParameter parameter = parameters[i];
if (identical(parameter.bound, StructuralParameter.unsetBoundSentinel)) {
problem(
currentParent,
"Missing bound for type parameter '$parameter'.",
);
}
if (identical(
parameter.defaultType,
StructuralParameter.unsetDefaultTypeSentinel,
)) {
problem(
currentParent,
"Missing default type for type parameter '$parameter'.",
);
}
if (!structuralParametersInScope.add(parameter)) {
problem(currentParent, "Type parameter '$parameter' redeclared.");
}
}
}
void undeclareTypeParameters(List<TypeParameter> parameters) {
typeParametersInScope.removeAll(parameters);
}
void undeclareStructuralParameters(List<StructuralParameter> parameters) {
structuralParametersInScope.removeAll(parameters);
}
void checkVariableInScope(VariableDeclaration variable, TreeNode where) {
if (!variableDeclarationsInScope.contains(variable)) {
problem(where, "Variable '$variable' used out of scope.");
}
}
@override
void visitComponent(Component component) {
void declareMember(Member member) {
if (member.transformerFlags & TransformerFlag.seenByVerifier != 0) {
problem(
member.function,
"Member '$member' has been declared more than once.",
);
}
member.transformerFlags |= TransformerFlag.seenByVerifier;
}
void undeclareMember(Member member) {
member.transformerFlags &= ~TransformerFlag.seenByVerifier;
}
try {
for (Library library in component.libraries) {
for (Class class_ in library.classes) {
if (!classes.add(class_)) {
problem(class_, "Class '$class_' declared more than once.");
}
}
for (Typedef typedef_ in library.typedefs) {
if (!typedefs.add(typedef_)) {
problem(typedef_, "Typedef '$typedef_' declared more than once.");
}
}
library.forEachMember(declareMember);
for (Class class_ in library.classes) {
class_.forEachMember(declareMember);
}
for (ExtensionTypeDeclaration extensionTypeDeclaration
in library.extensionTypeDeclarations) {
extensionTypeDeclaration.procedures.forEach(declareMember);
}
}
visitChildren(component);
} finally {
for (Library library in component.libraries) {
library.forEachMember(undeclareMember);
for (Class class_ in library.classes) {
class_.forEachMember(undeclareMember);
}
for (ExtensionTypeDeclaration extensionTypeDeclaration
in library.extensionTypeDeclarations) {
extensionTypeDeclaration.procedures.forEach(undeclareMember);
}
}
variableStack.forEach(undeclareVariable);
}
}
@override
void visitLibrary(Library node) {
if (skipPlatform &&
node.importUri.isScheme('dart') &&
// 'dart:test' is used in the unit tests and isn't an actual part of the
// platform so we don't skip its verification.
node.importUri.path != 'test') {
return;
}
if (librarySkipFilter != null && librarySkipFilter!(node)) {
return;
}
enterTreeNode(node);
fileUri = checkLocation(node, node.name, node.fileUri);
currentLibrary = node;
TreeNode? oldParent = enterParent(node);
_visitAnnotations(node.annotations);
visitList(node.dependencies, this);
visitList(node.parts, this);
visitList(node.typedefs, this);
visitList(node.classes, this);
visitList(node.extensions, this);
visitList(node.extensionTypeDeclarations, this);
visitList(node.procedures, this);
visitList(node.fields, this);
exitParent(oldParent);
currentLibrary = null;
exitTreeNode(node);
_extensionsMembers = null;
_extensionTypeMembers = null;
}
Map<Reference, ExtensionMemberDescriptor> _computeExtensionMembers(
Library library,
) {
if (_extensionsMembers == null) {
Map<Reference, ExtensionMemberDescriptor> map = _extensionsMembers = {};
for (Extension extension in library.extensions) {
for (ExtensionMemberDescriptor descriptor
in extension.memberDescriptors) {
Reference? memberReference = descriptor.memberReference;
if (memberReference != null) {
map[memberReference] = descriptor;
Member member = memberReference.asMember;
if (!member.isExtensionMember) {
problem(
member,
"Member $member (${descriptor}) from $extension is not "
" marked as an extension member.",
);
}
}
Reference? tearOffReference = descriptor.tearOffReference;
if (tearOffReference != null) {
map[tearOffReference] = descriptor;
Member tearOff = tearOffReference.asMember;
if (!tearOff.isExtensionMember) {
problem(
tearOff,
"Tear-off $tearOff (${descriptor}) from $extension is not "
"marked as an extension member.",
);
}
}
if (memberReference == null && tearOffReference == null) {
problem(
extension,
"Both member and tear-off references are null in "
"the descriptor $descriptor from $extension.",
);
}
}
}
}
return _extensionsMembers!;
}
@override
void visitLibraryPart(LibraryPart node) {
enterTreeNode(node);
TreeNode? oldParent = enterParent(node);
_visitAnnotations(node.annotations);
exitParent(oldParent);
exitTreeNode(node);
}
@override
void visitLibraryDependency(LibraryDependency node) {
enterTreeNode(node);
TreeNode? oldParent = enterParent(node);
_visitAnnotations(node.annotations);
visitList(node.combinators, this);
exitParent(oldParent);
exitTreeNode(node);
}
Map<Reference, ExtensionTypeMemberDescriptor> _computeExtensionTypeMembers(
Library library,
) {
if (_extensionTypeMembers == null) {
Map<Reference, ExtensionTypeMemberDescriptor> map =
_extensionTypeMembers = {};
for (ExtensionTypeDeclaration extensionTypeDeclaration
in library.extensionTypeDeclarations) {
for (ExtensionTypeMemberDescriptor descriptor
in extensionTypeDeclaration.memberDescriptors) {
Reference? memberReference = descriptor.memberReference;
if (memberReference != null) {
map[memberReference] = descriptor;
Member member = memberReference.asMember;
if (!member.isExtensionTypeMember) {
problem(
member,
"Member $member (${descriptor}) from "
"$extensionTypeDeclaration is not marked as an extension "
"type member.",
);
}
}
Reference? tearOffReference = descriptor.tearOffReference;
if (tearOffReference != null) {
map[tearOffReference] = descriptor;
Member tearOff = tearOffReference.asMember;
if (!tearOff.isExtensionTypeMember) {
problem(
tearOff,
"Tear-off $tearOff (${descriptor}) from "
"$extensionTypeDeclaration is not marked as an extension "
"type member.",
);
}
}
if (memberReference == null && tearOffReference == null) {
problem(
extensionTypeDeclaration,
"Both member and tear-off references are null in "
"the descriptor $descriptor from $extensionTypeDeclaration.",
);
}
}
}
}
return _extensionTypeMembers!;
}
@override
void visitExtension(Extension node) {
enterTreeNode(node);
fileUri = checkLocation(node, node.name, node.fileUri);
currentExtension = node;
_computeExtensionMembers(node.enclosingLibrary);
declareTypeParameters(node.typeParameters);
final TreeNode? oldParent = enterParent(node);
_visitAnnotations(node.annotations);
visitList(node.typeParameters, this);
node.onType.accept(this);
exitParent(oldParent);
undeclareTypeParameters(node.typeParameters);
currentExtension = null;
exitTreeNode(node);
}
@override
void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
enterTreeNode(node);
fileUri = checkLocation(node, node.name, node.fileUri);
currentExtensionTypeDeclaration = node;
_computeExtensionTypeMembers(node.enclosingLibrary);
declareTypeParameters(node.typeParameters);
final TreeNode? oldParent = enterParent(node);
for (DartType type in node.implements) {
if (!(type is ExtensionType || type is InterfaceType)) {
problem(
node,
"Extension type can only implement extension types and interface "
"types. Found $type.",
);
} else if (type is ExtensionType &&
type.nullability == Nullability.nullable ||
type is! ExtensionType && type.isPotentiallyNullable) {
problem(
node,
"Extension type can only implement non-nullable types. "
"Found $type.",
);
}
}
_visitAnnotations(node.annotations);
visitList(node.typeParameters, this);
node.declaredRepresentationType.accept(this);
visitList(node.procedures, this);
exitParent(oldParent);
undeclareTypeParameters(node.typeParameters);
currentExtensionTypeDeclaration = null;
exitTreeNode(node);
}
void checkTypedef(Typedef node) {
TypedefState? state = typedefState[node];
if (state == TypedefState.Done) return;
if (state == TypedefState.BeingChecked) {
problem(node, "The typedef '$node' refers to itself", context: node);
}
assert(state == null);
enterTreeNode(node);
typedefState[node] = TypedefState.BeingChecked;
Set<TypeParameter> savedTypeParameters = typeParametersInScope;
typeParametersInScope = node.typeParameters.toSet();
TreeNode? savedParent = currentParent;
currentParent = node;
// Visit children without checking the parent pointer on the typedef itself
// since this can be called from a context other than its true parent.
_visitAnnotations(node.annotations);
visitList(node.typeParameters, this);
node.type?.accept(this);
currentParent = savedParent;
typeParametersInScope = savedTypeParameters;
typedefState[node] = TypedefState.Done;
exitTreeNode(node);
}
@override
void visitTypedef(Typedef node) {
enterTreeNode(node);
fileUri = checkLocation(node, node.name, node.fileUri);
checkTypedef(node);
// Enter and exit the node to check the parent pointer on the typedef node.
exitParent(enterParent(node));
exitTreeNode(node);
}
void _findExtensionMember(Member node) {
assert(node.isExtensionMember);
Map<Reference, ExtensionMemberDescriptor> extensionMembers =
_computeExtensionMembers(node.enclosingLibrary);
if (!extensionMembers.containsKey(node.reference)) {
problem(
node,
"Extension member $node is not found in any extension of the "
"enclosing library.",
);
}
}
void _findExtensionTypeMember(Member node) {
assert(node.isExtensionTypeMember);
Map<Reference, ExtensionTypeMemberDescriptor> extensionTypeMembers =
_computeExtensionTypeMembers(node.enclosingLibrary);
if (node is Procedure &&
node.stubKind == ProcedureStubKind.RepresentationField) {
if (extensionTypeMembers.containsKey(node.reference)) {
problem(
node,
"Extension type representation field $node is found amongst the "
"lowered extension type members of the enclosing library.",
);
}
} else {
if (!extensionTypeMembers.containsKey(node.reference)) {
problem(
node,
"Extension type member $node is not found in any extension type "
"declaration of the enclosing library.",
);
}
}
}
@override
void visitField(Field node) {
enterTreeNode(node);
fileUri = checkLocation(node, node.name.text, node.fileUri);
currentMember = node;
TreeNode? oldParent = enterParent(node);
bool isTopLevel = node.parent == currentLibrary;
if (isTopLevel && !node.isStatic) {
problem(
node,
"The top-level field '${node.name.text}' should be static",
context: node,
);
}
if (node.isConst && !node.isStatic) {
problem(
node,
"The const field '${node.name.text}' should be static",
context: node,
);
}
bool isImmutable = node.isLate
? (node.isFinal && node.initializer != null)
: (node.isFinal || node.isConst);
if (isImmutable == node.hasSetter) {
if (node.hasSetter) {
problem(
node,
"The immutable field '${node.name.text}' has a setter reference",
context: node,
);
} else {
if (isOutline && node.isLate) {
// TODO(johnniwinther): Should we add a flag on Field for having
// a declared initializer?
// The initializer is not included in the outline so we can't tell
// whether it has an initializer or not.
} else {
problem(
node,
"The mutable field '${node.name.text}' has no setter reference",
context: node,
);
}
}
}
if (node.isExtensionMember) {
_findExtensionMember(node);
}
if (node.isExtensionTypeMember) {
_findExtensionTypeMember(node);
}
classTypeParametersAreInScope = !node.isStatic;
node.initializer?.accept(this);
node.type.accept(this);
classTypeParametersAreInScope = false;
_visitAnnotations(node.annotations);
exitParent(oldParent);
currentMember = null;
exitTreeNode(node);
}
void _visitAnnotations(List<Expression> annotations) {
for (Expression annotation in annotations) {
if (stage >= VerificationStage.afterConstantEvaluation) {
if (!(annotation is ConstantExpression ||
annotation is InvalidExpression)) {
problem(
annotation,
"Unexpected annotation $annotation (${annotation.runtimeType}). "
"Expected a ConstantExpression or InvalidExpression.",
);
}
}
annotation.accept(this);
}
}
@override
void visitProcedure(Procedure node) {
enterTreeNode(node);
fileUri = checkLocation(node, node.name.text, node.fileUri);
if (node.isExtensionMember) {
_findExtensionMember(node);
}
if (node.isExtensionTypeMember) {
_findExtensionTypeMember(node);
}
if (node.isRedirectingFactory &&
node.function.redirectingFactoryTarget == null) {
problem(
node,
"Procedure '${node.name}' doesn't have a redirecting "
"factory target, but has the 'isRedirectingFactory' bit set.",
);
} else if (!node.isRedirectingFactory &&
node.function.redirectingFactoryTarget != null) {
problem(
node,
"Procedure '${node.name}' has redirecting factory target, but "
"doesn't have the 'isRedirectingFactory' bit set.",
);
}
currentMember = node;
TreeNode? oldParent = enterParent(node);
classTypeParametersAreInScope = !node.isStatic;
if (node.isAbstract && node.isExternal) {
problem(node, "Procedure cannot be both abstract and external.");
}
if (node.isMemberSignature && node.isForwardingStub) {
problem(
node,
"Procedure cannot be both a member signature and a forwarding stub: "
"$node.",
);
}
if (node.isMemberSignature && node.isForwardingSemiStub) {
problem(
node,
"Procedure cannot be both a member signature and a forwarding semi "
"stub $node.",
);
}
if (node.isMemberSignature && node.isNoSuchMethodForwarder) {
problem(
node,
"Procedure cannot be both a member signature and a noSuchMethod "
"forwarder $node.",
);
}
if (node.isMemberSignature && node.memberSignatureOrigin == null) {
problem(
node,
"Member signature must have a member signature origin $node.",
);
}
if (node.abstractForwardingStubTarget != null &&
!(node.isForwardingStub || node.isForwardingSemiStub)) {
problem(
node,
"Only forwarding stubs can have a forwarding stub interface target "
"$node.",
);
}
if (node.concreteForwardingStubTarget != null &&
!(node.isForwardingStub || node.isForwardingSemiStub)) {
problem(
node,
"Only forwarding stubs can have a forwarding stub super target "
"$node.",
);
}
node.function.accept(this);
classTypeParametersAreInScope = false;
_visitAnnotations(node.annotations);
exitParent(oldParent);
// TODO(johnniwinther): Enable this invariant. Possibly by removing bodies
// from external procedures declared with a body or by removing the external
// flag from such procedures.
/*if (node.isExternal) {
if (node.function.body != null) {
problem(node, "External procedure has non-null body.");
}
} else if (node.isAbstract) {
if (node.function.body != null) {
problem(node, "Abstract procedure has non-null body.");
}
} else {
if (node.function.body == null) {
problem(node, "Non-external/abstract procedure has no body.");
}
}*/
currentMember = null;
exitTreeNode(node);
}
@override
void visitConstructor(Constructor node) {
enterTreeNode(node);
fileUri = checkLocation(node, node.name.text, node.fileUri);
currentMember = node;
classTypeParametersAreInScope = true;
if (node.isExtensionMember) {
_findExtensionMember(node);
}
if (node.isExtensionTypeMember) {
_findExtensionTypeMember(node);
}
// The constructor member needs special treatment due to parameters being
// in scope in the initializer list.
TreeNode? oldParent = enterParent(node);
int stackHeight = enterLocalScope();
visitChildren(node.function);
visitList(node.initializers, this);
if (!isOutline) {
checkInitializers(node);
}
exitLocalScope(stackHeight);
classTypeParametersAreInScope = false;
_visitAnnotations(node.annotations);
exitParent(oldParent);
// TODO(johnniwinther): Enable this invariant. Possibly by removing bodies
// from external constructors declared with a body or by removing the
// external flag from such constructors.
/*if (node.isExternal) {
if (node.function.body != null) {
problem(node, "External constructor has non-null body.");
}
} else {
if (node.function.body == null) {
problem(node, "Non-external constructor has no body.");
}
}*/
classTypeParametersAreInScope = false;
currentMember = null;
exitTreeNode(node);
}
@override
void visitClass(Class node) {
enterTreeNode(node);
fileUri = checkLocation(node, node.name, node.fileUri);
currentClass = node;
declareTypeParameters(node.typeParameters);
TreeNode? oldParent = enterParent(node);
classTypeParametersAreInScope = false;
_visitAnnotations(node.annotations);
classTypeParametersAreInScope = true;
visitList(node.typeParameters, this);
visitList(node.fields, this);
visitList(node.constructors, this);
visitList(node.procedures, this);
exitParent(oldParent);
undeclareTypeParameters(node.typeParameters);
currentClass = null;
exitTreeNode(node);
}
@override
void visitFunctionNode(FunctionNode node) {
enterTreeNode(node);
declareTypeParameters(node.typeParameters);
bool savedInCatchBlock = inCatchBlock;
AsyncMarker savedAsyncMarker = currentAsyncMarker;
currentAsyncMarker = node.asyncMarker;
if (!isOutline) {
if (node.asyncMarker == AsyncMarker.Async &&
node.emittedValueType == null) {
problem(
node,
"No future value type set for async function in opt-in library.",
);
}
TreeNode? parent = node.parent;
if (parent is! Procedure ||
!parent.isAbstract &&
!parent.isSynthetic &&
!parent.isSyntheticForwarder) {
for (
int positionalIndex = 0;
positionalIndex < node.positionalParameters.length;
positionalIndex++
) {
if (positionalIndex >= node.requiredParameterCount) {
VariableDeclaration positionalParameter =
node.positionalParameters[positionalIndex];
if (positionalParameter.initializer == null &&
// Global transformations like TFA may not maintain this
// invariant.
stage != VerificationStage.afterGlobalTransformations) {
problem(
positionalParameter,
"An optional positional parameter is expected to have a "
"default value initializer, defined or synthesized.",
);
}
}
}
for (VariableDeclaration namedParameter in node.namedParameters) {
if (!namedParameter.isRequired &&
namedParameter.initializer == null &&
// Global transformations like TFA may not maintain this
// invariant.
stage != VerificationStage.afterGlobalTransformations) {
problem(
namedParameter,
"An optional named parameter is expected to have a default "
"value initializer, defined or synthesized.",
);
}
}
}
}
inCatchBlock = false;
visitWithLocalScope(node);
inCatchBlock = savedInCatchBlock;
currentAsyncMarker = savedAsyncMarker;
undeclareTypeParameters(node.typeParameters);
exitTreeNode(node);
}
@override
void visitFunctionType(FunctionType node) {
for (int i = 1; i < node.namedParameters.length; ++i) {
if (node.namedParameters[i - 1].compareTo(node.namedParameters[i]) >= 0) {
problem(
currentParent,
"Named parameters are not sorted on function type ($node).",
);
}
}
declareStructuralParameters(node.typeParameters);
visitList(node.positionalParameters, this);
visitList(node.namedParameters, this);
node.returnType.accept(this);
undeclareStructuralParameters(node.typeParameters);
}
@override
void visitBlock(Block node) {
visitWithLocalScope(node);
}
@override
void visitForStatement(ForStatement node) {
visitWithLocalScope(node);
}
@override
void visitLet(Let node) {
if (_isCompileTimeErrorEncoding(node)) return;
visitWithLocalScope(node);
}
@override
void visitInvalidExpression(InvalidExpression node) {
return;
}
@override
void visitBlockExpression(BlockExpression node) {
enterTreeNode(node);
int stackHeight = enterLocalScope();
// Do not visit the block directly because the value expression needs to
// be in its scope.
TreeNode? oldParent = enterParent(node);
enterParent(node.body);
for (int i = 0; i < node.body.statements.length; ++i) {
node.body.statements[i].accept(this);
}
exitParent(node);
node.value.accept(this);
exitParent(oldParent);
exitLocalScope(stackHeight);
exitTreeNode(node);
}
@override
void visitCatch(Catch node) {
bool savedInCatchBlock = inCatchBlock;
inCatchBlock = true;
visitWithLocalScope(node);
inCatchBlock = savedInCatchBlock;
}
@override
void visitReturnStatement(ReturnStatement node) {
switch (currentAsyncMarker) {
case AsyncMarker.Sync:
case AsyncMarker.Async:
// ok
break;
case AsyncMarker.SyncStar:
case AsyncMarker.AsyncStar:
if (node.expression != null) {
problem(
node,
"Return statement in function with async marker: "
"$currentAsyncMarker",
);
}
break;
}
super.visitReturnStatement(node);
}
@override
void visitYieldStatement(YieldStatement node) {
switch (currentAsyncMarker) {
case AsyncMarker.Sync:
case AsyncMarker.Async:
problem(
node,
"Yield statement in function with async marker: "
"$currentAsyncMarker",
);
break;
case AsyncMarker.SyncStar:
case AsyncMarker.AsyncStar:
// ok
break;
}
super.visitYieldStatement(node);
}
@override
void visitRethrow(Rethrow node) {
if (!inCatchBlock) {
problem(node, "Rethrow must be inside a Catch block.");
}
}
@override
void visitLegacyVariableStatement(LegacyVariableStatement node) {
_verifyVariableStatement(node);
super.visitLegacyVariableStatement(node);
}
@override
void visitVariableInitialization(VariableInitialization node) {
_verifyVariableStatement(node);
super.visitVariableInitialization(node);
}
void _verifyVariableStatement(VariableStatement node) {
TreeNode? parent = node.parent;
if (parent is! Block && !(parent is ForStatement && parent.body != node)) {
problem(
node,
"VariableStatement must be a direct child of a Block or ForStatement, "
"not ${parent.runtimeType}.",
);
}
}
@override
void defaultVariableDeclaration(VariableDeclaration node) {
return _verifyVariableDeclaration(node);
}
void _verifyVariableDeclaration(VariableDeclaration node) {
enterTreeNode(node);
visitChildren(node);
declareVariable(node.variable);
if (afterConst && node.isConst && constantLocalsShouldBeRemoved) {
Expression? initializer = node.initializer;
if (!(initializer is InvalidExpression ||
initializer is ConstantExpression &&
initializer.constant is UnevaluatedConstant)) {
problem(node, "Constant VariableDeclaration");
}
}
exitTreeNode(node);
}
@override
void visitVariableGet(VariableGet node) {
// TODO(cstefantsova): Support new variable model.
if (_isNewModelVariable(node.variable)) {
return;
}
enterTreeNode(node);
checkVariableInScope(node.variable, node);
visitChildren(node);
if (constantsAreAlwaysInlined &&
afterConst &&
node.variable.isConst &&
!inUnevaluatedConstant) {
problem(node, "VariableGet of const variable '${node.variable}'.");
}
exitTreeNode(node);
}
@override
void visitVariableSet(VariableSet node) {
// TODO(cstefantsova): Support new variable model.
if (_isNewModelVariable(node.variable)) {
return;
}
enterTreeNode(node);
checkVariableInScope(node.variable, node);
visitChildren(node);
exitTreeNode(node);
}
@override
void visitStaticGet(StaticGet node) {
enterTreeNode(node);
visitChildren(node);
// TODO(johnniwinther): Can this be deleted now?
// Currently Constructor.hasGetter returns `false` even though the CFE uses
// it as a getter for internal purposes:
//
// CFE is letting all call site of a redirecting constructor be resolved
// to the real target. In order to resolve it, it seems to add a body into
// the redirecting-factory constructor which caches the target constructor.
// That cache is via a `StaticGet(real-constructor)` node, which we make
// here pass the verifier.
if (!node.target.hasGetter && node.target is! Constructor) {
problem(node, "StaticGet of '${node.target}' without getter.");
}
if (node.target.isInstanceMember) {
problem(node, "StaticGet of '${node.target}' that's an instance member.");
}
if (constantsAreAlwaysInlined &&
afterConst &&
node.target is Field &&
node.target.isConst) {
problem(node, "StaticGet of const field '${node.target}'.");
}
exitTreeNode(node);
}
@override
void visitStaticSet(StaticSet node) {
enterTreeNode(node);
visitChildren(node);
if (!node.target.hasSetter) {
problem(node, "StaticSet to '${node.target}' without setter.");
}
if (node.target.isInstanceMember) {
problem(node, "StaticSet to '${node.target}' that's an instance member.");
}
exitTreeNode(node);
}
@override
void visitStaticInvocation(StaticInvocation node) {
enterTreeNode(node);
checkTargetedInvocation(node.target, node);
if (node.target.isInstanceMember) {
problem(
node,
"StaticInvocation of '${node.target}' that's an instance member.",
);
}
if (node.isConst &&
!(node.target.isConst &&
node.target.isExternal &&
node.target.kind == ProcedureKind.Factory) &&
!(node.target.isConst && node.target.isExtensionTypeMember)) {
problem(
node,
"Constant StaticInvocation of '${node.target}' that isn't"
" a const external factory or a const extension type constructor.",
);
}
if (afterConst && node.isConst && !inUnevaluatedConstant) {
problem(node, "Constant StaticInvocation.");
}
exitTreeNode(node);
}
@override
void visitTypedefTearOff(TypedefTearOff node) {
_checkTypedefTearOff(node);
declareStructuralParameters(node.structuralParameters);
super.visitTypedefTearOff(node);
undeclareStructuralParameters(node.structuralParameters);
}
void checkTargetedInvocation(Member target, InvocationExpression node) {
visitChildren(node);
if (target.function == null) {
problem(node, "${node.runtimeType} without function.");
}
if (!areArgumentsCompatible(node.arguments, target.function!)) {
problem(
node,
"${node.runtimeType} with incompatible arguments for '${target}'.",
);
}
int expectedTypeParameters = target is Constructor
? target.enclosingClass.typeParameters.length
: target.function!.typeParameters.length;
if (node.arguments.types.length != expectedTypeParameters) {
problem(
node,
"${node.runtimeType} with wrong number of type arguments"
" for '${target}'.",
);
}
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
enterTreeNode(node);
checkTargetedInvocation(node.target, node);
if (node.target.enclosingClass.isAbstract) {
problem(node, "$node of abstract class ${node.target.enclosingClass}.");
}
if (node.isConst && !node.target.isConst) {
problem(
node,
"Constant ConstructorInvocation fo '${node.target}' that"
" isn't const.",
);
}
if (afterConst && node.isConst && !inUnevaluatedConstant) {
problem(node, "Invocation of const constructor '${node.target}'.");
}
exitTreeNode(node);
}
bool areArgumentsCompatible(Arguments arguments, FunctionNode function) {
if (arguments.positional.length < function.requiredParameterCount) {
return false;
}
if (arguments.positional.length > function.positionalParameters.length) {
return false;
}
namedLoop:
for (int i = 0; i < arguments.named.length; ++i) {
NamedExpression argument = arguments.named[i];
String name = argument.name;
for (int j = 0; j < function.namedParameters.length; ++j) {
if (function.namedParameters[j].name == name) continue namedLoop;
}
return false;
}
return true;
}
@override
void visitListLiteral(ListLiteral node) {
enterTreeNode(node);
visitChildren(node);
if (afterConst && node.isConst && !inUnevaluatedConstant) {
problem(node, "Constant list literal.");
}
exitTreeNode(node);
}
@override
void visitSetLiteral(SetLiteral node) {
enterTreeNode(node);
visitChildren(node);
if (afterConst && node.isConst && !inUnevaluatedConstant) {
problem(node, "Constant set literal.");
}
exitTreeNode(node);
}
@override
void visitMapLiteral(MapLiteral node) {
enterTreeNode(node);
visitChildren(node);
if (afterConst && node.isConst && !inUnevaluatedConstant) {
problem(node, "Constant map literal.");
}
exitTreeNode(node);
}
@override
void visitSymbolLiteral(SymbolLiteral node) {
enterTreeNode(node);
if (afterConst && !inUnevaluatedConstant) {
problem(node, "Symbol literal.");
}
exitTreeNode(node);
}
@override
void visitContinueSwitchStatement(ContinueSwitchStatement node) {
enterTreeNode(node);
if (node.target.parent == null) {
problem(node, "Target has no parent.");
} else {
SwitchStatement statement = node.target.parent as SwitchStatement;
for (SwitchCase switchCase in statement.cases) {
if (switchCase == node.target) {
exitTreeNode(node);
return;
}
}
problem(node, "Switch case isn't child of parent.");
}
exitTreeNode(node);
}
@override
void visitInstanceConstant(InstanceConstant constant) {
constant.visitChildren(this);
if (constant.typeArguments.length !=
constant.classNode.typeParameters.length) {
problem(
currentParent,
"Constant $constant provides ${constant.typeArguments.length}"
" type arguments, but the class declares"
" ${constant.classNode.typeParameters.length} parameters.",
);
}
Set<Class> superClasses = <Class>{};
int fieldCount = 0;
for (Class? cls = constant.classNode; cls != null; cls = cls.superclass) {
superClasses.add(cls);
for (Field f in cls.fields) {
if (!f.isStatic && !f.isConst) fieldCount++;
}
}
if (constant.fieldValues.length != fieldCount) {
problem(
currentParent,
"Constant $constant provides ${constant.fieldValues.length}"
" field values, but the class declares"
" $fieldCount fields.",
);
}
for (Reference fieldRef in constant.fieldValues.keys) {
Field field = fieldRef.asField;
if (!superClasses.contains(field.enclosingClass)) {
problem(
currentParent,
"Constant $constant refers to field $field,"
" which does not belong to the right class.",
);
}
}
}
@override
void visitUnevaluatedConstant(UnevaluatedConstant constant) {
bool savedInUnevaluatedConstant = inUnevaluatedConstant;
inUnevaluatedConstant = true;
TreeNode? oldParent = currentParent;
currentParent = null;
constant.expression.accept(this);
currentParent = oldParent;
inUnevaluatedConstant = savedInUnevaluatedConstant;
}
@override
void defaultMemberReference(Member node) {
if (node.transformerFlags & TransformerFlag.seenByVerifier == 0) {
problem(
node,
"Dangling reference to '$node', parent is: '${node.parent}'.",
);
}
}
@override
void visitClassReference(Class node) {
if (!classes.contains(node)) {
problem(
node,
"Dangling reference to '$node', parent is: '${node.parent}'.",
);
}
}
@override
void visitTypedefReference(Typedef node) {
if (!typedefs.contains(node)) {
problem(
node,
"Dangling reference to '$node', parent is: '${node.parent}'",
);
}
}
@override
void visitTypeParameterType(TypeParameterType node) {
TypeParameter parameter = node.parameter;
GenericDeclaration? declaration = parameter.declaration;
if (!typeParametersInScope.contains(parameter)) {
problem(
currentParent,
"Type parameter '$parameter' referenced out of"
" scope, declaration is: '${declaration}'.",
);
}
if (declaration is Class && !classTypeParametersAreInScope) {
problem(
currentParent,
"Type parameter '$parameter' referenced from"
" static context, declaration is: '${declaration}'.",
);
}
defaultDartType(node);
}
@override
void visitInterfaceType(InterfaceType node) {
if (isNullType(node) && node.nullability != Nullability.nullable) {
problem(localContext, "Found a not nullable Null type: ${node}");
}
defaultDartType(node);
if (node.typeArguments.length != node.classNode.typeParameters.length) {
problem(
currentParent,
"Type $node provides ${node.typeArguments.length}"
" type arguments, but the class declares"
" ${node.classNode.typeParameters.length} parameters.",
);
}
if (node.classNode.isAnonymousMixin) {
bool isOk = false;
if (currentParent is FunctionNode) {
TreeNode? functionNodeParent = currentParent!.parent;
if (functionNodeParent is Constructor) {
if (functionNodeParent.parent == node.classNode) {
// We only allow references to anonymous mixins in types as the
// return type of its own constructor.
isOk = true;
}
}
}
if (!isOk) {
problem(
currentParent,
"Type $node references an anonymous mixin class.",
);
}
}
}
@override
void visitTypedefType(TypedefType node) {
checkTypedef(node.typedefNode);
defaultDartType(node);
if (node.typeArguments.length != node.typedefNode.typeParameters.length) {
problem(
currentParent,
"The typedef type $node provides ${node.typeArguments.length}"
" type arguments, but the typedef declares"
" ${node.typedefNode.typeParameters.length} parameters.",
);
}
}
@override
void visitConstantExpression(ConstantExpression node) {
enterTreeNode(node);
inTreeNode(node, () {
bool oldInConstant = inConstant;
node.type.accept(this);
// Only visit the [Constant] in constant context.
inConstant = true;
node.constant.accept(this);
inConstant = oldInConstant;
});
exitTreeNode(node);
}
@override
void visitTypeParameter(TypeParameter node) {
if (identical(node.bound, TypeParameter.unsetBoundSentinel)) {
problem(node, "Unset bound on type parameter $node");
}
if (identical(node.defaultType, TypeParameter.unsetDefaultTypeSentinel)) {
problem(node, "Unset default type on type parameter $node");
}
// ignore: deprecated_member_use_from_same_package
if (node.parent == null) {
// TODO(johnniwinther): Enable this check.
// problem(node, "Type parameter without parent: $node");
node.visitChildren(this);
} else {
visitChildren(node);
}
}
@override
void visitTypedefTearOffConstant(TypedefTearOffConstant node) {
_checkTypedefTearOff(node);
declareStructuralParameters(node.parameters);
super.visitTypedefTearOffConstant(node);
undeclareStructuralParameters(node.parameters);
}
void _checkInterfaceTarget(Expression node, Member interfaceTarget) {
if (!interfaceTarget.isInstanceMember) {
problem(
node,
"Interface target $interfaceTarget is not an instance member.",
);
}
if (interfaceTarget is Procedure &&
interfaceTarget.stubKind == ProcedureStubKind.RepresentationField) {
problem(
node,
"Representation field used as interface target: $interfaceTarget.",
);
}
if (interfaceTarget.enclosingClass == null) {
problem(
node,
"Interface target $interfaceTarget does not have an "
"enclosing class.",
);
}
}
@override
void visitInstanceInvocation(InstanceInvocation node) {
if (node.name != node.interfaceTarget.name) {
problem(
node,
"Instance invocation with name '${node.name}' has a "
"target with name '${node.interfaceTarget.name}'.",
);
}
_checkInterfaceTarget(node, node.interfaceTarget);
super.visitInstanceInvocation(node);
}
@override
void visitInstanceGet(InstanceGet node) {
if (node.name != node.interfaceTarget.name) {
problem(
node,
"Instance get with name '${node.name}' has a "
"target with name '${node.interfaceTarget.name}'.",
);
}
_checkInterfaceTarget(node, node.interfaceTarget);
super.visitInstanceGet(node);
}
@override
void visitInstanceTearOff(InstanceTearOff node) {
if (node.name != node.interfaceTarget.name) {
problem(
node,
"Instance tear-off with name '${node.name}' has a "
"target with name '${node.interfaceTarget.name}'.",
);
}
_checkInterfaceTarget(node, node.interfaceTarget);
super.visitInstanceTearOff(node);
}
@override
void visitInstanceSet(InstanceSet node) {
if (node.name != node.interfaceTarget.name) {
problem(
node,
"Instance set with name '${node.name}' has a "
"target with name '${node.interfaceTarget.name}'.",
);
}
_checkInterfaceTarget(node, node.interfaceTarget);
super.visitInstanceSet(node);
}
/// Invoked by all visit methods if the visited node is a [TreeNode].
// TODO(johnniwinther): Merge this with enter/exitParent.
void enterTreeNode(TreeNode node) {
treeNodeStack.add(node);
testLocation(node);
}
/// Invoked by all visit methods if the visited node is a [TreeNode].
void exitTreeNode(TreeNode node) {
if (treeNodeStack.isEmpty) {
throw new StateError(
"Attempting to exit tree node '${node}' "
"when the tree node stack is empty.",
);
}
if (!identical(treeNodeStack.last, node)) {
throw new StateError(
"Attempting to exit tree node '${node}' "
"when another node '${treeNodeStack.last}' is active.",
);
}
treeNodeStack.removeLast();
}
TreeNode? getLastSeenTreeNode({bool withLocation = false}) {
assert(treeNodeStack.isNotEmpty);
for (int i = treeNodeStack.length - 1; i >= 0; --i) {
TreeNode node = treeNodeStack[i];
if (withLocation && !_hasLocation(_getLocation(node), node)) continue;
return node;
}
return null;
}
TreeNode? getSameLibraryLastSeenTreeNode({bool withLocation = false}) {
if (treeNodeStack.isEmpty) return null;
if (currentLibrary == null) return null;
for (int i = treeNodeStack.length - 1; i >= 0; --i) {
TreeNode node = treeNodeStack[i];
Location? location = _getLocation(node);
if (withLocation && !_hasLocation(location, node)) continue;
if (location != null && location.file == currentLibrary!.fileUri) {
return node;
}
}
return null;
}
/// Returns the `TreeNode.location` while handling [RangeError]s caused by
/// file offsets not within the range of the enclosing file.
Location? _getLocation(TreeNode node, {bool allowInvalidLocation = false}) {
try {
return node.location;
} on RangeError catch (e) {
if (allowInvalidLocation ||
target.verification.allowInvalidLocation(stage, node)) {
return null;
}
problem(
node,
"Invalid location with target '${target.name}' on "
"${node} (${node.runtimeType}): $e",
);
}
return null;
}
bool _hasLocation(Location? location, TreeNode node) {
return location != null && node.fileOffset != TreeNode.noOffset;
}
bool _isInSameLibrary(Library? library, TreeNode node) {
if (library == null) return false;
Location? location = _getLocation(node);
if (location == null) return false;
return library.fileUri == location.file;
}
TreeNode? get localContext {
TreeNode? result = getSameLibraryLastSeenTreeNode(withLocation: true);
if (result == null &&
currentClassOrExtensionOrMember != null &&
_isInSameLibrary(currentLibrary, currentClassOrExtensionOrMember!)) {
result = currentClassOrExtensionOrMember;
}
return result;
}
TreeNode? get remoteContext {
TreeNode? result = getLastSeenTreeNode(withLocation: true);
if (result != null && _isInSameLibrary(currentLibrary, result)) {
result = null;
}
return result;
}
// We disable the location test for now, at least these tests currently fail:
// outline/dartdevc/factory_patch/main
// outline/general/constructor_patch/main
// outline/general/factory_patch/main
// outline/general/mixin_from_patch/main
// outline/general/multiple_class_patches/main
// outline/general/patch_extends_implements/main
// outline/nnbd/platform_optional_parameters/main
// pkg/front_end/test/macros/application/macro_application_test.dart -p \
// subtypes.dart
static const bool doTestLocation = false;
void testLocation(TreeNode node) {
if (!doTestLocation) return;
// When these comes from patching (and in the future from augmentation) they
// don't point correctly.
if (node is LibraryDependency || node is LibraryPart) return;
try {
if (node.fileOffset != TreeNode.noOffset) {
node.location;
}
} catch (e) {
problem(
node,
"${node.runtimeType} crashes when asked for location: '$e'",
context: node,
);
}
}
Uri checkLocation(TreeNode node, String? name, Uri fileUri) {
if (name == null || name.contains("#")) {
// TODO(ahe): Investigate if these checks can be enabled:
// if (node.fileUri != null && node is! Library) {
// problem(node, "A synthetic node shouldn't have a fileUri",
// context: node);
// }
// if (node.fileOffset != -1) {
// problem(node, "A synthetic node shouldn't have a fileOffset",
// context: node);
// }
return fileUri;
} else {
if (node.fileOffset == TreeNode.noOffset &&
!target.verification.allowNoFileOffset(stage, node)) {
problem(node, "'$name' has no fileOffset", context: node);
}
return fileUri;
}
}
void checkSuperInvocation(TreeNode node) {
Member? containingMember = getContainingMember(node);
if (containingMember == null) {
problem(node, 'Super call outside of any member');
} else {
if (!containingMember.containsSuperCalls) {
problem(
node,
'Super call in a member lacking TransformerFlag.superCalls',
);
}
}
}
Member? getContainingMember(TreeNode? node) {
while (node != null) {
if (node is Member) return node;
node = node.parent;
}
return null;
}
@override
void visitAsExpression(AsExpression node) {
enterTreeNode(node);
super.visitAsExpression(node);
if (node.fileOffset == TreeNode.noOffset &&
!node.isUnchecked &&
!target.verification.allowNoFileOffset(stage, node)) {
TreeNode? parent = node.parent;
while (parent != null) {
if (parent.fileOffset != TreeNode.noOffset) break;
parent = parent.parent;
}
problem(parent, "No offset for $node", context: node);
}
exitTreeNode(node);
}
@override
void visitExpressionStatement(ExpressionStatement node) {
// Bypass verification of the [StaticGet] in [RedirectingFactoryBody] as
// this is a static get without a getter.
enterTreeNode(node);
super.visitExpressionStatement(node);
exitTreeNode(node);
}
bool isNullType(DartType node) => node is NullType;
bool isObjectClass(Class c) {
return c.name == "Object" &&
c.enclosingLibrary.importUri.isScheme("dart") &&
c.enclosingLibrary.importUri.path == "core";
}
bool isTopType(DartType node) {
return node is DynamicType ||
node is VoidType ||
node is InterfaceType &&
isObjectClass(node.classNode) &&
node.nullability == Nullability.nullable ||
node is FutureOrType && isTopType(node.typeArgument);
}
bool isFutureOrNull(DartType node) {
return isNullType(node) ||
node is FutureOrType && isFutureOrNull(node.typeArgument);
}
@override
void defaultDartType(DartType node) {
if (!AllowedTypes.isAllowed(node, inConstant: inConstant)) {
final TreeNode? localContext = this.localContext;
final TreeNode? remoteContext = this.remoteContext;
problem(
localContext,
"Unexpected appearance of the disallowed type $node"
"${inConstant ? " inside a constant" : ""}.",
origin: remoteContext,
);
}
super.defaultDartType(node);
}
@override
void visitSuperMethodInvocation(SuperMethodInvocation node) {
enterTreeNode(node);
checkSuperInvocation(node);
super.visitSuperMethodInvocation(node);
exitTreeNode(node);
}
@override
void visitSuperPropertyGet(SuperPropertyGet node) {
enterTreeNode(node);
checkSuperInvocation(node);
super.visitSuperPropertyGet(node);
exitTreeNode(node);
}
@override
void visitSuperPropertySet(SuperPropertySet node) {
enterTreeNode(node);
checkSuperInvocation(node);
super.visitSuperPropertySet(node);
exitTreeNode(node);
}
void _checkConstructorTearOff(Node node, Member tearOffTarget) {
if (tearOffTarget.enclosingLibrary.importUri.isScheme('dart')) {
// Platform libraries are not compilation with test flags and might
// contain tear-offs not expected when testing lowerings.
return;
}
if (tearOffTarget is Constructor &&
target.isConstructorTearOffLoweringEnabled) {
problem(
node is TreeNode ? node : getLastSeenTreeNode(),
'${node.runtimeType} nodes for generative constructors should be '
'lowered for target "${target.name}".',
);
}
if (tearOffTarget is Procedure &&
tearOffTarget.isFactory &&
target.isFactoryTearOffLoweringEnabled) {
problem(
node is TreeNode ? node : getLastSeenTreeNode(),
'${node.runtimeType} nodes for factory constructors should be '
'lowered for target "${target.name}".',
);
}
}
@override
void visitConstructorTearOff(ConstructorTearOff node) {
_checkConstructorTearOff(node, node.target);
super.visitConstructorTearOff(node);
}
@override
void visitConstructorTearOffConstant(ConstructorTearOffConstant node) {
_checkConstructorTearOff(node, node.target);
super.visitConstructorTearOffConstant(node);
}
void _checkTypedefTearOff(Node node) {
if (target.isTypedefTearOffLoweringEnabled) {
problem(
node is TreeNode ? node : getLastSeenTreeNode(),
'${node.runtimeType} nodes for typedefs should be '
'lowered for target "${target.name}".',
);
}
}
void _checkRedirectingFactoryTearOff(Node node) {
if (target.isRedirectingFactoryTearOffLoweringEnabled) {
problem(
node is TreeNode ? node : getLastSeenTreeNode(),
'ConstructorTearOff nodes for redirecting factories should be '
'lowered for target "${target.name}".',
);
}
}
@override
void visitRedirectingFactoryTearOff(RedirectingFactoryTearOff node) {
_checkRedirectingFactoryTearOff(node);
super.visitRedirectingFactoryTearOff(node);
}
@override
void visitRedirectingFactoryTearOffConstant(
RedirectingFactoryTearOffConstant node,
) {
_checkRedirectingFactoryTearOff(node);
super.visitRedirectingFactoryTearOffConstant(node);
}
@override
void visitSwitchStatement(SwitchStatement node) {
if (node.expressionTypeInternal == null) {
problem(node, 'SwitchStatement.expressionType has not been set.');
}
super.visitSwitchStatement(node);
}
@override
void visitListConcatenation(ListConcatenation node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
if (!inUnevaluatedConstant) {
problem(node, "Unexpected internal node $node.");
}
}
}
@override
void visitSetConcatenation(SetConcatenation node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
if (!inUnevaluatedConstant) {
problem(node, "Unexpected internal node $node.");
}
}
}
@override
void visitMapConcatenation(MapConcatenation node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
if (!inUnevaluatedConstant) {
problem(node, "Unexpected internal node $node.");
}
}
}
@override
void visitInstanceCreation(InstanceCreation node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
if (!inUnevaluatedConstant) {
problem(node, "Unexpected internal node $node.");
}
}
}
@override
void visitFileUriExpression(FileUriExpression node) {
if (!target.supportsFileUriExpression) {
if (stage >= VerificationStage.afterConstantEvaluation) {
if (!inUnevaluatedConstant) {
problem(node, "Unexpected internal node $node.");
}
}
}
}
@override
void visitPatternAssignment(PatternAssignment node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void visitIfCaseStatement(IfCaseStatement node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void visitPatternSwitchStatement(PatternSwitchStatement node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void defaultPattern(Pattern node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void visitSwitchExpression(SwitchExpression node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void visitSwitchExpressionCase(SwitchExpressionCase node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void visitPatternGuard(PatternGuard node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
@override
void visitPatternSwitchCase(PatternSwitchCase node) {
if (stage >= VerificationStage.afterConstantEvaluation) {
problem(node, "Unexpected internal node $node.");
}
}
}
class VerifyGetStaticType extends RecursiveVisitor {
final TypeEnvironment env;
Member? currentMember;
final StatefulStaticTypeContext _staticTypeContext;
VerifyGetStaticType(this.env)
: _staticTypeContext = new StatefulStaticTypeContext.stacked(env);
@override
void visitLibrary(Library node) {
_staticTypeContext.enterLibrary(node);
super.visitLibrary(node);
_staticTypeContext.leaveLibrary(node);
}
@override
void visitField(Field node) {
currentMember = node;
_staticTypeContext.enterMember(node);
super.visitField(node);
_staticTypeContext.leaveMember(node);
currentMember = node;
}
@override
void visitProcedure(Procedure node) {
currentMember = node;
_staticTypeContext.enterMember(node);
super.visitProcedure(node);
_staticTypeContext.leaveMember(node);
currentMember = node;
}
@override
void visitConstructor(Constructor node) {
currentMember = node;
_staticTypeContext.enterMember(node);
super.visitConstructor(node);
_staticTypeContext.leaveMember(node);
currentMember = null;
}
@override
void visitLet(Let node) {
if (_isCompileTimeErrorEncoding(node)) return;
super.visitLet(node);
}
@override
void visitInvalidExpression(InvalidExpression node) {
return;
}
@override
void defaultExpression(Expression node) {
try {
node.getStaticType(_staticTypeContext);
} catch (_) {
print(
'Error in $currentMember in ${currentMember?.fileUri}: '
'$node (${node.runtimeType})',
);
rethrow;
}
super.defaultExpression(node);
}
}
void checkInitializers(Constructor constructor) {
// TODO(ahe): I'll add more here in other CLs.
}
bool _isCompileTimeErrorEncoding(TreeNode? node) {
return node is Let && node.variable.initializer is InvalidExpression;
}
class AllowedTypes implements DartTypeVisitor<bool> {
static bool isAllowed(DartType type, {required bool inConstant}) {
return type.accept(
inConstant
? const AllowedTypes(inConstant: true)
: const AllowedTypes(inConstant: false),
);
}
final bool inConstant;
const AllowedTypes({required this.inConstant});
@override
bool visitAuxiliaryType(AuxiliaryType node) => false;
@override
bool visitDynamicType(DynamicType node) => true;
@override
bool visitFunctionType(FunctionType node) => true;
@override
bool visitFutureOrType(FutureOrType node) => true;
@override
bool visitExtensionType(ExtensionType node) => !inConstant;
@override
bool visitInterfaceType(InterfaceType node) => true;
@override
bool visitIntersectionType(IntersectionType node) => true;
@override
bool visitInvalidType(InvalidType node) => true;
@override
bool visitNeverType(NeverType node) => true;
@override
bool visitNullType(NullType node) => true;
@override
bool visitRecordType(RecordType node) => true;
@override
bool visitTypeParameterType(TypeParameterType node) => true;
@override
bool visitStructuralParameterType(StructuralParameterType node) => true;
@override
bool visitTypedefType(TypedefType node) => true;
@override
bool visitVoidType(VoidType node) => true;
@override
bool visitFunctionTypeParameterType(FunctionTypeParameterType node) {
// TODO(cstefantsova): Implement visitFunctionTypeParameterType.
throw new UnimplementedError(
"Unimplemented support for $node (${node.runtimeType}).",
);
}
@override
bool visitClassTypeParameterType(ClassTypeParameterType node) {
// TODO(cstefantsova): Implement visitClassTypeParameterType.
throw new UnimplementedError(
"Unimplemented support for $node (${node.runtimeType}).",
);
}
}