blob: 5933d4f4585eb42749a3b1e9c9a7e43244f2086a [file] [log] [blame]
// 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.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_resolution_map.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:linter/src/analyzer.dart';
import 'package:linter/src/util/dart_type_utilities.dart';
import 'package:meta/meta.dart';
_PredicateBuilder _hasConstructorFieldInitializers = (VariableDeclaration v) =>
(AstNode n) =>
n is ConstructorFieldInitializer &&
n.fieldName.bestElement == v.name.bestElement;
_PredicateBuilder _hasFieldFormalParameter = (VariableDeclaration v) =>
(AstNode n) =>
n is FieldFormalParameter &&
(n.identifier.bestElement as FieldFormalParameterElement).field ==
v.name.bestElement;
_PredicateBuilder _hasReturn = (VariableDeclaration v) => (AstNode n) =>
n is ReturnStatement &&
n.expression is SimpleIdentifier &&
(n.expression as SimpleIdentifier).bestElement == v.name.bestElement;
/// Builds a function that reports the variable node if the set of nodes
/// inside the [container] node is empty for all the predicates resulting
/// from building (predicates) with the provided [predicateBuilders] evaluated
/// in the variable.
_VisitVariableDeclaration _buildVariableReporter(
AstNode container,
Iterable<_PredicateBuilder> predicateBuilders,
LintRule rule,
Map<DartTypePredicate, String> predicates) =>
(VariableDeclaration variable) {
if (!predicates.keys.any((DartTypePredicate p) => p(
resolutionMap.elementDeclaredByVariableDeclaration(variable).type))) {
return;
}
Iterable<AstNode> containerNodes =
DartTypeUtilities.traverseNodesInDFS(container);
List<Iterable<AstNode>> validators = <Iterable<AstNode>>[];
predicateBuilders.forEach((f) {
validators.add(containerNodes.where(f(variable)));
});
validators
..add(_findVariableAssignments(containerNodes, variable))
..add(_findNodesInvokingMethodOnVariable(
containerNodes, variable, predicates))
..add(_findMethodCallbackNodes(containerNodes, variable, predicates))
// If any function is invoked with our variable, we suppress lints. This
// is because it is not so uncommon to invoke the target method there. We
// might not have access to the body of such function at analysis time, so
// trying to infer if the close method is invoked there is not always
// possible.
// TODO: Should there be another lint more relaxed that omits this step?
..add(_findMethodInvocationsWithVariableAsArgument(
containerNodes, variable));
if (validators.every((i) => i.isEmpty)) {
rule.reportLint(variable);
}
};
Iterable<AstNode> _findMethodCallbackNodes(Iterable<AstNode> containerNodes,
VariableDeclaration variable, Map<DartTypePredicate, String> predicates) {
Iterable<PrefixedIdentifier> prefixedIdentifiers =
containerNodes.where((n) => n is PrefixedIdentifier);
return prefixedIdentifiers.where((n) =>
n.prefix.bestElement == variable.name.bestElement &&
_hasMatch(
predicates,
resolutionMap.elementDeclaredByVariableDeclaration(variable).type,
n.identifier.token.lexeme));
}
Iterable<AstNode> _findMethodInvocationsWithVariableAsArgument(
Iterable<AstNode> containerNodes, VariableDeclaration variable) {
Iterable<MethodInvocation> prefixedIdentifiers =
containerNodes.where((n) => n is MethodInvocation);
return prefixedIdentifiers.where((n) => n.argumentList.arguments
.where((e) => e is SimpleIdentifier)
.map((e) => (e as SimpleIdentifier).bestElement)
.contains(variable.name.bestElement));
}
Iterable<AstNode> _findNodesInvokingMethodOnVariable(
Iterable<AstNode> classNodes,
VariableDeclaration variable,
Map<DartTypePredicate, String> predicates) =>
classNodes.where((AstNode n) =>
n is MethodInvocation &&
((_hasMatch(
predicates,
resolutionMap
.elementDeclaredByVariableDeclaration(variable)
.type,
n.methodName.name) &&
(_isSimpleIdentifierElementEqualToVariable(
n.realTarget, variable) ||
(n.getAncestor((a) => a == variable) != null))) ||
(_isInvocationThroughCascadeExpression(n, variable))));
Iterable<AstNode> _findVariableAssignments(
Iterable<AstNode> containerNodes, VariableDeclaration variable) {
if (variable.equals != null &&
variable.initializer != null &&
variable.initializer is SimpleIdentifier) {
return [variable];
}
return containerNodes.where((n) =>
n is AssignmentExpression &&
(_isSimpleIdentifierElementEqualToVariable(n.leftHandSide, variable) ||
// Assignment to VariableDeclaration as setter.
(n.leftHandSide is PropertyAccess &&
(n.leftHandSide as PropertyAccess).propertyName.token.lexeme ==
variable.name.token.lexeme))
// Being assigned another reference.
&&
n.rightHandSide is SimpleIdentifier);
}
bool _hasMatch(Map<DartTypePredicate, String> predicates, DartType type,
String methodName) =>
predicates.keys.fold(
false,
(bool previous, DartTypePredicate p) =>
previous || p(type) && predicates[p] == methodName);
bool _isInvocationThroughCascadeExpression(
MethodInvocation invocation, VariableDeclaration variable) {
if (invocation.realTarget is! SimpleIdentifier) {
return false;
}
final identifier = invocation.realTarget;
if (identifier is SimpleIdentifier) {
final element = identifier.bestElement;
if (element is PropertyAccessorElement) {
return element.variable == variable.element;
}
}
return false;
}
bool _isSimpleIdentifierElementEqualToVariable(
AstNode n, VariableDeclaration variable) =>
(n is SimpleIdentifier &&
// Assignment to VariableDeclaration as variable.
(n.bestElement == variable.name.bestElement ||
(n.bestElement is PropertyAccessorElement &&
(n.bestElement as PropertyAccessorElement).variable ==
variable.name.bestElement)));
typedef bool DartTypePredicate(DartType type);
typedef bool _Predicate(AstNode node);
typedef _Predicate _PredicateBuilder(VariableDeclaration v);
typedef void _VisitVariableDeclaration(VariableDeclaration node);
abstract class LeakDetectorVisitor extends SimpleAstVisitor {
static final _variablePredicateBuilders = <_PredicateBuilder>[_hasReturn];
static final _fieldPredicateBuilders = <_PredicateBuilder>[
_hasConstructorFieldInitializers,
_hasFieldFormalParameter
];
final LintRule rule;
LeakDetectorVisitor(this.rule);
@protected
Map<DartTypePredicate, String> get predicates;
@override
void visitFieldDeclaration(FieldDeclaration node) {
CompilationUnit unit = node.getAncestor((a) => a is CompilationUnit);
node.fields.variables.forEach(_buildVariableReporter(
unit, _fieldPredicateBuilders, rule, predicates));
}
@override
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
FunctionBody function = node.getAncestor((a) => a is FunctionBody);
node.variables.variables.forEach(_buildVariableReporter(
function, _variablePredicateBuilders, rule, predicates));
}
}