Skip to content

Commit

Permalink
Replace InferenceContext and ThisAccessTracker with simpler mechanisms.
Browse files Browse the repository at this point in the history
During my work on dart-lang/language#3648, I
ran into some trybot failures with an exception that looked like this:

    RangeError (index): Invalid value: Valid value range is empty: -1
    #0      List.[] (dart:core-patch/growable_array.dart:264:36)
    #1      List.removeLast (dart:core-patch/growable_array.dart:336:20)
    #2      InferenceContext.popFunctionBodyContext (package:analyzer/src/generated/resolver.dart:124:33)
    #3      ResolverVisitor.visitBlockFunctionBody (package:analyzer/src/generated/resolver.dart:1890:38)

Some quick reading of the code revealed that `popFunctionBodyContext`
always removed a single entry from a list, and
`pushFunctionBodyContext` always added a single entry to it. The two
calls were always paired up using a straightforward try/finally
pattern that seemed like it should guarantee proper nesting, making
this exception impossible:

    try {
      inferenceContext.pushFunctionBodyContext(...);
      ...
    } finally {
      ...
      inferenceContext.popFunctionBodyContext(node);
    }

After a lot of headscratching and experimenting, I eventually figured
out what was happening: an exception was being thrown during
`pushFunctionBodyContext`, _before_ it had a chance to add an entry to
the list. But since the exception happened inside the `try` block, the
call to `popFunctionBodyContext` would happen anyway. As a result, the
pop would fail, causing its own exception, and the exception that was
the original source of the problem would be lost.

This seemed like a code smell to me: where possible, the clean-up
logic in `finally` clauses should be simple enough that it can always
succeed, without causing an exception, even if a previous exception
has put data structures in an unexpected state.

And I had gained enough familiarity with the code over the course of
my debugging to see that what we were doing in those `finally` clauses
was more complex than necessary:

- In the ResolverVisitor, we were pushing and popping a stack of
  `BodyInferenceContext` objects using the try/finally pattern
  described above. But we were only ever accessing the top entry on
  the stack, which meant that the same state could be maintained with
  a single BodyInferenceContext pointer, and some logic that can't
  possibly lead to an exception in the `finally` clause:

    var oldBodyContext = _bodyContext;
    try {
      _bodyContext = ...;
      ...
    } finally {
      _bodyContext = oldBodyContext;
    }

- In the ResolverVisitor and the ErrorVerifier, we were also pushing
  and popping a stack of booleans tracking whether the currently
  enclosing function (or initializer) has access to `this`. In the
  ResolverVisitor, this information wasn't being used at all, so it
  could be safely removed. In the ErrorVerifier, it was being used,
  but it was possible to simplify it in a similar way, so that it was
  tracked with a single boolean (`_hasAccessToThis`), rather than a
  stack.

Simplifying this logic brings several advantages:

- As noted above, since it's now impossible for an exception to occur
  in the `finally` clause, exceptions occurring in the `try` clause
  won't get lost, making debugging easier.

- The code should be more performant, since it no longer requires
  auxiliary heap-allocated stacks.

- The code is (IMHO) easier to understand, since the try/catch pattern
  for maintaining the new `_bodyContext` and `_hasAccessToThis` fields
  just involves saving a field in a local variable (and restoring it
  later), rather than calling out to separate classes.

Change-Id: I61ae80fb28a69760ea0b2856a6954b4a68cfcbe1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358200
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
  • Loading branch information
