| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 library kernel.checks; | 4 library kernel.checks; |
| 5 | 5 |
| 6 import 'ast.dart'; | 6 import 'ast.dart'; |
| 7 import 'transformations/flags.dart'; | 7 import 'transformations/flags.dart'; |
| 8 | 8 |
| 9 void verifyProgram(Program program) { | 9 void verifyProgram(Program program) { |
| 10 VerifyingVisitor.check(program); | 10 VerifyingVisitor.check(program); |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 return "$file:${location.line}:${location.column}: Verification error:" | 31 return "$file:${location.line}:${location.column}: Verification error:" |
| 32 " $details"; | 32 " $details"; |
| 33 } else { | 33 } else { |
| 34 return "Verification error: $details\n" | 34 return "Verification error: $details\n" |
| 35 "Context: '$context'.\n" | 35 "Context: '$context'.\n" |
| 36 "Node: '$node'."; | 36 "Node: '$node'."; |
| 37 } | 37 } |
| 38 } | 38 } |
| 39 } | 39 } |
| 40 | 40 |
| 41 enum TypedefState { Done, BeingChecked } |
| 42 |
| 41 /// Checks that a kernel program is well-formed. | 43 /// Checks that a kernel program is well-formed. |
| 42 /// | 44 /// |
| 43 /// This does not include any kind of type checking. | 45 /// This does not include any kind of type checking. |
| 44 class VerifyingVisitor extends RecursiveVisitor { | 46 class VerifyingVisitor extends RecursiveVisitor { |
| 45 final Set<Class> classes = new Set<Class>(); | 47 final Set<Class> classes = new Set<Class>(); |
| 46 final Set<TypeParameter> typeParameters = new Set<TypeParameter>(); | 48 final Set<Typedef> typedefs = new Set<Typedef>(); |
| 49 Set<TypeParameter> typeParametersInScope = new Set<TypeParameter>(); |
| 47 final List<VariableDeclaration> variableStack = <VariableDeclaration>[]; | 50 final List<VariableDeclaration> variableStack = <VariableDeclaration>[]; |
| 51 final Map<Typedef, TypedefState> typedefState = <Typedef, TypedefState>{}; |
| 48 bool classTypeParametersAreInScope = false; | 52 bool classTypeParametersAreInScope = false; |
| 49 | 53 |
| 50 /// If true, relax certain checks for *outline* mode. For example, don't | 54 /// If true, relax certain checks for *outline* mode. For example, don't |
| 51 /// attempt to validate constructor initializers. | 55 /// attempt to validate constructor initializers. |
| 52 bool isOutline = false; | 56 bool isOutline = false; |
| 53 | 57 |
| 54 bool inCatchBlock = false; | 58 bool inCatchBlock = false; |
| 55 | 59 |
| 56 Member currentMember; | 60 Member currentMember; |
| 57 Class currentClass; | 61 Class currentClass; |
| 58 TreeNode currentParent; | 62 TreeNode currentParent; |
| 59 | 63 |
| 60 TreeNode get context => currentMember ?? currentClass; | 64 TreeNode get context => currentMember ?? currentClass; |
| 61 | 65 |
| 62 static void check(Program program) { | 66 static void check(Program program) { |
| 63 program.accept(new VerifyingVisitor()); | 67 program.accept(new VerifyingVisitor()); |
| 64 } | 68 } |
| 65 | 69 |
| 66 defaultTreeNode(TreeNode node) { | 70 defaultTreeNode(TreeNode node) { |
| 67 visitChildren(node); | 71 visitChildren(node); |
| 68 } | 72 } |
| 69 | 73 |
| 70 problem(TreeNode node, String details) { | 74 problem(TreeNode node, String details, {TreeNode context}) { |
| 75 context ??= this.context; |
| 71 throw new VerificationError(context, node, details); | 76 throw new VerificationError(context, node, details); |
| 72 } | 77 } |
| 73 | 78 |
| 74 TreeNode enterParent(TreeNode node) { | 79 TreeNode enterParent(TreeNode node) { |
| 75 if (!identical(node.parent, currentParent)) { | 80 if (!identical(node.parent, currentParent)) { |
| 76 problem( | 81 problem( |
| 77 node, | 82 node, |
| 78 "Incorrect parent pointer: expected '${node.parent.runtimeType}'," | 83 "Incorrect parent pointer on ${node.runtimeType}:" |
| 84 " expected '${node.parent.runtimeType}'," |
| 79 " but found: '${currentParent.runtimeType}'."); | 85 " but found: '${currentParent.runtimeType}'."); |
| 80 } | 86 } |
| 81 var oldParent = currentParent; | 87 var oldParent = currentParent; |
| 82 currentParent = node; | 88 currentParent = node; |
| 83 return oldParent; | 89 return oldParent; |
| 84 } | 90 } |
| 85 | 91 |
| 86 void exitParent(TreeNode oldParent) { | 92 void exitParent(TreeNode oldParent) { |
| 87 currentParent = oldParent; | 93 currentParent = oldParent; |
| 88 } | 94 } |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 128 variableStack.add(variable); | 134 variableStack.add(variable); |
| 129 } | 135 } |
| 130 | 136 |
| 131 void undeclareVariable(VariableDeclaration variable) { | 137 void undeclareVariable(VariableDeclaration variable) { |
| 132 variable.flags &= ~VariableDeclaration.FlagInScope; | 138 variable.flags &= ~VariableDeclaration.FlagInScope; |
| 133 } | 139 } |
| 134 | 140 |
| 135 void declareTypeParameters(List<TypeParameter> parameters) { | 141 void declareTypeParameters(List<TypeParameter> parameters) { |
| 136 for (int i = 0; i < parameters.length; ++i) { | 142 for (int i = 0; i < parameters.length; ++i) { |
| 137 var parameter = parameters[i]; | 143 var parameter = parameters[i]; |
| 138 if (!typeParameters.add(parameter)) { | 144 if (!typeParametersInScope.add(parameter)) { |
| 139 problem(parameter, "Type parameter '$parameter' redeclared."); | 145 problem(parameter, "Type parameter '$parameter' redeclared."); |
| 140 } | 146 } |
| 141 } | 147 } |
| 142 } | 148 } |
| 143 | 149 |
| 144 void undeclareTypeParameters(List<TypeParameter> parameters) { | 150 void undeclareTypeParameters(List<TypeParameter> parameters) { |
| 145 typeParameters.removeAll(parameters); | 151 typeParametersInScope.removeAll(parameters); |
| 146 } | 152 } |
| 147 | 153 |
| 148 void checkVariableInScope(VariableDeclaration variable, TreeNode where) { | 154 void checkVariableInScope(VariableDeclaration variable, TreeNode where) { |
| 149 if (variable.flags & VariableDeclaration.FlagInScope == 0) { | 155 if (variable.flags & VariableDeclaration.FlagInScope == 0) { |
| 150 problem(where, "Variable '$variable' used out of scope."); | 156 problem(where, "Variable '$variable' used out of scope."); |
| 151 } | 157 } |
| 152 } | 158 } |
| 153 | 159 |
| 154 visitProgram(Program program) { | 160 visitProgram(Program program) { |
| 155 try { | 161 try { |
| 156 for (var library in program.libraries) { | 162 for (var library in program.libraries) { |
| 157 for (var class_ in library.classes) { | 163 for (var class_ in library.classes) { |
| 158 if (!classes.add(class_)) { | 164 if (!classes.add(class_)) { |
| 159 problem(class_, "Class '$class_' declared more than once."); | 165 problem(class_, "Class '$class_' declared more than once."); |
| 160 } | 166 } |
| 161 } | 167 } |
| 168 for (var typedef_ in library.typedefs) { |
| 169 if (!typedefs.add(typedef_)) { |
| 170 problem(typedef_, "Typedef '$typedef_' declared more than once."); |
| 171 } |
| 172 } |
| 162 library.members.forEach(declareMember); | 173 library.members.forEach(declareMember); |
| 163 for (var class_ in library.classes) { | 174 for (var class_ in library.classes) { |
| 164 class_.members.forEach(declareMember); | 175 class_.members.forEach(declareMember); |
| 165 } | 176 } |
| 166 } | 177 } |
| 167 visitChildren(program); | 178 visitChildren(program); |
| 168 } finally { | 179 } finally { |
| 169 for (var library in program.libraries) { | 180 for (var library in program.libraries) { |
| 170 library.members.forEach(undeclareMember); | 181 library.members.forEach(undeclareMember); |
| 171 for (var class_ in library.classes) { | 182 for (var class_ in library.classes) { |
| 172 class_.members.forEach(undeclareMember); | 183 class_.members.forEach(undeclareMember); |
| 173 } | 184 } |
| 174 } | 185 } |
| 175 variableStack.forEach(undeclareVariable); | 186 variableStack.forEach(undeclareVariable); |
| 176 } | 187 } |
| 177 } | 188 } |
| 178 | 189 |
| 190 void checkTypedef(Typedef node) { |
| 191 var state = typedefState[node]; |
| 192 if (state == TypedefState.Done) return; |
| 193 if (state == TypedefState.BeingChecked) { |
| 194 problem(node, "The typedef '$node' refers to itself", context: node); |
| 195 } |
| 196 assert(state == null); |
| 197 typedefState[node] = TypedefState.BeingChecked; |
| 198 var savedTypeParameters = typeParametersInScope; |
| 199 typeParametersInScope = node.typeParameters.toSet(); |
| 200 var savedParent = currentParent; |
| 201 currentParent = node; |
| 202 // Visit children without checking the parent pointer on the typedef itself |
| 203 // since this can be called from a context other than its true parent. |
| 204 node.visitChildren(this); |
| 205 currentParent = savedParent; |
| 206 typeParametersInScope = savedTypeParameters; |
| 207 typedefState[node] = TypedefState.Done; |
| 208 } |
| 209 |
| 210 visitTypedef(Typedef node) { |
| 211 checkTypedef(node); |
| 212 // Enter and exit the node to check the parent pointer on the typedef node. |
| 213 exitParent(enterParent(node)); |
| 214 } |
| 215 |
| 179 visitField(Field node) { | 216 visitField(Field node) { |
| 180 currentMember = node; | 217 currentMember = node; |
| 181 var oldParent = enterParent(node); | 218 var oldParent = enterParent(node); |
| 182 classTypeParametersAreInScope = !node.isStatic; | 219 classTypeParametersAreInScope = !node.isStatic; |
| 183 node.initializer?.accept(this); | 220 node.initializer?.accept(this); |
| 184 classTypeParametersAreInScope = false; | 221 classTypeParametersAreInScope = false; |
| 185 visitList(node.annotations, this); | 222 visitList(node.annotations, this); |
| 186 exitParent(oldParent); | 223 exitParent(oldParent); |
| 224 node.type.accept(this); |
| 187 currentMember = null; | 225 currentMember = null; |
| 188 } | 226 } |
| 189 | 227 |
| 190 visitProcedure(Procedure node) { | 228 visitProcedure(Procedure node) { |
| 191 currentMember = node; | 229 currentMember = node; |
| 192 var oldParent = enterParent(node); | 230 var oldParent = enterParent(node); |
| 193 classTypeParametersAreInScope = !node.isStatic; | 231 classTypeParametersAreInScope = !node.isStatic; |
| 194 node.function.accept(this); | 232 node.function.accept(this); |
| 195 classTypeParametersAreInScope = false; | 233 classTypeParametersAreInScope = false; |
| 196 visitList(node.annotations, this); | 234 visitList(node.annotations, this); |
| (...skipping 288 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 485 | 523 |
| 486 @override | 524 @override |
| 487 visitClassReference(Class node) { | 525 visitClassReference(Class node) { |
| 488 if (!classes.contains(node)) { | 526 if (!classes.contains(node)) { |
| 489 problem( | 527 problem( |
| 490 node, "Dangling reference to '$node', parent is: '${node.parent}'."); | 528 node, "Dangling reference to '$node', parent is: '${node.parent}'."); |
| 491 } | 529 } |
| 492 } | 530 } |
| 493 | 531 |
| 494 @override | 532 @override |
| 533 visitTypedefReference(Typedef node) { |
| 534 if (!typedefs.contains(node)) { |
| 535 problem( |
| 536 node, "Dangling reference to '$node', parent is: '${node.parent}'"); |
| 537 } |
| 538 } |
| 539 |
| 540 @override |
| 495 visitTypeParameterType(TypeParameterType node) { | 541 visitTypeParameterType(TypeParameterType node) { |
| 496 var parameter = node.parameter; | 542 var parameter = node.parameter; |
| 497 if (!typeParameters.contains(parameter)) { | 543 if (!typeParametersInScope.contains(parameter)) { |
| 498 problem( | 544 problem( |
| 499 currentParent, | 545 currentParent, |
| 500 "Type parameter '$parameter' referenced out of" | 546 "Type parameter '$parameter' referenced out of" |
| 501 " scope, parent is: '${parameter.parent}'."); | 547 " scope, parent is: '${parameter.parent}'."); |
| 502 } | 548 } |
| 503 if (parameter.parent is Class && !classTypeParametersAreInScope) { | 549 if (parameter.parent is Class && !classTypeParametersAreInScope) { |
| 504 problem( | 550 problem( |
| 505 currentParent, | 551 currentParent, |
| 506 "Type parameter '$parameter' referenced from" | 552 "Type parameter '$parameter' referenced from" |
| 507 " static context, parent is '${parameter.parent}'."); | 553 " static context, parent is '${parameter.parent}'."); |
| 508 } | 554 } |
| 509 } | 555 } |
| 510 | 556 |
| 511 @override | 557 @override |
| 512 visitInterfaceType(InterfaceType node) { | 558 visitInterfaceType(InterfaceType node) { |
| 513 node.visitChildren(this); | 559 node.visitChildren(this); |
| 514 if (node.typeArguments.length != node.classNode.typeParameters.length) { | 560 if (node.typeArguments.length != node.classNode.typeParameters.length) { |
| 515 problem( | 561 problem( |
| 516 currentParent, | 562 currentParent, |
| 517 "Type $node provides ${node.typeArguments.length}" | 563 "Type $node provides ${node.typeArguments.length}" |
| 518 " type arguments but the class declares" | 564 " type arguments but the class declares" |
| 519 " ${node.classNode.typeParameters.length} parameters."); | 565 " ${node.classNode.typeParameters.length} parameters."); |
| 520 } | 566 } |
| 521 } | 567 } |
| 568 |
| 569 @override |
| 570 visitTypedefType(TypedefType node) { |
| 571 checkTypedef(node.typedefNode); |
| 572 node.visitChildren(this); |
| 573 if (node.typeArguments.length != node.typedefNode.typeParameters.length) { |
| 574 problem( |
| 575 currentParent, |
| 576 "The typedef type $node provides ${node.typeArguments.length}" |
| 577 " type arguments but the typedef declares" |
| 578 " ${node.typedefNode.typeParameters.length} parameters."); |
| 579 } |
| 580 } |
| 522 } | 581 } |
| 523 | 582 |
| 524 class CheckParentPointers extends Visitor { | 583 class CheckParentPointers extends Visitor { |
| 525 static void check(TreeNode node) { | 584 static void check(TreeNode node) { |
| 526 node.accept(new CheckParentPointers(node.parent)); | 585 node.accept(new CheckParentPointers(node.parent)); |
| 527 } | 586 } |
| 528 | 587 |
| 529 TreeNode parent; | 588 TreeNode parent; |
| 530 | 589 |
| 531 CheckParentPointers([this.parent]); | 590 CheckParentPointers([this.parent]); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 542 var oldParent = parent; | 601 var oldParent = parent; |
| 543 parent = node; | 602 parent = node; |
| 544 node.visitChildren(this); | 603 node.visitChildren(this); |
| 545 parent = oldParent; | 604 parent = oldParent; |
| 546 } | 605 } |
| 547 } | 606 } |
| 548 | 607 |
| 549 void checkInitializers(Constructor constructor) { | 608 void checkInitializers(Constructor constructor) { |
| 550 // TODO(ahe): I'll add more here in other CLs. | 609 // TODO(ahe): I'll add more here in other CLs. |
| 551 } | 610 } |
| OLD | NEW |