Skip to content

Commit 7303fca

Browse files
stereotype441Commit Queue
authored and
Commit Queue
committed
[analyzer] Improve dead_code detection for for statements/elements.
Special care has to be taken when reporting dead code for `for` statements and `for` elements, because the "updaters" part of a for-loop appears before the body in source, but is executed after the body. This means that if the end of the loop body is unreachable, then a separate dead_code range might need to be generated to cover the updaters. Previously, this was accomplished by special-case logic in `NullSafetyDeadCodeVerifier.flowEnd`: if the parent of `_firstDeadNode` was a `ForStatement` (or a `Block` inside a `ForStatement`), extra logic would be triggered to emit a dead_code range to cover the updaters. This mostly worked, but it didn't handle a `Block` inside a `Block` inside a `ForStatement`, and I couldn't figure out a good way to generalize it to handle `ForElement`s. The new approach is for the resolver to make two additional calls into `NullSafetyDeadCodeVerifier`, just after visiting the "condition" part of the loop and just before visiting the updaters. Combining together information gathered at these two times, the `NullSafetyDeadCodeVerifier` can determine whether or not it's necessary to emit a separate dead_code range to cover the updaters. Pre-existing tests in `dead_code_test.dart` still pass, and I've added a bunch of tests of the new functionality. These improvements pave the way for improvements to the analysis server's "remove dead code" fix, which I want to do in order to prepare for the rollout of sound flow analysis. Bug: #60438 Change-Id: I6d2c43fd7c06adcd4de22d42144b033319e949a3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/424180 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 8d62a1f commit 7303fca

File tree

3 files changed

+327
-23
lines changed

3 files changed

+327
-23
lines changed

pkg/analyzer/lib/src/dart/resolver/for_resolver.dart

+6
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,16 @@ class ForResolver {
253253
);
254254
}
255255

256+
var deadCodeForPartsState =
257+
_resolver.nullSafetyDeadCodeVerifier.for_conditionEnd();
256258
_resolver.flowAnalysis.for_bodyBegin(node, condition);
257259
visitBody();
258260

259261
_resolver.flowAnalysis.flow?.for_updaterBegin();
262+
_resolver.nullSafetyDeadCodeVerifier.for_updaterBegin(
263+
forParts.updaters,
264+
deadCodeForPartsState,
265+
);
260266
for (var updater in forParts.updaters) {
261267
_resolver.analyzeExpression(updater, _resolver.operations.unknownType);
262268
_resolver.popRewrite();

pkg/analyzer/lib/src/error/dead_code_verifier.dart

+51-23
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ typedef _CatchClausesVerifierReporter =
2525
List<Object> arguments,
2626
);
2727

28+
/// State information captured by [NullSafetyDeadCodeVerifier.for_conditionEnd]
29+
/// for later use by [NullSafetyDeadCodeVerifier.for_updaterBegin].
30+
class DeadCodeForPartsState {
31+
/// The value of [NullSafetyDeadCodeVerifier._firstDeadNode] at the time of
32+
/// the call to [NullSafetyDeadCodeVerifier.for_conditionEnd]
33+
final AstNode? _firstDeadNodeAsOfConditionEnd;
34+
35+
DeadCodeForPartsState._({required AstNode? firstDeadNodeAsOfConditionEnd})
36+
: _firstDeadNodeAsOfConditionEnd = firstDeadNodeAsOfConditionEnd;
37+
}
38+
2839
/// A visitor that finds dead code, other than unreachable code that is
2940
/// handled in [NullSafetyDeadCodeVerifier].
3041
class DeadCodeVerifier extends RecursiveAstVisitor<void> {
@@ -302,13 +313,6 @@ class NullSafetyDeadCodeVerifier {
302313
}
303314
} else if (parent is ForParts) {
304315
node = parent.updaters.last;
305-
} else if (parent is ForStatement) {
306-
_reportForUpdaters(parent);
307-
} else if (parent is Block) {
308-
var grandParent = parent.parent;
309-
if (grandParent is ForStatement) {
310-
_reportForUpdaters(grandParent);
311-
}
312316
} else if (parent is BinaryExpression) {
313317
offset = parent.operator.offset;
314318
node = parent.rightOperand;
@@ -330,6 +334,46 @@ class NullSafetyDeadCodeVerifier {
330334
_firstDeadNode = null;
331335
}
332336

337+
/// Performs the necessary dead code analysis when reaching the end of the
338+
/// `condition` part of [ForParts].
339+
///
340+
/// Some state is returned, which should be passed to [for_updaterBegin] after
341+
/// visiting the body of the loop.
342+
DeadCodeForPartsState for_conditionEnd() {
343+
// Capture the state of this class so that `for_updaterBegin` can use it to
344+
// decide whether it's necessary to create an extra dead code report for the
345+
// updaters.
346+
return DeadCodeForPartsState._(
347+
firstDeadNodeAsOfConditionEnd: _firstDeadNode,
348+
);
349+
}
350+
351+
/// Performs the necessary dead code analysis when reaching the beginning of
352+
/// the `updaters` part of [ForParts].
353+
///
354+
/// [state] should be the state returned by [for_conditionEnd].
355+
void for_updaterBegin(
356+
NodeListImpl<ExpressionImpl> updaters,
357+
DeadCodeForPartsState state,
358+
) {
359+
var isReachable = _flowAnalysis?.flow?.isReachable ?? true;
360+
if (!isReachable && state._firstDeadNodeAsOfConditionEnd == null) {
361+
// A dead code range started either at the beginning of the loop body or
362+
// somewhere inside it, and so the updaters are dead. Since the updaters
363+
// appear textually before the loop body, they need their own dead code
364+
// warning.
365+
var beginToken = updaters.beginToken;
366+
var endToken = updaters.endToken;
367+
if (beginToken != null && endToken != null) {
368+
_errorReporter.atOffset(
369+
offset: beginToken.offset,
370+
length: endToken.end - beginToken.offset,
371+
errorCode: WarningCode.DEAD_CODE,
372+
);
373+
}
374+
}
375+
}
376+
333377
/// Rewites [_firstDeadNode] if it is equal to [oldNode], as [oldNode] is
334378
/// being rewritten into [newNode] in the syntax tree.
335379
void maybeRewriteFirstDeadNode(AstNode oldNode, AstNode newNode) {
@@ -428,22 +472,6 @@ class NullSafetyDeadCodeVerifier {
428472
return false;
429473
}
430474

431-
void _reportForUpdaters(ForStatement node) {
432-
var forParts = node.forLoopParts;
433-
if (forParts is ForParts) {
434-
var updaters = forParts.updaters;
435-
var beginToken = updaters.beginToken;
436-
var endToken = updaters.endToken;
437-
if (beginToken != null && endToken != null) {
438-
_errorReporter.atOffset(
439-
offset: beginToken.offset,
440-
length: endToken.end - beginToken.offset,
441-
errorCode: WarningCode.DEAD_CODE,
442-
);
443-
}
444-
}
445-
}
446-
447475
void _verifyUnassignedSimpleIdentifier(
448476
AstNode node,
449477
Expression? target,

0 commit comments

Comments
 (0)