| // 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. |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/dart/element/type_system.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/generated/variable_type_provider.dart'; |
| import 'package:front_end/src/fasta/flow_analysis/flow_analysis.dart'; |
| import 'package:meta/meta.dart'; |
| |
| class AnalyzerFunctionBodyAccess |
| implements FunctionBodyAccess<VariableElement> { |
| final FunctionBody node; |
| |
| AnalyzerFunctionBodyAccess(this.node); |
| |
| @override |
| bool isPotentiallyMutatedInClosure(VariableElement variable) { |
| return node.isPotentiallyMutatedInClosure(variable); |
| } |
| |
| @override |
| bool isPotentiallyMutatedInScope(VariableElement variable) { |
| return node.isPotentiallyMutatedInScope(variable); |
| } |
| } |
| |
| class AnalyzerNodeOperations implements NodeOperations<Expression> { |
| const AnalyzerNodeOperations(); |
| |
| @override |
| Expression unwrapParenthesized(Expression node) { |
| return node.unParenthesized; |
| } |
| } |
| |
| /// The helper for performing flow analysis during resolution. |
| /// |
| /// It contains related precomputed data, result, and non-trivial pieces of |
| /// code that are independent from visiting AST during resolution, so can |
| /// be extracted. |
| class FlowAnalysisHelper { |
| /// The reused instance for creating new [FlowAnalysis] instances. |
| final NodeOperations<Expression> _nodeOperations; |
| |
| /// The reused instance for creating new [FlowAnalysis] instances. |
| final _TypeSystemTypeOperations _typeOperations; |
| |
| /// Precomputed sets of potentially assigned variables. |
| final AssignedVariables<AstNode, VariableElement> assignedVariables; |
| |
| /// The result for post-resolution stages of analysis. |
| final FlowAnalysisResult result; |
| |
| /// The current flow, when resolving a function body, or `null` otherwise. |
| FlowAnalysis<Statement, Expression, VariableElement, DartType> flow; |
| |
| int _blockFunctionBodyLevel = 0; |
| |
| factory FlowAnalysisHelper( |
| TypeSystem typeSystem, AstNode node, bool retainDataForTesting) { |
| return FlowAnalysisHelper._( |
| const AnalyzerNodeOperations(), |
| _TypeSystemTypeOperations(typeSystem), |
| computeAssignedVariables(node), |
| retainDataForTesting ? FlowAnalysisResult() : null); |
| } |
| |
| FlowAnalysisHelper._(this._nodeOperations, this._typeOperations, |
| this.assignedVariables, this.result); |
| |
| LocalVariableTypeProvider get localVariableTypeProvider { |
| return _LocalVariableTypeProvider(this); |
| } |
| |
| VariableElement assignmentExpression(AssignmentExpression node) { |
| if (flow == null) return null; |
| |
| var left = node.leftHandSide; |
| |
| if (left is SimpleIdentifier) { |
| var element = left.staticElement; |
| if (element is VariableElement) { |
| return element; |
| } |
| } |
| |
| return null; |
| } |
| |
| void assignmentExpression_afterRight( |
| VariableElement localElement, Expression right) { |
| if (localElement == null) return; |
| |
| flow.write(localElement); |
| } |
| |
| void binaryExpression_equal( |
| BinaryExpression node, Expression left, Expression right, |
| {@required bool notEqual}) { |
| if (flow == null) return; |
| |
| if (right is NullLiteral) { |
| if (left is SimpleIdentifier) { |
| var element = left.staticElement; |
| if (element is VariableElement) { |
| flow.conditionEqNull(node, element, notEqual: notEqual); |
| } |
| } |
| } else if (left is NullLiteral) { |
| if (right is SimpleIdentifier) { |
| var element = right.staticElement; |
| if (element is VariableElement) { |
| flow.conditionEqNull(node, element, notEqual: notEqual); |
| } |
| } |
| } |
| } |
| |
| void breakStatement(BreakStatement node) { |
| var target = getLabelTarget(node, node.label?.staticElement); |
| flow.handleBreak(target); |
| } |
| |
| /// Mark the [node] as unreachable if it is not covered by another node that |
| /// is already known to be unreachable. |
| void checkUnreachableNode(AstNode node) { |
| if (flow == null) return; |
| if (flow.isReachable) return; |
| |
| if (result != null) { |
| // Ignore the [node] if it is fully covered by the last unreachable. |
| if (result.unreachableNodes.isNotEmpty) { |
| var last = result.unreachableNodes.last; |
| if (node.offset >= last.offset && node.end <= last.end) return; |
| } |
| |
| result.unreachableNodes.add(node); |
| } |
| } |
| |
| void continueStatement(ContinueStatement node) { |
| var target = getLabelTarget(node, node.label?.staticElement); |
| flow.handleContinue(target); |
| } |
| |
| void for_bodyBegin(AstNode node, Expression condition) { |
| flow.for_bodyBegin(node is Statement ? node : null, condition); |
| } |
| |
| void for_conditionBegin(AstNode node, Expression condition) { |
| var assigned = assignedVariables.writtenInNode(node); |
| flow.for_conditionBegin(assigned); |
| } |
| |
| void functionBody_enter(FunctionBody node) { |
| _blockFunctionBodyLevel++; |
| |
| if (_blockFunctionBodyLevel > 1) { |
| assert(flow != null); |
| } else { |
| flow = FlowAnalysis<Statement, Expression, VariableElement, DartType>( |
| _nodeOperations, |
| _typeOperations, |
| AnalyzerFunctionBodyAccess(node), |
| ); |
| } |
| |
| var parameters = _enclosingExecutableParameters(node); |
| if (parameters != null) { |
| for (var parameter in parameters.parameters) { |
| flow.add(parameter.declaredElement, assigned: true); |
| } |
| } |
| } |
| |
| void functionBody_exit(FunctionBody node) { |
| _blockFunctionBodyLevel--; |
| |
| if (_blockFunctionBodyLevel > 0) { |
| return; |
| } |
| |
| // Set this.flow to null before doing any clean-up so that if an exception |
| // is raised, the state is already updated correctly, and we don't have |
| // cascading failures. |
| var flow = this.flow; |
| this.flow = null; |
| |
| if (!flow.isReachable) { |
| result?.functionBodiesThatDontComplete?.add(node); |
| } |
| |
| flow.finish(); |
| } |
| |
| void isExpression(IsExpression node) { |
| if (flow == null) return; |
| |
| var expression = node.expression; |
| var typeAnnotation = node.type; |
| |
| if (expression is SimpleIdentifier) { |
| var element = expression.staticElement; |
| if (element is VariableElement) { |
| flow.isExpression_end( |
| node, |
| element, |
| node.notOperator != null, |
| typeAnnotation.type, |
| ); |
| } |
| } |
| } |
| |
| bool isPotentiallyNonNullableLocalReadBeforeWrite(SimpleIdentifier node) { |
| if (flow == null) return false; |
| |
| if (node.inDeclarationContext()) return false; |
| if (!node.inGetterContext()) return false; |
| |
| var element = node.staticElement; |
| if (element is LocalVariableElement) { |
| var typeSystem = _typeOperations.typeSystem; |
| if (typeSystem.isPotentiallyNonNullable(element.type)) { |
| var isUnassigned = !flow.isAssigned(element); |
| if (isUnassigned) { |
| result?.unassignedNodes?.add(node); |
| } |
| // Note: in principle we could make this slightly more performant by |
| // checking element.isLate earlier, but we would lose the ability to |
| // test the flow analysis mechanism using late variables. And it seems |
| // unlikely that the `late` modifier will be used often enough for it to |
| // make a significant difference. |
| if (element.isLate) return false; |
| return isUnassigned; |
| } |
| } |
| |
| return false; |
| } |
| |
| void loopVariable(DeclaredIdentifier declaredVariable) { |
| if (declaredVariable != null) { |
| flow.add(declaredVariable.declaredElement, assigned: false); |
| } |
| } |
| |
| void variableDeclarationList(VariableDeclarationList node) { |
| if (flow != null) { |
| var variables = node.variables; |
| for (var i = 0; i < variables.length; ++i) { |
| var variable = variables[i]; |
| flow.add(variable.declaredElement, |
| assigned: variable.initializer != null); |
| } |
| } |
| } |
| |
| FormalParameterList _enclosingExecutableParameters(FunctionBody node) { |
| var parent = node.parent; |
| if (parent is ConstructorDeclaration) { |
| return parent.parameters; |
| } |
| if (parent is FunctionExpression) { |
| return parent.parameters; |
| } |
| if (parent is MethodDeclaration) { |
| return parent.parameters; |
| } |
| return null; |
| } |
| |
| /// Computes the [AssignedVariables] map for the given [node]. |
| static AssignedVariables<AstNode, VariableElement> computeAssignedVariables( |
| AstNode node) { |
| var assignedVariables = AssignedVariables<AstNode, VariableElement>(); |
| node.accept(_AssignedVariablesVisitor(assignedVariables)); |
| return assignedVariables; |
| } |
| |
| /// Return the target of the `break` or `continue` statement with the |
| /// [element] label. The [element] might be `null` (when the statement does |
| /// not specify a label), so the default enclosing target is returned. |
| static Statement getLabelTarget(AstNode node, LabelElement element) { |
| for (; node != null; node = node.parent) { |
| if (node is DoStatement || |
| node is ForStatement || |
| node is SwitchStatement || |
| node is WhileStatement) { |
| if (element == null) { |
| return node; |
| } |
| var parent = node.parent; |
| if (parent is LabeledStatement) { |
| for (var nodeLabel in parent.labels) { |
| if (identical(nodeLabel.label.staticElement, element)) { |
| return node; |
| } |
| } |
| } |
| } |
| if (element != null && node is SwitchStatement) { |
| for (var member in node.members) { |
| for (var nodeLabel in member.labels) { |
| if (identical(nodeLabel.label.staticElement, element)) { |
| return node; |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| /// The result of performing flow analysis on a unit. |
| class FlowAnalysisResult { |
| /// The list of nodes, [Expression]s or [Statement]s, that cannot be reached, |
| /// for example because a previous statement always exits. |
| final List<AstNode> unreachableNodes = []; |
| |
| /// The list of [FunctionBody]s that don't complete, for example because |
| /// there is a `return` statement at the end of the function body block. |
| final List<FunctionBody> functionBodiesThatDontComplete = []; |
| |
| /// The list of [Expression]s representing variable accesses that occur before |
| /// the corresponding variable has been definitely assigned. |
| final List<AstNode> unassignedNodes = []; |
| } |
| |
| /// The visitor that gathers local variables that are potentially assigned |
| /// in corresponding statements, such as loops, `switch` and `try`. |
| class _AssignedVariablesVisitor extends RecursiveAstVisitor<void> { |
| final AssignedVariables assignedVariables; |
| |
| _AssignedVariablesVisitor(this.assignedVariables); |
| |
| @override |
| void visitAssignmentExpression(AssignmentExpression node) { |
| var left = node.leftHandSide; |
| |
| super.visitAssignmentExpression(node); |
| |
| if (left is SimpleIdentifier) { |
| var element = left.staticElement; |
| if (element is VariableElement) { |
| assignedVariables.write(element); |
| } |
| } |
| } |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| assignedVariables.beginNode(); |
| super.visitDoStatement(node); |
| assignedVariables.endNode(node); |
| } |
| |
| @override |
| void visitForElement(ForElement node) { |
| _handleFor(node, node.forLoopParts, node.body); |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| _handleFor(node, node.forLoopParts, node.body); |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| var expression = node.expression; |
| var members = node.members; |
| |
| expression.accept(this); |
| |
| assignedVariables.beginNode(); |
| members.accept(this); |
| assignedVariables.endNode(node); |
| } |
| |
| @override |
| void visitTryStatement(TryStatement node) { |
| assignedVariables.beginNode(); |
| node.body.accept(this); |
| assignedVariables.endNode(node.body); |
| |
| node.catchClauses.accept(this); |
| |
| var finallyBlock = node.finallyBlock; |
| if (finallyBlock != null) { |
| assignedVariables.beginNode(); |
| finallyBlock.accept(this); |
| assignedVariables.endNode(finallyBlock); |
| } |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| assignedVariables.beginNode(); |
| super.visitWhileStatement(node); |
| assignedVariables.endNode(node); |
| } |
| |
| void _handleFor(AstNode node, ForLoopParts forLoopParts, AstNode body) { |
| if (forLoopParts is ForParts) { |
| if (forLoopParts is ForPartsWithExpression) { |
| forLoopParts.initialization?.accept(this); |
| } else if (forLoopParts is ForPartsWithDeclarations) { |
| forLoopParts.variables?.accept(this); |
| } else { |
| throw new StateError('Unrecognized for loop parts'); |
| } |
| |
| assignedVariables.beginNode(); |
| forLoopParts.condition?.accept(this); |
| body.accept(this); |
| forLoopParts.updaters?.accept(this); |
| assignedVariables.endNode(node); |
| } else if (forLoopParts is ForEachParts) { |
| var iterable = forLoopParts.iterable; |
| |
| iterable.accept(this); |
| |
| assignedVariables.beginNode(); |
| if (forLoopParts is ForEachPartsWithIdentifier) { |
| var element = forLoopParts.identifier.staticElement; |
| if (element is VariableElement) { |
| assignedVariables.write(element); |
| } |
| } else if (forLoopParts is ForEachPartsWithDeclaration) { |
| assignedVariables.write(forLoopParts.loopVariable.declaredElement); |
| } else { |
| throw new StateError('Unrecognized for loop parts'); |
| } |
| body.accept(this); |
| assignedVariables.endNode(node); |
| } else { |
| throw new StateError('Unrecognized for loop parts'); |
| } |
| } |
| } |
| |
| /// The flow analysis based implementation of [LocalVariableTypeProvider]. |
| class _LocalVariableTypeProvider implements LocalVariableTypeProvider { |
| final FlowAnalysisHelper _manager; |
| |
| _LocalVariableTypeProvider(this._manager); |
| |
| @override |
| DartType getType(SimpleIdentifier node) { |
| var variable = node.staticElement as VariableElement; |
| var promotedType = _manager.flow?.promotedType(variable); |
| return promotedType ?? variable.type; |
| } |
| } |
| |
| class _TypeSystemTypeOperations |
| implements TypeOperations<VariableElement, DartType> { |
| final TypeSystem typeSystem; |
| |
| _TypeSystemTypeOperations(this.typeSystem); |
| |
| @override |
| bool isLocalVariable(VariableElement element) { |
| return element is LocalVariableElement; |
| } |
| |
| @override |
| bool isSameType(covariant TypeImpl type1, covariant TypeImpl type2) { |
| return type1 == type2; |
| } |
| |
| @override |
| bool isSubtypeOf(DartType leftType, DartType rightType) { |
| return typeSystem.isSubtypeOf(leftType, rightType); |
| } |
| |
| @override |
| DartType promoteToNonNull(DartType type) { |
| return typeSystem.promoteToNonNull(type); |
| } |
| |
| @override |
| DartType variableType(VariableElement variable) { |
| return variable.type; |
| } |
| } |