stereotype441 authored and Commit Queue committed Mar 18, 2024
1 parent 3cf6ac9 commit 80dc547
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class YieldStatementResolver {
TypeSystemImpl get _typeSystem => _resolver.typeSystem;

void resolve(YieldStatement node) {
var bodyContext = _resolver.inferenceContext.bodyContext;
var bodyContext = _resolver.bodyContext;
if (bodyContext != null && bodyContext.isGenerator) {
_resolve_generator(bodyContext, node);
} else {
Expand Down
29 changes: 19 additions & 10 deletions pkg/analyzer/lib/src/generated/error_verifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error_detection_helpers.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/parser.dart' show ParserErrorCode;
import 'package:analyzer/src/generated/this_access_tracker.dart';
import 'package:analyzer/src/summary2/macro_application_error.dart';
import 'package:analyzer/src/summary2/macro_type_location.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
Expand Down Expand Up @@ -216,8 +215,8 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
/// in the scope of an extension.
ExtensionElement? _enclosingExtension;

/// The helper for tracking if the current location has access to `this`.
final ThisAccessTracker _thisAccessTracker = ThisAccessTracker.unit();
/// Whether the current location has access to `this`.
bool _hasAccessToThis = false;

/// The context of the method or function that we are currently visiting, or
/// `null` if we are not inside a method or function.
Expand Down Expand Up @@ -401,11 +400,12 @@ class ErrorVerifier extends RecursiveAstVisitor<void>

@override
void visitBlockFunctionBody(BlockFunctionBody node) {
_thisAccessTracker.enterFunctionBody(node);
var oldHasAccessToThis = _hasAccessToThis;
try {
_hasAccessToThis = _computeThisAccessForFunctionBody(node);
super.visitBlockFunctionBody(node);
} finally {
_thisAccessTracker.exitFunctionBody(node);
_hasAccessToThis = oldHasAccessToThis;
}
}

Expand Down Expand Up @@ -674,12 +674,13 @@ class ErrorVerifier extends RecursiveAstVisitor<void>

@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
_thisAccessTracker.enterFunctionBody(node);
var oldHasAccessToThis = _hasAccessToThis;
try {
_hasAccessToThis = _computeThisAccessForFunctionBody(node);
_returnTypeVerifier.verifyExpressionFunctionBody(node);
super.visitExpressionFunctionBody(node);
} finally {
_thisAccessTracker.exitFunctionBody(node);
_hasAccessToThis = oldHasAccessToThis;
}
}

Expand Down Expand Up @@ -759,7 +760,6 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
@override
void visitFieldDeclaration(covariant FieldDeclarationImpl node) {
var fields = node.fields;
_thisAccessTracker.enterFieldDeclaration(node);
_isInStaticVariableDeclaration = node.isStatic;
_isInInstanceNotLateVariableDeclaration =
!node.isStatic && !node.fields.isLate;
Expand All @@ -771,7 +771,9 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
);
}
}
var oldHasAccessToThis = _hasAccessToThis;
try {
_hasAccessToThis = !node.isStatic && node.fields.isLate;
_checkForExtensionTypeDeclaresInstanceField(node);
_checkForNotInitializedNonNullableStaticField(node);
_checkForWrongTypeParameterVarianceInField(node);
Expand All @@ -788,7 +790,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
} finally {
_isInStaticVariableDeclaration = false;
_isInInstanceNotLateVariableDeclaration = false;
_thisAccessTracker.exitFieldDeclaration(node);
_hasAccessToThis = oldHasAccessToThis;
}
}

Expand Down Expand Up @@ -3684,7 +3686,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
///
/// See [CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS].
void _checkForInvalidReferenceToThis(ThisExpression expression) {
if (!_thisAccessTracker.hasAccess) {
if (!_hasAccessToThis) {
errorReporter.atNode(
expression,
CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS,
Expand Down Expand Up @@ -5822,6 +5824,13 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
}
}

bool _computeThisAccessForFunctionBody(FunctionBody node) =>
switch (node.parent) {
ConstructorDeclaration(:var factoryKeyword) => factoryKeyword == null,
MethodDeclaration(:var isStatic) => !isStatic,
_ => _hasAccessToThis
};

/// Given an [expression] in a switch case whose value is expected to be an
/// enum constant, return the name of the constant.
String? _getConstantName(Expression expression) {
Expand Down
95 changes: 29 additions & 66 deletions pkg/analyzer/lib/src/generated/resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ import 'package:analyzer/src/generated/element_resolver.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error_detection_helpers.dart';
import 'package:analyzer/src/generated/static_type_analyzer.dart';
import 'package:analyzer/src/generated/this_access_tracker.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/generated/variable_type_provider.dart';
import 'package:analyzer/src/task/inference_error.dart';
Expand All @@ -97,50 +96,6 @@ typedef SharedPatternField
/// promoted.
typedef WhyNotPromotedGetter = Map<DartType, NonPromotionReason> Function();

/// Maintains and manages contextual type information used for
/// inferring types.
class InferenceContext {
final ResolverVisitor _resolver;

/// The type system in use.
final TypeSystemImpl _typeSystem;

/// The stack of contexts for nested function bodies.
final List<BodyInferenceContext> _bodyContexts = [];

InferenceContext._(ResolverVisitor resolver)
: _resolver = resolver,
_typeSystem = resolver.typeSystem;

BodyInferenceContext? get bodyContext {
if (_bodyContexts.isNotEmpty) {
return _bodyContexts.last;
} else {
return null;
}
}

DartType popFunctionBodyContext(FunctionBody node) {
var context = _bodyContexts.removeLast();

var flow = _resolver.flowAnalysis.flow;

return context.computeInferredReturnType(
endOfBlockIsReachable: flow == null || flow.isReachable,
);
}

void pushFunctionBodyContext(FunctionBody node, DartType? imposedType) {
_bodyContexts.add(
BodyInferenceContext(
typeSystem: _typeSystem,
node: node,
imposedType: imposedType,
),
);
}
}

/// Instances of the class `ResolverVisitor` are used to resolve the nodes
/// within a single compilation unit.
class ResolverVisitor extends ThrowingAstVisitor<void>
Expand Down Expand Up @@ -244,10 +199,9 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
@override
final TypeSystemImpl typeSystem;

/// The helper for tracking if the current location has access to `this`.
final ThisAccessTracker _thisAccessTracker = ThisAccessTracker.unit();

late final InferenceContext inferenceContext;
/// Inference context information for the current function body, if the
/// current node is inside a function body.
BodyInferenceContext? _bodyContext;

/// If a class, or mixin, is being resolved, the type of the class.
/// Otherwise `null`.
Expand Down Expand Up @@ -417,11 +371,14 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
flowAnalysis,
);
elementResolver = ElementResolver(this);
inferenceContext = InferenceContext._(this);
typeAnalyzer = StaticTypeAnalyzer(this);
_functionReferenceResolver = FunctionReferenceResolver(this);
}

/// Inference context information for the current function body, if the
/// current node is inside a function body.
BodyInferenceContext? get bodyContext => _bodyContext;

/// Return the element representing the function containing the current node,
/// or `null` if the current node is not contained in a function.
///
Expand Down Expand Up @@ -1891,16 +1848,16 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
@override
DartType visitBlockFunctionBody(BlockFunctionBody node,
{DartType? imposedType}) {
var oldBodyContext = _bodyContext;
try {
inferenceContext.pushFunctionBodyContext(node, imposedType);
_thisAccessTracker.enterFunctionBody(node);
_bodyContext = BodyInferenceContext(
typeSystem: typeSystem, node: node, imposedType: imposedType);
checkUnreachableNode(node);
node.visitChildren(this);
return _finishFunctionBodyInference();
} finally {
_thisAccessTracker.exitFunctionBody(node);
imposedType = inferenceContext.popFunctionBodyContext(node);
_bodyContext = oldBodyContext;
}
return imposedType;
}

@override
Expand Down Expand Up @@ -2349,25 +2306,25 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
return imposedType ?? typeProvider.dynamicType;
}

