OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 library kernel.checks; |
| 5 |
| 6 import 'ast.dart'; |
| 7 import 'transformations/flags.dart'; |
| 8 |
| 9 void verifyProgram(Program program) { |
| 10 VerifyingVisitor.check(program); |
| 11 } |
| 12 |
| 13 /// Checks that a kernel program is well-formed. |
| 14 /// |
| 15 /// This does not include any kind of type checking. |
| 16 class VerifyingVisitor extends RecursiveVisitor { |
| 17 final Set<Class> classes = new Set<Class>(); |
| 18 final Set<TypeParameter> typeParameters = new Set<TypeParameter>(); |
| 19 final List<VariableDeclaration> variableStack = <VariableDeclaration>[]; |
| 20 bool classTypeParametersAreInScope = false; |
| 21 |
| 22 Member currentMember; |
| 23 Class currentClass; |
| 24 TreeNode currentParent; |
| 25 |
| 26 TreeNode get context => currentMember ?? currentClass; |
| 27 |
| 28 static void check(Program program) { |
| 29 program.accept(new VerifyingVisitor()); |
| 30 } |
| 31 |
| 32 defaultTreeNode(TreeNode node) { |
| 33 visitChildren(node); |
| 34 } |
| 35 |
| 36 TreeNode enterParent(TreeNode node) { |
| 37 if (!identical(node.parent, currentParent)) { |
| 38 throw 'Incorrect parent pointer on ${node.runtimeType} in $context. ' |
| 39 'Parent pointer is ${node.parent.runtimeType}, ' |
| 40 'actual parent is ${currentParent.runtimeType}.'; |
| 41 } |
| 42 var oldParent = currentParent; |
| 43 currentParent = node; |
| 44 return oldParent; |
| 45 } |
| 46 |
| 47 void exitParent(TreeNode oldParent) { |
| 48 currentParent = oldParent; |
| 49 } |
| 50 |
| 51 int enterLocalScope() => variableStack.length; |
| 52 |
| 53 void exitLocalScope(int stackHeight) { |
| 54 for (int i = stackHeight; i < variableStack.length; ++i) { |
| 55 undeclareVariable(variableStack[i]); |
| 56 } |
| 57 variableStack.length = stackHeight; |
| 58 } |
| 59 |
| 60 void visitChildren(TreeNode node) { |
| 61 var oldParent = enterParent(node); |
| 62 node.visitChildren(this); |
| 63 exitParent(oldParent); |
| 64 } |
| 65 |
| 66 void visitWithLocalScope(TreeNode node) { |
| 67 int stackHeight = enterLocalScope(); |
| 68 visitChildren(node); |
| 69 exitLocalScope(stackHeight); |
| 70 } |
| 71 |
| 72 void declareMember(Member member) { |
| 73 if (member.transformerFlags & TransformerFlag.seenByVerifier != 0) { |
| 74 throw '$member has been declared more than once (${member.location})'; |
| 75 } |
| 76 member.transformerFlags |= TransformerFlag.seenByVerifier; |
| 77 } |
| 78 |
| 79 void undeclareMember(Member member) { |
| 80 member.transformerFlags &= ~TransformerFlag.seenByVerifier; |
| 81 } |
| 82 |
| 83 void declareVariable(VariableDeclaration variable) { |
| 84 if (variable.flags & VariableDeclaration.FlagInScope != 0) { |
| 85 throw '$variable declared more than once (${variable.location})'; |
| 86 } |
| 87 variable.flags |= VariableDeclaration.FlagInScope; |
| 88 variableStack.add(variable); |
| 89 } |
| 90 |
| 91 void undeclareVariable(VariableDeclaration variable) { |
| 92 variable.flags &= ~VariableDeclaration.FlagInScope; |
| 93 } |
| 94 |
| 95 void checkVariableInScope(VariableDeclaration variable, TreeNode where) { |
| 96 if (variable.flags & VariableDeclaration.FlagInScope == 0) { |
| 97 throw 'Variable $variable used out of scope in $context ' |
| 98 '(${where.location})'; |
| 99 } |
| 100 } |
| 101 |
| 102 visitProgram(Program program) { |
| 103 for (var library in program.libraries) { |
| 104 classes.addAll(library.classes); |
| 105 library.members.forEach(declareMember); |
| 106 for (var class_ in library.classes) { |
| 107 class_.members.forEach(declareMember); |
| 108 } |
| 109 } |
| 110 visitChildren(program); |
| 111 for (var library in program.libraries) { |
| 112 library.members.forEach(undeclareMember); |
| 113 for (var class_ in library.classes) { |
| 114 class_.members.forEach(undeclareMember); |
| 115 } |
| 116 } |
| 117 } |
| 118 |
| 119 visitField(Field node) { |
| 120 currentMember = node; |
| 121 var oldParent = enterParent(node); |
| 122 classTypeParametersAreInScope = !node.isStatic; |
| 123 node.initializer?.accept(this); |
| 124 classTypeParametersAreInScope = false; |
| 125 visitList(node.annotations, this); |
| 126 exitParent(oldParent); |
| 127 currentMember = null; |
| 128 } |
| 129 |
| 130 visitProcedure(Procedure node) { |
| 131 currentMember = node; |
| 132 var oldParent = enterParent(node); |
| 133 classTypeParametersAreInScope = !node.isStatic; |
| 134 node.function.accept(this); |
| 135 classTypeParametersAreInScope = false; |
| 136 visitList(node.annotations, this); |
| 137 exitParent(oldParent); |
| 138 currentMember = null; |
| 139 } |
| 140 |
| 141 visitConstructor(Constructor node) { |
| 142 currentMember = node; |
| 143 classTypeParametersAreInScope = true; |
| 144 // The constructor member needs special treatment due to parameters being |
| 145 // in scope in the initializer list. |
| 146 var oldParent = enterParent(node); |
| 147 int stackHeight = enterLocalScope(); |
| 148 visitChildren(node.function); |
| 149 visitList(node.initializers, this); |
| 150 exitLocalScope(stackHeight); |
| 151 classTypeParametersAreInScope = false; |
| 152 visitList(node.annotations, this); |
| 153 exitParent(oldParent); |
| 154 classTypeParametersAreInScope = false; |
| 155 currentMember = null; |
| 156 } |
| 157 |
| 158 visitClass(Class node) { |
| 159 currentClass = node; |
| 160 typeParameters.addAll(node.typeParameters); |
| 161 var oldParent = enterParent(node); |
| 162 classTypeParametersAreInScope = false; |
| 163 visitList(node.annotations, this); |
| 164 classTypeParametersAreInScope = true; |
| 165 visitList(node.typeParameters, this); |
| 166 visitList(node.fields, this); |
| 167 visitList(node.constructors, this); |
| 168 visitList(node.procedures, this); |
| 169 exitParent(oldParent); |
| 170 typeParameters.removeAll(node.typeParameters); |
| 171 currentClass = null; |
| 172 } |
| 173 |
| 174 visitFunctionNode(FunctionNode node) { |
| 175 typeParameters.addAll(node.typeParameters); |
| 176 visitWithLocalScope(node); |
| 177 typeParameters.removeAll(node.typeParameters); |
| 178 } |
| 179 |
| 180 visitFunctionType(FunctionType node) { |
| 181 for (int i = 1; i < node.namedParameters.length; ++i) { |
| 182 if (node.namedParameters[i - 1].compareTo(node.namedParameters[i]) >= 0) { |
| 183 throw 'Named parameters are not sorted on function type found in ' |
| 184 '$context'; |
| 185 } |
| 186 } |
| 187 typeParameters.addAll(node.typeParameters); |
| 188 for (var typeParameter in node.typeParameters) { |
| 189 typeParameter.bound?.accept(this); |
| 190 } |
| 191 visitList(node.positionalParameters, this); |
| 192 visitList(node.namedParameters, this); |
| 193 node.returnType.accept(this); |
| 194 typeParameters.removeAll(node.typeParameters); |
| 195 } |
| 196 |
| 197 visitBlock(Block node) { |
| 198 visitWithLocalScope(node); |
| 199 } |
| 200 |
| 201 visitForStatement(ForStatement node) { |
| 202 visitWithLocalScope(node); |
| 203 } |
| 204 |
| 205 visitForInStatement(ForInStatement node) { |
| 206 visitWithLocalScope(node); |
| 207 } |
| 208 |
| 209 visitLet(Let node) { |
| 210 visitWithLocalScope(node); |
| 211 } |
| 212 |
| 213 visitCatch(Catch node) { |
| 214 visitWithLocalScope(node); |
| 215 } |
| 216 |
| 217 visitVariableDeclaration(VariableDeclaration node) { |
| 218 visitChildren(node); |
| 219 declareVariable(node); |
| 220 } |
| 221 |
| 222 visitVariableGet(VariableGet node) { |
| 223 checkVariableInScope(node.variable, node); |
| 224 } |
| 225 |
| 226 visitVariableSet(VariableSet node) { |
| 227 checkVariableInScope(node.variable, node); |
| 228 visitChildren(node); |
| 229 } |
| 230 |
| 231 @override |
| 232 defaultMemberReference(Member node) { |
| 233 if (node.transformerFlags & TransformerFlag.seenByVerifier == 0) { |
| 234 throw 'Dangling reference to $node found in $context.\n' |
| 235 'Parent pointer is set to ${node.parent}'; |
| 236 } |
| 237 } |
| 238 |
| 239 @override |
| 240 visitClassReference(Class node) { |
| 241 if (!classes.contains(node)) { |
| 242 throw 'Dangling reference to $node found in $context.\n' |
| 243 'Parent pointer is set to ${node.parent}'; |
| 244 } |
| 245 } |
| 246 |
| 247 @override |
| 248 visitTypeParameterType(TypeParameterType node) { |
| 249 var parameter = node.parameter; |
| 250 if (!typeParameters.contains(parameter)) { |
| 251 throw 'Type parameter $parameter referenced out of scope in $context.\n' |
| 252 'Parent pointer is set to ${parameter.parent}'; |
| 253 } |
| 254 if (parameter.parent is Class && !classTypeParametersAreInScope) { |
| 255 throw 'Type parameter $parameter referenced from static context ' |
| 256 'in $context.\n' |
| 257 'Parent pointer is set to ${parameter.parent}'; |
| 258 } |
| 259 } |
| 260 |
| 261 @override |
| 262 visitInterfaceType(InterfaceType node) { |
| 263 node.visitChildren(this); |
| 264 if (node.typeArguments.length != node.classNode.typeParameters.length) { |
| 265 throw 'Type $node provides ${node.typeArguments.length} type arguments ' |
| 266 'but the class declares ${node.classNode.typeParameters.length} ' |
| 267 'parameters. Found in $context.'; |
| 268 } |
| 269 } |
| 270 } |
| 271 |
| 272 class CheckParentPointers extends Visitor { |
| 273 static void check(TreeNode node) { |
| 274 node.accept(new CheckParentPointers(node.parent)); |
| 275 } |
| 276 |
| 277 TreeNode parent; |
| 278 |
| 279 CheckParentPointers([this.parent]); |
| 280 |
| 281 defaultTreeNode(TreeNode node) { |
| 282 if (node.parent != parent) { |
| 283 throw 'Parent pointer on ${node.runtimeType} ' |
| 284 'is ${node.parent.runtimeType} ' |
| 285 'but should be ${parent.runtimeType}'; |
| 286 } |
| 287 var oldParent = parent; |
| 288 parent = node; |
| 289 node.visitChildren(this); |
| 290 parent = oldParent; |
| 291 } |
| 292 } |
OLD | NEW |