Create a data structure to track the graph of nullability nodes.
The previous design (which stored the graph impicitly in the nodes
themselves) had a memory leak because some nullability nodes are
static, meaning that once a node became connected to one of the static
nodes, it would never be garbage collected.
Change-Id: I9821660198e29ff974ee0afa678851e0418e0527
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/101487
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
index 52ed8a5..c6d9e80 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
@@ -6,6 +6,7 @@
import 'package:analysis_server/src/nullability/constraint_variable_gatherer.dart';
import 'package:analysis_server/src/nullability/decorated_type.dart';
import 'package:analysis_server/src/nullability/expression_checks.dart';
+import 'package:analysis_server/src/nullability/nullability_graph.dart';
import 'package:analysis_server/src/nullability/nullability_node.dart';
import 'package:analysis_server/src/nullability/transitional_api.dart';
import 'package:analysis_server/src/nullability/unit_propagation.dart';
@@ -38,6 +39,8 @@
/// Constraints gathered by the visitor are stored here.
final Constraints _constraints;
+ final NullabilityGraph _graph;
+
/// The file being analyzed.
final Source _source;
@@ -78,8 +81,14 @@
/// or expression.
bool _inConditionalControlFlow = false;
- ConstraintGatherer(TypeProvider typeProvider, this._variables,
- this._constraints, this._source, this._permissive, this.assumptions)
+ ConstraintGatherer(
+ TypeProvider typeProvider,
+ this._variables,
+ this._constraints,
+ this._graph,
+ this._source,
+ this._permissive,
+ this.assumptions)
: _notNullType =
DecoratedType(typeProvider.objectType, NullabilityNode.never),
_nonNullableBoolType =
@@ -207,7 +216,7 @@
var overallType = DecoratedType(
node.staticType,
NullabilityNode.forLUB(
- node, thenType.node, elseType.node, _joinNullabilities));
+ node, thenType.node, elseType.node, _graph, _joinNullabilities));
_variables.recordDecoratedExpressionType(node, overallType);
return overallType;
}
@@ -228,6 +237,7 @@
null,
_guards,
_constraints,
+ _graph,
false);
} else {
assert(assumptions.namedNoDefaultParameterHeuristic ==
@@ -438,7 +448,7 @@
expression.end, sourceType.node, destinationType.node, _guards));
}
NullabilityNode.recordAssignment(sourceType.node, destinationType.node,
- checkNotNull, _guards, _constraints, _inConditionalControlFlow);
+ checkNotNull, _guards, _constraints, _graph, _inConditionalControlFlow);
// TODO(paulberry): it's a cheat to pass in expression=null for the
// recursive checks. Really we want to unify all the checks in a single
// ExpressionChecks object.
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
new file mode 100644
index 0000000..f1c9d45
--- /dev/null
+++ b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
@@ -0,0 +1,28 @@
+// 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:analysis_server/src/nullability/nullability_node.dart';
+
+/// Data structure to keep track of the relationship between [NullabilityNode]
+/// objects.
+class NullabilityGraph {
+ /// Map from a nullability node to those nodes that are "downstream" from it
+ /// (meaning that if a key node is nullable, then all the nodes in the
+ /// corresponding value will either have to be nullable, or null checks will
+ /// have to be added).
+ final _downstream = Map<NullabilityNode, List<NullabilityNode>>.identity();
+
+ /// Records that [sourceNode] is immediately upstream from [destinationNode].
+ void connect(NullabilityNode sourceNode, NullabilityNode destinationNode) {
+ (_downstream[sourceNode] ??= []).add(destinationNode);
+ }
+
+ /// Iterates through all nodes that are "downstream" of [node] (i.e. if
+ /// [node] is nullable, then all the iterated nodes will either have to be
+ /// nullable, or null checks will have to be added).
+ ///
+ /// There is no guarantee of uniqueness of the iterated nodes.
+ Iterable<NullabilityNode> getDownstreamNodes(NullabilityNode node) =>
+ _downstream[node] ?? const [];
+}
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index e244d0e..e871c28 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/nullability/decorated_type.dart';
+import 'package:analysis_server/src/nullability/nullability_graph.dart';
import 'package:analysis_server/src/nullability/transitional_api.dart';
import 'package:analysis_server/src/nullability/unit_propagation.dart';
import 'package:analyzer/dart/ast/ast.dart';
@@ -30,11 +31,6 @@
/// migrated) forces this type to be non-nullable.
final ConstraintVariable nullable;
- /// List of all the nodes that are "downstream" of this one (i.e. if this node
- /// is nullable, then all the nodes in [_downstreamNodes] will either have to
- /// be nullable, or null checks will have to be added).
- final _downstreamNodes = <NullabilityNode>[];
-
ConstraintVariable _nonNullIntent;
bool _isPossiblyOptional = false;
@@ -57,6 +53,7 @@
Expression conditionalExpression,
NullabilityNode a,
NullabilityNode b,
+ NullabilityGraph graph,
ConstraintVariable Function(
Expression, ConstraintVariable, ConstraintVariable)
joinNullabilities) = NullabilityNodeForLUB._;
@@ -88,11 +85,6 @@
/// annotate the nullability of that type.
String get debugSuffix => nullable == null ? '' : '?($nullable)';
- /// Iterates through all nodes that are "downstream" of this node (i.e. if
- /// this node is nullable, then all the nodes in [downstreamNodes] will either
- /// have to be nullable, or null checks will have to be added).
- Iterable<NullabilityNode> get downstreamNodes => _downstreamNodes;
-
/// Indicates whether this node is always nullable, by construction.
bool get isAlwaysNullable => identical(nullable, ConstraintVariable.always);
@@ -162,9 +154,10 @@
CheckExpression checkNotNull,
List<NullabilityNode> guards,
Constraints constraints,
+ NullabilityGraph graph,
bool inConditionalControlFlow) {
var additionalConditions = <ConstraintVariable>[];
- sourceNode._downstreamNodes.add(destinationNode);
+ graph.connect(sourceNode, destinationNode);
if (sourceNode.nullable != null) {
additionalConditions.add(sourceNode.nullable);
var destinationNonNullIntent = destinationNode.nonNullIntent;
@@ -223,12 +216,13 @@
Expression expression,
this.left,
this.right,
+ NullabilityGraph graph,
ConstraintVariable Function(
ConditionalExpression, ConstraintVariable, ConstraintVariable)
joinNullabilities)
: super._(joinNullabilities(expression, left.nullable, right.nullable)) {
- left._downstreamNodes.add(this);
- right._downstreamNodes.add(this);
+ graph.connect(left, this);
+ graph.connect(right, this);
}
}
diff --git a/pkg/analysis_server/lib/src/nullability/transitional_api.dart b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
index 7a1b505..ac1d8df 100644
--- a/pkg/analysis_server/lib/src/nullability/transitional_api.dart
+++ b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
@@ -7,6 +7,7 @@
import 'package:analysis_server/src/nullability/constraint_variable_gatherer.dart';
import 'package:analysis_server/src/nullability/decorated_type.dart';
import 'package:analysis_server/src/nullability/expression_checks.dart';
+import 'package:analysis_server/src/nullability/nullability_graph.dart';
import 'package:analysis_server/src/nullability/nullability_node.dart';
import 'package:analysis_server/src/nullability/unit_propagation.dart';
import 'package:analyzer/dart/ast/ast.dart';
@@ -149,6 +150,8 @@
final _constraints = Solver();
+ final _graph = NullabilityGraph();
+
/// Prepares to perform nullability migration.
///
/// If [permissive] is `true`, exception handling logic will try to proceed
@@ -172,7 +175,7 @@
void processInput(CompilationUnit unit, TypeProvider typeProvider) {
unit.accept(ConstraintGatherer(typeProvider, _variables, _constraints,
- unit.declaredElement.source, _permissive, assumptions));
+ _graph, unit.declaredElement.source, _permissive, assumptions));
}
}
diff --git a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
index cc45432..5501916 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -7,6 +7,7 @@
import 'package:analysis_server/src/nullability/constraint_variable_gatherer.dart';
import 'package:analysis_server/src/nullability/decorated_type.dart';
import 'package:analysis_server/src/nullability/expression_checks.dart';
+import 'package:analysis_server/src/nullability/nullability_graph.dart';
import 'package:analysis_server/src/nullability/nullability_node.dart';
import 'package:analysis_server/src/nullability/transitional_api.dart';
import 'package:analysis_server/src/nullability/unit_propagation.dart';
@@ -31,6 +32,9 @@
@override
final _Constraints constraints = _Constraints();
+ @override
+ final graph = NullabilityGraph();
+
void assertConditional(
NullabilityNode node, NullabilityNode left, NullabilityNode right) {
var conditionalNode = node as NullabilityNodeForLUB;
@@ -788,6 +792,8 @@
abstract class ConstraintsTestBase extends MigrationVisitorTestBase {
Constraints get constraints;
+ NullabilityGraph get graph;
+
/// Analyzes the given source code, producing constraint variables and
/// constraints for it.
@override
@@ -795,8 +801,8 @@
{NullabilityMigrationAssumptions assumptions:
const NullabilityMigrationAssumptions()}) async {
var unit = await super.analyze(code);
- unit.accept(ConstraintGatherer(
- typeProvider, _variables, constraints, testSource, false, assumptions));
+ unit.accept(ConstraintGatherer(typeProvider, _variables, constraints, graph,
+ testSource, false, assumptions));
return unit;
}
}