var oldBodyContext = _bodyContext;
try {
inferenceContext.pushFunctionBodyContext(node, imposedType);
_thisAccessTracker.enterFunctionBody(node);
var bodyContext = _bodyContext = BodyInferenceContext(
typeSystem: typeSystem, node: node, imposedType: imposedType);

checkUnreachableNode(node);
analyzeExpression(
node.expression,
inferenceContext.bodyContext!.contextType,
bodyContext.contextType,
);
popRewrite();

flowAnalysis.flow?.handleExit();

inferenceContext.bodyContext!.addReturnExpression(node.expression);
bodyContext.addReturnExpression(node.expression);
return _finishFunctionBodyInference();
} finally {
_thisAccessTracker.exitFunctionBody(node);
imposedType = inferenceContext.popFunctionBodyContext(node);
_bodyContext = oldBodyContext;
}
return imposedType;
}

@override
Expand Down Expand Up @@ -2440,15 +2397,13 @@ class ResolverVisitor extends ThrowingAstVisitor<void>

@override
void visitFieldDeclaration(FieldDeclaration node) {
_thisAccessTracker.enterFieldDeclaration(node);
try {
assert(_thisType == null);
_setupThisType();
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitFieldDeclaration(node);
} finally {
_thisAccessTracker.exitFieldDeclaration(node);
_thisType = null;
}
}
Expand Down Expand Up @@ -3232,13 +3187,13 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
if (expression != null) {
analyzeExpression(
expression,
inferenceContext.bodyContext?.contextType,
bodyContext?.contextType,
);
// Pick up the expression again in case it was rewritten.
expression = popRewrite();
}

inferenceContext.bodyContext?.addReturnExpression(expression);
bodyContext?.addReturnExpression(expression);
flowAnalysis.flow?.handleExit();
}

Expand Down Expand Up @@ -3607,6 +3562,14 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
return true;
}

DartType _finishFunctionBodyInference() {
var flow = flowAnalysis.flow;

return _bodyContext!.computeInferredReturnType(
endOfBlockIsReachable: flow == null || flow.isReachable,
);
}

/// Infers type arguments corresponding to [typeParameters] used it the
/// [declaredType], so that thr resulting type is a subtype of [contextType].
List<DartType> _inferTypeArguments({
Expand Down
45 changes: 0 additions & 45 deletions pkg/analyzer/lib/src/generated/this_access_tracker.dart

This file was deleted.

0 comments on commit 80dc547

Please sign in to comment.