| // Copyright (c) 2023, 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 'dart:io'; |
| |
| import 'package:kernel/kernel.dart'; |
| |
| void main(List<String> args) { |
| for (String arg in args) { |
| File f = new File(arg); |
| if (!f.existsSync()) { |
| print("Skipping '$arg', not a file."); |
| continue; |
| } |
| Component c; |
| try { |
| c = loadComponentFromBytes(f.readAsBytesSync()); |
| } catch (e) { |
| print("Skipping '$arg', couldn't load as kernel: '$e'"); |
| continue; |
| } |
| try { |
| List<Warning> warnings = UnreachableIfFinder.find(c); |
| if (warnings.isEmpty) { |
| print("No warnings found for '$arg'"); |
| } else { |
| for (Warning warning in warnings) { |
| print(warning); |
| print(""); |
| } |
| } |
| } catch (e, st) { |
| print("Failure '$e' on '$arg':"); |
| print(st); |
| } |
| } |
| } |
| |
| class UnreachableIfFinder extends RecursiveVisitor { |
| static List<Warning> find(Component c) { |
| EffectivelyFinal effectivelyFinal = new EffectivelyFinal._(); |
| c.accept(effectivelyFinal); |
| UnreachableIfFinder unreachableIfFinder = |
| new UnreachableIfFinder._(effectivelyFinal.unwritten); |
| c.accept(unreachableIfFinder); |
| return unreachableIfFinder.warnings; |
| } |
| |
| final Set<VariableDeclaration> unwritten; |
| |
| UnreachableIfFinder._(this.unwritten); |
| |
| List<Warning> warnings = []; |
| |
| Map<VariableDeclaration, bool> knownValues = {}; |
| |
| @override |
| void visitIfStatement(IfStatement node) { |
| helperForIfLikeStructure(node.condition, node.then, node.otherwise, node); |
| } |
| |
| @override |
| void visitConditionalExpression(ConditionalExpression node) { |
| helperForIfLikeStructure(node.condition, node.then, node.otherwise, node); |
| } |
| |
| void helperForIfLikeStructure( |
| Expression condition, |
| TreeNode then, |
| TreeNode? otherwise, |
| TreeNode originNode, |
| ) { |
| // TODO(jensj): We could make the visit return a bool? instead and use that |
| // from the condition instead of doing special casing on `Not` and |
| // `VariableGet`. |
| VariableDeclaration? newKnownValueHere; |
| bool conditionNegated = false; |
| |
| if (condition is Not) { |
| condition = condition.operand; |
| conditionNegated = true; |
| } |
| |
| if (condition is VariableGet) { |
| bool? knownValue = knownValues[condition.variable]; |
| if (knownValue != null) { |
| if (conditionNegated) knownValue = !knownValue; |
| String? hint; |
| |
| if (knownValue && otherwise != null) { |
| hint = "The else branch will never execute."; |
| } else if (!knownValue) { |
| hint = "The then branch will never execute."; |
| } |
| warnings.add(new Warning( |
| originNode.location, "Condition is always $knownValue", hint)); |
| } else { |
| if (condition.variable.isFinal || |
| unwritten.contains(condition.variable)) { |
| newKnownValueHere = condition.variable; |
| } |
| } |
| } |
| if (newKnownValueHere != null) { |
| knownValues[newKnownValueHere] = conditionNegated ? false : true; |
| } |
| then.accept(this); |
| if (newKnownValueHere != null) { |
| knownValues[newKnownValueHere] = conditionNegated ? true : false; |
| } |
| otherwise?.accept(this); |
| if (newKnownValueHere != null) { |
| knownValues.remove(newKnownValueHere); |
| } |
| } |
| } |
| |
| class EffectivelyFinal extends RecursiveVisitor { |
| final Set<VariableDeclaration> unwritten = {}; |
| |
| EffectivelyFinal._(); |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| unwritten.add(node); |
| super.visitVariableDeclaration(node); |
| } |
| |
| @override |
| void visitVariableSet(VariableSet node) { |
| unwritten.remove(node.variable); |
| super.visitVariableSet(node); |
| } |
| } |
| |
| class Warning { |
| final Location? location; |
| final String message; |
| final String? hint; |
| |
| Warning(this.location, this.message, this.hint); |
| |
| @override |
| String toString() { |
| return "Warning: $message @ $location.${hint != null ? "\n$hint." : ""}"; |
| } |
| } |