001 /*BEGIN_COPYRIGHT_BLOCK
002 *
003 * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 * * Redistributions of source code must retain the above copyright
009 * notice, this list of conditions and the following disclaimer.
010 * * Redistributions in binary form must reproduce the above copyright
011 * notice, this list of conditions and the following disclaimer in the
012 * documentation and/or other materials provided with the distribution.
013 * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014 * names of its contributors may be used to endorse or promote products
015 * derived from this software without specific prior written permission.
016 *
017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 *
029 * This software is Open Source Initiative approved Open Source Software.
030 * Open Source Initative Approved is a trademark of the Open Source Initiative.
031 *
032 * This file is part of DrJava. Download the current version of this project
033 * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034 *
035 * END_COPYRIGHT_BLOCK*/
036
037 package edu.rice.cs.javalanglevels;
038
039 import edu.rice.cs.javalanglevels.tree.*;
040 import edu.rice.cs.javalanglevels.parser.JExprParser;
041 import java.util.*;
042 import java.io.File;
043 import edu.rice.cs.plt.reflect.JavaVersion;
044 import edu.rice.cs.plt.iter.*;
045
046 import junit.framework.TestCase;
047
048 /** We felt that this class should be named Bob because "Bob" was found to be very memorable. It is a TypeChecker
049 * for all code segments that rely on some enclosing Data and other context information. Basically, Bob sits between
050 * the TypeChecker and all other more specific type checkers (such as BodyTypeChecker, ClassBodyTypeChecker, etc)
051 * where Bob maintains the context.
052 */
053
054 public class Bob extends TypeChecker {
055 /** An incremental list of fields used because forward references among fields are not allowed.*/
056 protected LinkedList<VariableData> _vars;
057
058 /**Stores what variable datas have been newly given a value in this scope.*/
059 protected LinkedList<VariableData> thingsThatHaveBeenAssigned;
060
061 /**The context of this type checking--i.e. the data of the enclosing body.*/
062 protected Data _data;
063
064 /**The list of SymbolDatas corresponding to exceptions thrown in this body. */
065 protected LinkedList<Pair<SymbolData, JExpression>> _thrown;
066
067 /** Constructor for Bob.
068 * @param data The data that represents the context.
069 * @param file The file that corresponds to the source file
070 * @param packageName The string representing the package name
071 * @param importedFiles The list of file names that have been specifically imported
072 * @param importedPackages The list of package names that have been specifically imported
073 * @param vars The list of fields that have been assigned up to the point where Bob is called.
074 * @param thrown The list of exceptions that the context is declared to throw
075 */
076 public Bob(Data data, File file, String packageName, LinkedList<String> importedFiles,
077 LinkedList<String> importedPackages, LinkedList<VariableData> vars,
078 LinkedList<Pair<SymbolData, JExpression>> thrown) {
079 super(file, packageName, importedFiles, importedPackages);
080 if (vars == null) throw new RuntimeException("Bob called with _vars = null!");
081 _data = data;
082 _vars = vars;
083
084 thingsThatHaveBeenAssigned = new LinkedList<VariableData>();
085 _thrown = thrown;
086 }
087
088 /** @return the enclosing data*/
089 protected Data _getData() { return _data; }
090
091 /** @return true iff the first enclosing MethodData or SymbolData is a static method. */
092 protected boolean inStaticMethod() {
093 for (Data d = _data; d != null; d = d.getOuterData()) {
094 if (d instanceof MethodData) { return d.hasModifier("static"); }
095 // else if (d instanceof SymbolData) { return false; }
096 }
097 return false;
098 }
099
100 /** Return the symbol data corresponding to the lhs and the namePiece, if possible.
101 * Otherwise, return null.
102 * @param lhs The left hand side of this complex reference, or null if this is a simple reference
103 * @param namePiece The String right hand side of this reference
104 * @param jexpr The JExpression corresponding to this class reference
105 */
106 protected SymbolData findClassReference(TypeData lhs, String namePiece, JExpression jexpr) {
107 SymbolData result;
108 if (lhs == null) {
109 // Do not give an error if the SymbolData could not be found. This is done later.
110 result = getSymbolData(true, namePiece, _getData(), jexpr, false);
111
112 }
113
114 else if (lhs instanceof PackageData) {
115 // Do not give an error if the SymbolData could not be found
116 result = getSymbolData(lhs.getName() + "." + namePiece, _getData(), jexpr, false);
117 }
118
119 else {
120 // Do not give error if it could not be found, but do give an error if the reference is ambiguous.
121 result = getSymbolData(true, namePiece, lhs.getSymbolData(), jexpr, false);
122
123 // Don't check for visibility here--check for it wherever this was called from.
124 }
125
126 return result;
127
128 }
129
130
131 /**
132 * TODO: Move this code to where it is needed?
133 * Do any extra processing of this MethodInvocation, based on what level it is found at.
134 * Here, check if the MethodData is declared to throw any exceptions, add them to the list of Exceptions
135 * @param md The MethodData of the method being invoked
136 * @param jexpr The jexpression corresponding to where this method is being invoked from.
137 */
138 protected void handleMethodInvocation(MethodData md, JExpression jexpr) {
139 String[] thrown = md.getThrown();
140 for (int i = 0; i<thrown.length; i++) {
141 _thrown.addLast(new Pair<SymbolData, JExpression>(getSymbolData(thrown[i], _data, jexpr), jexpr));
142 }
143 }
144
145 /**
146 * Given a ParenthesizedExpressionList, extract its expression array and return
147 * an array with InstanceDatas for each type of the arguments. Throw an error if a non-instance type is
148 * passed as an argument.
149 * @param pel The ParenthesizedExpressionList the arguments are stored in.
150 * @return The InstanceData[] corresponding to the types of the arguments
151 */
152 protected InstanceData[] getArgTypesForInvocation(ParenthesizedExpressionList pel) {
153 Expression[] exprs = pel.getExpressions();
154 InstanceData[] newArgs = new InstanceData[exprs.length];
155 TypeData[] args = new TypeData[exprs.length];
156 ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
157 _vars, _thrown);
158 for (int i = 0; i < exprs.length; i++) {
159 args[i] = exprs[i].visit(etc);
160 if (args[i] == null || !assertFound(args[i], exprs[i])) {
161 return null;
162 }
163 if (!args[i].isInstanceType()) {
164 _addError("Cannot pass a class or interface name as a constructor argument. " +
165 "Perhaps you meant to create an instance or use " + args[i].getName() + ".class", exprs[i]);
166 }
167 newArgs[i] = args[i].getInstanceData(); // getInstanceData() is used in place of a cast
168 }
169 thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned);
170 return newArgs;
171 }
172
173 /**
174 * Assumes type_result is a SymbolData.
175 * Makes sure that the initializedvariable declarator is correct--i.e. the types match.
176 * Also, add the Variable Data corresponding to this initializiation to the _vars list, so
177 * that it can be referenced within this scope.
178 * @param that The InitializedVariableDeclarator being visited
179 * @param type_result The TypeData (should be a SymbolData) corresponding to the type on the lhs of the assignment
180 * @param name_result Not used.
181 * @param initializer_result The type of what we are initializing the varaible with
182 */
183 public TypeData forInitializedVariableDeclaratorOnly(InitializedVariableDeclarator that, TypeData type_result,
184 TypeData name_result, TypeData initializer_result) {
185 if (initializer_result != null && assertFound(initializer_result, that.getInitializer())) {
186 if (!initializer_result.isInstanceType()) {
187 _addError("Field or variable " + that.getName().getText() +
188 " cannot be initialized with the class or interface name " + initializer_result.getName() +
189 ". Perhaps you meant to create an instance or use " + initializer_result.getName() + ".class", that);
190 }
191 //we know type_result is always a SymbolData.
192 else if (!_isAssignableFrom(type_result.getSymbolData(), initializer_result.getSymbolData())) {
193 _addError("Type: \"" + type_result.getName() + "\" expected, instead found type: \"" +
194 initializer_result.getName() + "\".", that);
195 }
196 }
197 Word name = that.getName();
198 String text = that.getName().getText();
199 VariableData vd = _data.getVar(text);
200 if (vd == null) {
201 throw new RuntimeException("Internal Program Error: The field or variable " + text +
202 " was not found in this block. Please report this bug.");
203 }
204 _vars.addLast(vd);
205 return null;
206 }
207
208 /*This is not supported at any Language Level. It should have been caught during the first pass.*/
209 public TypeData forInstanceInitializer(InstanceInitializer that) {
210 throw new RuntimeException("Internal Program Error: Instance Initializers are not supported." +
211 " This should have been caught before the Type Checker Pass. Please report this bug.");
212 }
213
214 /*This is not supported at any Language Level. It should have been caught during the first pass.*/
215 public TypeData forStaticInitializer(StaticInitializer that) {
216 throw new RuntimeException("Internal Program Error: Static Initializers are not supported." +
217 " This should have been caught before the Type Checker Pass. Please report this bug.");
218 }
219
220 /*This is not supported at any Language Level. It should have been caught during the first pass.*/
221 public TypeData forLabeledStatement(LabeledStatement that) {
222 throw new RuntimeException("Internal Program Error: Labeled Statements are not supported." +
223 " This should have been caught before the Type Checker Pass. Please report this bug.");
224 }
225
226 /*
227 * Visit the expression with a new ExpressionTypeChecker, and return the result of that visitation.
228 * Keep track of what variables get values within the expression.
229 * @param that The ExpressionStatement we are visiting.
230 * @return The result of visiting the expression with the ExpressionTypeChecker.
231 */
232 public TypeData forExpressionStatement(ExpressionStatement that) {
233 ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
234 _vars, _thrown);
235 final TypeData expression_result = that.getExpression().visit(etc);
236
237 //do this so that we can keep track of anything that got assigned
238 thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned);
239
240 return expression_result;
241 }
242
243 /**
244 * Visit the ThrowStatement's expression to determine what type of Exception is being thrown.
245 * Add the corresponding SymbolData to _thrown.
246 */
247 public TypeData forThrowStatement(ThrowStatement that) {
248 ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
249 _vars, _thrown);
250 final TypeData thrown_result = that.getThrown().visit(etc);
251 thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned);
252
253 forThrowStatementOnly(that, thrown_result);
254 return SymbolData.EXCEPTION.getInstanceData();
255 }
256
257
258 /** Make sure that what is being thrown is an instantiation of a class, not a class name, and that
259 * it extends Throwable. Otherwise, give an error.
260 * @param that The throw statement we are visiting
261 * @param thrown_result The TypeData result of visiting the throw statement. It should be an InstanceData,
262 * unless there is an error in the student's code.
263 */
264 public TypeData forThrowStatementOnly(ThrowStatement that, TypeData thrown_result) {
265 if (thrown_result == null || !assertFound(thrown_result, that.getThrown())) return null;
266
267 // add the SymbolData even if we're throwing a SymbolData, not an InstanceData
268 _thrown.addLast(new Pair<SymbolData, JExpression>(thrown_result.getSymbolData(), that));
269
270 //make sure they instantiated what is being thrown.
271 if (!thrown_result.isInstanceType()) {
272 _addError("You cannot throw a class or interface name. Perhaps you mean to instantiate the exception class " +
273 thrown_result.getSymbolData().getName() + " that you are throwing", that);
274 thrown_result = thrown_result.getInstanceData();
275 }
276
277 // System.err.println("getSymbolData(\"java.lang.Throwable\", that, false, true) = " +
278 // getSymbolData("java.lang.Throwable", that, false, true));
279 //make sure what is being thrown extends java.lang.Throwable.
280 if (!_isAssignableFrom(getSymbolData("java.lang.Throwable", that, false, true), thrown_result.getSymbolData())) {
281 _addError("You are attempting to throw " + thrown_result.getSymbolData().getName() +
282 ", which does not implement the Throwable interface", that);
283 }
284 return thrown_result;
285 }
286
287 /*This is not supported at any Language Level. It should have been caught during the first pass.*/
288 public TypeData forSynchronizedStatement(SynchronizedStatement that) {
289 throw new RuntimeException("SynchronizedStatements are not supported.");
290 }
291
292
293 /* Visit the declarator of this formal parameter and return its result.
294 * @param that The Formal Parameter we are visiting.
295 */
296 public TypeData forFormalParameter(FormalParameter that) {
297 final TypeData declarator_result = that.getDeclarator().visit(this);
298 return declarator_result;
299 }
300
301 /** Visit each of the declarators of this declaration.
302 * @param that The VariableDeclaration we are visiting.
303 */
304 public TypeData forVariableDeclaration(VariableDeclaration that) {
305 final TypeData mav_result = that.getMav().visit(this);
306 final TypeData[] declarators_result = makeArrayOfRetType(that.getDeclarators().length);
307 for (int i = 0; i < that.getDeclarators().length; i++) {
308 declarators_result[i] = that.getDeclarators()[i].visit(this);
309 }
310 return null;
311 }
312
313 /** If this VariableDeclarator is uninitialized, make sure its type can be resolved and visit
314 * its name.
315 * @param that The UninitializedVariableDeclarator we are visiting.
316 */
317 public TypeData forUninitializedVariableDeclarator(UninitializedVariableDeclarator that) {
318 final TypeData type_result = getSymbolData(that.getType().getName(), _data, that.getType());
319 final TypeData name_result = that.getName().visit(this);
320 return forUninitializedVariableDeclaratorOnly(that, type_result, name_result);
321 }
322
323 /** If the VariableDeclarator is initialized, things get a little bit more complicated.
324 * Resolve the type and visit the name, like we do for the uninitilized case.
325 * Then, check to see if the initializer is an array initializer. If so, delegate.
326 * Otherwise, just visit it with an ExpressionTypeChecker.
327 * @param that The InitializedVariableDeclarator we are visiting.
328 */
329 public TypeData forInitializedVariableDeclarator(InitializedVariableDeclarator that) {
330 final SymbolData type_result = getSymbolData(that.getType().getName(), _data, that.getType());
331 final TypeData name_result = that.getName().visit(this); //we think this is always null
332
333 TypeData initializer_result;
334 if (that.getInitializer() instanceof ArrayInitializer) {
335 initializer_result = forArrayInitializerHelper((ArrayInitializer) that.getInitializer(), type_result);
336 }
337 else {
338 ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
339 _vars, _thrown);
340 initializer_result = that.getInitializer().visit(etc);
341 thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned); //incorporate this list here
342 }
343 return forInitializedVariableDeclaratorOnly(that, type_result, name_result, initializer_result);
344 }
345
346 /** A variable data can be assigned to if it is not final or if it does not have a value.
347 * (i.e. final variables that already have a value cannot be assigned to. Everything else can be).
348 */
349 protected boolean canBeAssigned(VariableData vd) {
350 return !vd.isFinal() || !vd.hasValue();
351 }
352
353 /** Makes sure that the specified type is an array type, and then
354 * examines the elements in the array initializer and makes sure each has a type assignable to
355 * the elementType of the specified array type. Returns an instance data corresponding to the type of the array.
356 */
357 public TypeData forArrayInitializerHelper(ArrayInitializer that, SymbolData type) {
358 if (type == null) {return null;}
359 if (!(type instanceof ArrayData)) {_addError("You cannot initialize the non-array type " + type.getName() +
360 " with an array initializer", that); return type.getInstanceData();}
361
362 SymbolData elementType = ((ArrayData) type).getElementType();
363 VariableInitializerI[] elements = that.getItems();
364 TypeData[] result = makeArrayOfRetType(elements.length);
365
366 for (int i = 0; i<elements.length; i++) {
367 if (elements[i] instanceof ArrayInitializer) {
368 result[i] = forArrayInitializerHelper((ArrayInitializer) elements[i], elementType);
369 }
370 else {
371 ExpressionTypeChecker etc = new ExpressionTypeChecker(_data, _file, _package, _importedFiles, _importedPackages,
372 _vars, _thrown);
373 result[i] = elements[i].visit(etc);
374
375 // Incorporate the things that were assigned in the expression here
376 thingsThatHaveBeenAssigned.addAll(etc.thingsThatHaveBeenAssigned);
377
378 if (result[i] != null) {
379 if (assertFound(result[i], (JExpression) that.getItems()[i])) {
380 if (!result[i].getSymbolData().isAssignableTo(elementType, LanguageLevelConverter.OPT.javaVersion())) {
381 _addError("The elements of this initializer should have type " + elementType.getName() + " but element "
382 + i + " has type " + result[i].getSymbolData().getName(), (JExpression) that.getItems()[i]);
383 }
384 else {
385 assertInstanceType(result[i], "The elements of this initializer should all be instances," +
386 " but you have specified the type name " + result[i].getName(),
387 (JExpression) that.getItems()[i]);
388 }
389 }
390 }
391 }
392 }
393 return type.getInstanceData();
394 }
395
396 /** Look up the SymbolData for this InnerClass within the enclosing data, check for cyclic inheritance,
397 * and then visit everything inside the inner class.
398 * @param that The InnerClassDef we're visiting
399 */
400 public TypeData forInnerClassDef(InnerClassDef that) {
401 String className = that.getName().getText();
402
403 // This works because className will never be a qualified name
404 SymbolData sd = _data.getInnerClassOrInterface(className);
405
406 if (sd == null) throw new RuntimeException("SymbolData is null for class name = " + className);
407
408 // Check for cyclic inheritance
409 if (checkForCyclicInheritance(sd, new LinkedList<SymbolData>(), that)) { return null; }
410 final TypeData mav_result = that.getMav().visit(this);
411 final TypeData name_result = that.getName().visit(this);
412 final TypeData[] typeParameters_result = makeArrayOfRetType(that.getTypeParameters().length);
413 for (int i = 0; i < that.getTypeParameters().length; i++) {
414 typeParameters_result[i] = that.getTypeParameters()[i].visit(this);
415 }
416 final TypeData superclass_result = that.getSuperclass().visit(this);
417 final TypeData[] interfaces_result = makeArrayOfRetType(that.getInterfaces().length);
418 for (int i = 0; i < that.getInterfaces().length; i++) {
419 interfaces_result[i] = that.getInterfaces()[i].visit(this);
420 }
421 final TypeData body_result =
422 that.getBody().visit(new ClassBodyTypeChecker(sd, _file, _package, _importedFiles, _importedPackages, _vars,
423 _thrown));
424 return null;
425 }
426
427 /** Look for the inner interface inside of the enclosing data.
428 * Then, visit everything that needs to be visited.
429 * @param that The InnerInterfaceDef that is being visited.
430 */
431 public TypeData forInnerInterfaceDef(InnerInterfaceDef that) {
432 String className = that.getName().getText();
433 SymbolData sd = _data.getInnerClassOrInterface(className); // This works because className will never be a qualified name
434 // if (sd == null) { System.out.println("I tried to look up " + className + " in " + _data.getName() + " but I got back null");}
435
436 // Check for cyclic inheritance
437 if (checkForCyclicInheritance(sd, new LinkedList<SymbolData>(), that)) {
438 return null;
439 }
440 final TypeData mav_result = that.getMav().visit(this);
441 final TypeData name_result = that.getName().visit(this);
442 final TypeData[] typeParameters_result = makeArrayOfRetType(that.getTypeParameters().length);
443 for (int i = 0; i < that.getTypeParameters().length; i++) {
444 typeParameters_result[i] = that.getTypeParameters()[i].visit(this);
445 }
446 final TypeData[] interfaces_result = makeArrayOfRetType(that.getInterfaces().length);
447 for (int i = 0; i < that.getInterfaces().length; i++) {
448 interfaces_result[i] = that.getInterfaces()[i].visit(this);
449 }
450
451 final TypeData body_result =
452 that.getBody().visit(new InterfaceBodyTypeChecker(sd, _file, _package, _importedFiles, _importedPackages, _vars,
453 _thrown));
454 return null;
455
456 }
457
458
459 /**
460 * Compare the two lists of variable datas, and if a data is in both lists, mark it as
461 * having been assigned.
462 * @param l1 One of the lists of variable datas
463 * @param l2 The other list of variable datas.
464 */
465 void reassignVariableDatas(LinkedList<VariableData> l1, LinkedList<VariableData> l2) {
466 for (int i = 0; i<l1.size(); i++) {
467 if (l2.contains(l1.get(i))) {
468 l1.get(i).gotValue();
469 l1.get(i).gotValue();
470 }
471 }
472 }
473
474 /** Compare a list of variable datas and a list of list of variable datas.
475 * If a variable data is in the list and in each list of the lists of lists, mark it as having been
476 * assigned.
477 * @param tryBlock The list of variable datas.
478 * @param catchBlocks The list of list of variable datas.
479 */
480 void reassignLotsaVariableDatas(LinkedList<VariableData> tryBlock, LinkedList<LinkedList<VariableData>> catchBlocks) {
481 for (int i = 0; i<tryBlock.size(); i++) {
482 boolean seenIt = true;
483 for (int j = 0; j<catchBlocks.size(); i++) {
484 if (!catchBlocks.get(j).contains(tryBlock.get(i))) {seenIt = false;}
485 }
486 if (seenIt) { //find the variable data in vars and give it a value!
487 tryBlock.get(i).gotValue();
488 }
489 }
490 }
491
492
493 /** If an exception is thrown but not caught, throw the appropriate error, based on the JExpression.*/
494 public void handleUncheckedException(SymbolData sd, JExpression j) {
495 if (j instanceof MethodInvocation) {
496 _addError("The method " + ((MethodInvocation)j).getName().getText() + " is declared to throw the exception " +
497 sd.getName() + " which needs to be caught or declared to be thrown", j);
498 }
499 else if (j instanceof ThrowStatement) {
500 _addError("This statement throws the exception " + sd.getName() +
501 " which needs to be caught or declared to be thrown", j);
502 }
503 else if (j instanceof ClassInstantiation) {
504 _addError("The constructor for the class " + ((ClassInstantiation)j).getType().getName() +
505 " is declared to throw the exception " + sd.getName() +
506 " which needs to be caught or declared to be thrown.", j);
507 }
508 else if (j instanceof SuperConstructorInvocation) {
509 _addError("The constructor of this class's super class could throw the exception " + sd.getName() +
510 ", so the enclosing constructor needs to be declared to throw it", j);
511 }
512 else if (j instanceof ThisConstructorInvocation) {
513 _addError("This constructor could throw the exception " + sd.getName() +
514 ", so this enclosing constructor needs to be declared to throw it", j);
515 }
516
517 else if (j instanceof BracedBody) { //then this is because of an implicit super constructor reference.
518 _addError("There is an implicit call to the superclass's constructor here. " +
519 "That constructor could throw the exception " + sd.getName() +
520 ", so the enclosing constructor needs to be declared to throw it", j);
521 }
522
523 else {
524 throw new RuntimeException("Internal Program Error: Something besides a method invocation or throw statement" +
525 " threw an exception. Please report this bug.");
526 }
527 }
528
529 /** Returns whether the sd is a checked exception, i.e. one that needs to be caught or declared to be thrown.
530 * This is defined as all subclasses of java.lang.Throwable except for subclasses of java.lang.RuntimeException
531 */
532 public boolean isCheckedException(SymbolData sd, JExpression that) {
533 return sd.isSubClassOf(getSymbolData("java.lang.Throwable", _data, that, false)) &&
534 ! sd.isSubClassOf(getSymbolData("java.lang.RuntimeException", _data, that, false)) &&
535 ! sd.isSubClassOf(getSymbolData("java.lang.Error", _data, that, false));
536 }
537
538 /** Return true if the Exception is a checked exception yet is not caught or declared to be thrown, and false
539 * otherwise. An exception is a checked if it does not extend either java.lang.RuntimeException or java.lang.Error,
540 * and is not declared to be thrown by the enclosing method.
541 * @param sd The SymbolData of the Exception we are checking.
542 * @param that The JExpression passed to getSymbolData for error purposes.
543 */
544 public boolean isUncaughtCheckedException(SymbolData sd, JExpression that) {
545 return isCheckedException(sd, that);
546 }
547
548 /** Visit each of the items in the body and make sure that none throw uncaught exceptions */
549 public TypeData forBody(Body that) {
550 final TypeData[] items_result = makeArrayOfRetType(that.getStatements().length);
551 for (int i = 0; i < that.getStatements().length; i++) {
552 items_result[i] = that.getStatements()[i].visit(this);
553 //walk over what has been thrown and throw an error if it contains an unchecked exception
554 for (int j = 0; j < this._thrown.size(); j++) {
555 if (isUncaughtCheckedException(this._thrown.get(j).getFirst(), that)) {
556 handleUncheckedException(this._thrown.get(j).getFirst(), this._thrown.get(j).getSecond());
557 }
558 }
559 }
560
561 return forBodyOnly(that, items_result);
562 }
563
564 /** Delegate to forBody*/
565 public TypeData forBracedBody(BracedBody that) { return forBody(that); }
566
567 /** Delegate to forBody*/
568 public TypeData forUnbracedBody(UnbracedBody that) { return forBody(that); }
569
570 /** Test the methods defined in the enclosing class. */
571 public static class BobTest extends TestCase {
572
573 private Bob _b;
574
575 private SymbolData _sd1;
576 private SymbolData _sd2;
577 private SymbolData _sd3;
578 private SymbolData _sd4;
579 private SymbolData _sd5;
580 private SymbolData _sd6;
581 private ModifiersAndVisibility _publicMav =
582 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"public"});
583 private ModifiersAndVisibility _protectedMav =
584 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"protected"});
585 private ModifiersAndVisibility _privateMav =
586 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"private"});
587 private ModifiersAndVisibility _packageMav = new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[0]);
588 private ModifiersAndVisibility _abstractMav =
589 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"abstract"});
590 private ModifiersAndVisibility _finalMav =
591 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"final"});
592 private ModifiersAndVisibility _finalPublicMav =
593 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"final", "public"});
594 private ModifiersAndVisibility _publicAbstractMav =
595 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"public", "abstract"});
596 private ModifiersAndVisibility _publicStaticMav =
597 new ModifiersAndVisibility(SourceInfo.NO_INFO, new String[] {"public", "static"});
598
599
600 public BobTest() { this(""); }
601 public BobTest(String name) { super(name); }
602
603 public void setUp() {
604 errors = new LinkedList<Pair<String, JExpressionIF>>();
605 LanguageLevelConverter.symbolTable.clear();
606 _b = new Bob(null, new File(""), "", new LinkedList<String>(), new LinkedList<String>(),
607 new LinkedList<VariableData>(), new LinkedList<Pair<SymbolData, JExpression>>());
608 LanguageLevelConverter.OPT = new Options(JavaVersion.JAVA_5, EmptyIterable.<File>make());
609 _b._importedPackages.addFirst("java.lang");
610 _sd1 = new SymbolData("i.like.monkey");
611 _sd2 = new SymbolData("i.like.giraffe");
612 _sd3 = new SymbolData("zebra");
613 _sd4 = new SymbolData("u.like.emu");
614 _sd5 = new SymbolData("");
615 _sd6 = new SymbolData("cebu");
616 _b._data = _sd1;
617 }
618
619 public void testForInitializedVariableDeclarator() {
620 LanguageLevelVisitor llv =
621 new LanguageLevelVisitor(_b._file,
622 _b._package,
623 _b._importedFiles,
624 _b._importedPackages, new LinkedList<String>(),
625 new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>(),
626 new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
627
628 // LanguageLevelConverter.symbolTable.clear();
629
630 SourceInfo si = SourceInfo.NO_INFO;
631 Expression e1 = new IntegerLiteral(si, 1);
632 Expression e2 = new IntegerLiteral(si, 2);
633 Expression e3 = new PlusExpression(si, new IntegerLiteral(si, 3), new CharLiteral(si, 'e'));
634 Expression e4 = new CharLiteral(si, 'c');
635
636 ArrayType intArrayType =
637 new ArrayType(SourceInfo.NO_INFO, "int[]", new PrimitiveType(SourceInfo.NO_INFO, "int"));
638
639 //make sure it works -- most testing done in testArrayInitializerHelper
640 ArrayData intArray = new ArrayData(SymbolData.INT_TYPE, llv, si);
641 intArray.setIsContinuation(false);
642 symbolTable.remove("int[]");
643 symbolTable.put("int[]", intArray);
644
645 _b._data.addVar(new VariableData("foozle", _publicMav, intArray, false, _b._data));
646 InitializedVariableDeclarator ivd =
647 new InitializedVariableDeclarator(SourceInfo.NO_INFO, intArrayType,
648 new Word(SourceInfo.NO_INFO, "foozle"),
649 new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e3, e4}));
650
651 assertEquals("Should return null", null, ivd.visit(_b));
652 assertEquals("There should be no errors", 0, errors.size());
653 }
654
655 public void testForInitializedVariableDeclaratorOnly() {
656 SymbolData sd1 = SymbolData.DOUBLE_TYPE;
657 SymbolData sd2 = SymbolData.BOOLEAN_TYPE;
658 SymbolData sd3 = SymbolData.INT_TYPE;
659 _b._data.addVar(new VariableData("j", _publicMav, SymbolData.DOUBLE_TYPE, false, _b._data));
660
661 InitializedVariableDeclarator ivd =
662 new InitializedVariableDeclarator(SourceInfo.NO_INFO,
663 JExprParser.NO_TYPE,
664 new Word(SourceInfo.NO_INFO, "j"),
665 new DoubleLiteral(SourceInfo.NO_INFO, 1.0));
666
667
668 assertEquals("Two assignable types should not throw an error; return null.", null,
669 _b.forInitializedVariableDeclaratorOnly(ivd, sd1, sd1, sd3.getInstanceData()));
670 assertEquals("Should be no errors", 0, errors.size());
671
672 assertEquals("Two unassignable types should throw an error; return null.", null,
673 _b.forInitializedVariableDeclaratorOnly(ivd, sd1, sd1, sd2.getInstanceData()));
674 assertEquals("Should now be one error", 1, errors.size());
675 assertEquals("Error message should be correct:", "Type: \"double\" expected, instead found type: \"boolean\".",
676 errors.getLast().getFirst());
677
678 SymbolData foo = new SymbolData("Foo");
679 assertEquals("An initialization from a SymbolData should return null", null,
680 _b.forInitializedVariableDeclaratorOnly(ivd, sd1, null, foo));
681 assertEquals("There should be 2 errors", 2, errors.size());
682 assertEquals("Error message should be correct:",
683 "Field or variable j cannot be initialized with the class or interface name Foo. " +
684 "Perhaps you meant to create an instance or use Foo.class", errors.getLast().getFirst());
685 }
686
687 public void testForThrowStatementOnly() {
688 ThrowStatement s = new ThrowStatement(SourceInfo.NO_INFO, new NullLiteral(SourceInfo.NO_INFO));
689 SymbolData exception = _b.getSymbolData("java.lang.Throwable", s, false, true);
690 InstanceData exceptionInstance = exception.getInstanceData();
691
692 SymbolData notAnException = new SymbolData("bob");
693 InstanceData naeInstance = notAnException.getInstanceData();
694
695
696 assertEquals("When a SymbolData is the thrown type, return its InstanceData", exceptionInstance,
697 _b.forThrowStatementOnly(s, exception));
698 assertEquals("There should be 1 error", 1, errors.size());
699
700 assertEquals("Error message should be correct",
701 "You cannot throw a class or interface name. " +
702 "Perhaps you mean to instantiate the exception class java.lang.Throwable that you are throwing",
703 errors.get(0).getFirst());
704
705 assertEquals("When a thrown type does not implement Throwable, return the type anyway", naeInstance,
706 _b.forThrowStatementOnly(s, naeInstance));
707 assertEquals("There should be 2 errors", 2, errors.size());
708 assertEquals("Error message should be correct",
709 "You are attempting to throw bob, which does not implement the Throwable interface",
710 errors.getLast().getFirst());
711 }
712
713
714 public void testForArrayInitializerHelper() {
715 LanguageLevelVisitor llv =
716 new LanguageLevelVisitor(_b._file, _b._package, _b._importedFiles,
717 _b._importedPackages, new LinkedList<String>(),
718 new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>(),
719 new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
720 // LanguageLevelConverter.symbolTable = llv.symbolTable = _b.symbolTable;
721
722 SourceInfo si = SourceInfo.NO_INFO;
723
724 Expression e1 = new IntegerLiteral(si, 1);
725 Expression e2 = new IntegerLiteral(si, 2);
726 Expression e3 = new PlusExpression(si, new IntegerLiteral(si, 3), new CharLiteral(si, 'e'));
727 Expression e4 = new CharLiteral(si, 'c');
728 Expression e5 = new DoubleLiteral(si, 5.8);
729 Expression e6 = new SimpleNameReference(SourceInfo.NO_INFO, new Word(SourceInfo.NO_INFO, "int"));
730
731 ArrayInitializer a1 = new ArrayInitializer(si, new VariableInitializerI[] {e1, e3, e4});
732 ArrayInitializer a2 = new ArrayInitializer(si, new VariableInitializerI[] {e2, e3, e1});
733
734 Expression nl = new NullLiteral(si);
735
736 //it works for a one dimensional array
737 ArrayData intArray = new ArrayData(SymbolData.INT_TYPE, llv, si);
738 intArray.setIsContinuation(false);
739 symbolTable.remove("int[]");
740 symbolTable.put("int[]", intArray);
741
742 ArrayInitializer ia = new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e3, e4});
743 assertEquals("Should return instance of int[]", intArray.getInstanceData(),
744 _b.forArrayInitializerHelper(ia, intArray));
745 assertEquals("There should be no errors", 0, errors.size());
746
747 //it works for a 2 dimensional array
748 ArrayData intArray2 = new ArrayData(intArray, llv, si);
749 intArray2.setIsContinuation(false);
750 symbolTable.put("int[][]", intArray2);
751
752 ia = new ArrayInitializer(si, new VariableInitializerI[]{a1, a2});
753 assertEquals("Should return instance of int[][]", intArray2.getInstanceData(),
754 _b.forArrayInitializerHelper(ia, intArray2));
755 assertEquals("There should be no errors", 0, errors.size());
756
757 //it works for a 2 dimensional array with null as its elements
758 ia = new ArrayInitializer(si, new VariableInitializerI[] {nl, nl});
759 assertEquals("Should return instance of int[][]", intArray2.getInstanceData(),
760 _b.forArrayInitializerHelper(ia, intArray2));
761
762 //throw an error if the type passed to the helper is not an array data
763 assertEquals("Should return double", SymbolData.DOUBLE_TYPE.getInstanceData(),
764 _b.forArrayInitializerHelper(ia, SymbolData.DOUBLE_TYPE));
765 assertEquals("There should be one error message", 1, errors.size());
766 assertEquals("The error message should be correct",
767 "You cannot initialize the non-array type double with an array initializer",
768 errors.getLast().getFirst());
769
770 //throw an error if the type of one of the elements doesn't match
771 ia = new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e5, e4});
772 assertEquals("Should return instance of int[]", intArray.getInstanceData(),
773 _b.forArrayInitializerHelper(ia, intArray));
774 assertEquals("There should be two error messages", 2, errors.size());
775 assertEquals("The error message should be correct",
776 "The elements of this initializer should have type int but element 2 has type double",
777 errors.getLast().getFirst());
778
779 //throw an error if null in 1 dimensional int array
780 ia = new ArrayInitializer(si, new VariableInitializerI[] {nl, nl});
781 assertEquals("Should return instance of int[]", intArray.getInstanceData(),
782 _b.forArrayInitializerHelper(ia, intArray));
783 assertEquals("There should be four error messages", 4, errors.size());
784 assertEquals("The error message should be correct",
785 "The elements of this initializer should have type int but element 0 has type null",
786 errors.get(2).getFirst());
787 assertEquals("The error message should be correct",
788 "The elements of this initializer should have type int but element 1 has type null",
789 errors.get(3).getFirst());
790
791 //should throw error if type name is passed instead of instance
792 ia = new ArrayInitializer(si, new VariableInitializerI[] {e1, e2, e3, e4, e6});
793 assertEquals("Should return instance of int[]", intArray.getInstanceData(),
794 _b.forArrayInitializerHelper(ia, intArray));
795 assertEquals("Should now be 5 error messages", 5, errors.size());
796 assertEquals("Error message should be correct",
797 "The elements of this initializer should all be instances, but you have specified the type name" +
798 " int. Perhaps you meant to create a new instance of int",
799 errors.getLast().getFirst());
800 }
801
802 public void testFindClassReference() {
803 SymbolData string = new SymbolData("java.lang.String");
804 string.setIsContinuation(false);
805 string.setPackage("java.lang");
806 symbolTable.remove("java.lang.String");
807 symbolTable.put("java.lang.String", string);
808
809 //if lhs is null, just look up SymbolData
810 assertEquals("Should return string", string,
811 _b.findClassReference(null, "java.lang.String", new NullLiteral(SourceInfo.NO_INFO)));
812 assertEquals("Should not be an error", 0, errors.size());
813
814 //if SymbolData cannot be found, do not add error--just return null
815 assertEquals("Should return null", null,
816 _b.findClassReference(null, "non-existant", new NullLiteral(SourceInfo.NO_INFO)));
817 assertEquals("Should be no errors", 0, errors.size());
818
819 //if LHS is package data, try to look up fully qualified name
820 assertEquals("Should return string", string,
821 _b.findClassReference(new PackageData("java.lang"), "String", new NullLiteral(SourceInfo.NO_INFO)));
822 assertEquals("Should not be an error", 0, errors.size());
823
824 //if symbol data cannot be found, do not give error
825 assertEquals("Should return null", null,
826 _b.findClassReference(new PackageData("nonsense"), "non-existant",
827 new NullLiteral(SourceInfo.NO_INFO)));
828 assertEquals("Should be no errors", 0, errors.size());
829
830 //otherwise, try to look up symbolData from context of lhs
831 SymbolData inner = new SymbolData("java.lang.String$Inner");
832 inner.setIsContinuation(false);
833 inner.setPackage("java.lang");
834 inner.setOuterData(string);
835 string.addInnerClass(inner);
836 assertEquals("Should return inner", inner,
837 _b.findClassReference(string, "Inner", new NullLiteral(SourceInfo.NO_INFO)));
838 assertEquals("Should be no errors", 0, errors.size());
839
840 //do not give error if it could not be found
841 assertEquals("Should return null", null,
842 _b.findClassReference(string, "non-existant", new NullLiteral(SourceInfo.NO_INFO)));
843 assertEquals("Should be no errors", 0, errors.size());
844 }
845 }
846 }