Clover coverage report - DynamicJava Test Coverage (dynamicjava-20130622-r5436)
Coverage timestamp: Sat Jun 22 2013 03:01:29 CDT
file stats: LOC: 887   Methods: 85
NCLOC: 773   Classes: 11
 
 Source file Conditionals Statements Methods TOTAL
SourceChecker.java 0% 0% 0% 0%
coverage
 1    package edu.rice.cs.dynamicjava.sourcechecker;
 2   
 3    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 4   
 5    import java.io.File;
 6    import java.io.FileFilter;
 7    import java.io.FileNotFoundException;
 8    import java.io.FileReader;
 9    import java.io.PrintWriter;
 10    import java.lang.reflect.Field;
 11    import java.util.ArrayList;
 12    import java.util.Arrays;
 13    import java.util.EnumSet;
 14    import java.util.HashSet;
 15    import java.util.LinkedHashMap;
 16    import java.util.LinkedHashSet;
 17    import java.util.List;
 18    import java.util.Map;
 19    import java.util.Set;
 20   
 21    import koala.dynamicjava.interpreter.NodeProperties;
 22    import koala.dynamicjava.interpreter.error.ExecutionError;
 23    import koala.dynamicjava.parser.wrapper.JavaCCParser;
 24    import koala.dynamicjava.parser.wrapper.ParseError;
 25    import koala.dynamicjava.tree.*;
 26    import koala.dynamicjava.tree.tiger.HookTypeName;
 27    import koala.dynamicjava.tree.visitor.DepthFirstVisitor;
 28    import edu.rice.cs.dynamicjava.Options;
 29    import edu.rice.cs.dynamicjava.interpreter.*;
 30    import edu.rice.cs.dynamicjava.symbol.*;
 31    import edu.rice.cs.dynamicjava.symbol.type.*;
 32    import edu.rice.cs.plt.collect.CollectUtil;
 33    import edu.rice.cs.plt.collect.IndexedRelation;
 34    import edu.rice.cs.plt.collect.PredicateSet;
 35    import edu.rice.cs.plt.collect.UnindexedRelation;
 36    import edu.rice.cs.plt.collect.Relation;
 37    import edu.rice.cs.plt.io.IOUtil;
 38    import edu.rice.cs.plt.iter.IterUtil;
 39    import edu.rice.cs.plt.lambda.Box;
 40    import edu.rice.cs.plt.lambda.Lambda;
 41    import edu.rice.cs.plt.lambda.Lambda2;
 42    import edu.rice.cs.plt.lambda.LambdaUtil;
 43    import edu.rice.cs.plt.lambda.Predicate2;
 44    import edu.rice.cs.plt.lambda.SimpleBox;
 45    import edu.rice.cs.plt.lambda.Thunk;
 46    import edu.rice.cs.plt.recur.RecursionStack2;
 47    import edu.rice.cs.plt.reflect.PathClassLoader;
 48    import edu.rice.cs.plt.text.ArgumentParser;
 49    import edu.rice.cs.plt.text.TextUtil;
 50    import edu.rice.cs.plt.tuple.Option;
 51    import edu.rice.cs.plt.tuple.Pair;
 52   
 53    public class SourceChecker {
 54   
 55    private final Options _opt;
 56    private final boolean _quiet;
 57    private int _statusCount;
 58    private Iterable<CompilationUnit> _processed;
 59   
 60  0 public SourceChecker(Options opt, boolean quiet) {
 61  0 _opt = opt;
 62  0 _quiet = quiet;
 63  0 _statusCount = 0;
 64  0 _processed = IterUtil.empty();
 65    }
 66   
 67  0 public Iterable<CompilationUnit> processed() { return _processed; }
 68   
 69  0 public void check(File... sources) throws InterpreterException {
 70  0 check(IterUtil.asIterable(sources), IterUtil.<File>empty());
 71    }
 72   
 73  0 public void check(Iterable<? extends File> sources) throws InterpreterException {
 74  0 check(sources, IterUtil.<File>empty());
 75    }
 76   
 77  0 public void check(Iterable<? extends File> sources, Iterable<? extends File> classPath)
 78    throws InterpreterException {
 79  0 Iterable<CompilationUnit> tree = parse(sources);
 80  0 _processed = IterUtil.compose(_processed, tree);
 81  0 TypeContext context = makeContext(tree, classPath);
 82  0 Relation<TypeDeclaration, ClassChecker> decls = extractDeclarations(tree, context);
 83  0 initializeClassSignatures(decls);
 84  0 checkSignatures(decls);
 85  0 checkBodies(decls);
 86    }
 87   
 88  0 private Iterable<CompilationUnit> parse(Iterable<? extends File> sources) throws InterpreterException {
 89  0 final List<CompilationUnit> result = new ArrayList<CompilationUnit>();
 90  0 Iterable<File> files = IterUtil.collapse(IterUtil.map(sources, new Lambda<File, Iterable<File>>() {
 91    private final FileFilter _filter = IOUtil.extensionFilePredicate("java");
 92  0 public Iterable<File> value(File f) { return IOUtil.listFilesRecursively(f, _filter); }
 93    }));
 94  0 new Phase<File>("Parsing") {
 95  0 protected void step(File source) throws InterpreterException {
 96  0 try {
 97  0 JavaCCParser parser = new JavaCCParser(new FileReader(source), source, _opt);
 98  0 result.add(parser.parseCompilationUnit());
 99    }
 100  0 catch (ParseError e) { throw new ParserException(e); }
 101  0 catch (FileNotFoundException e) { throw new SourceException(e); }
 102    }
 103  0 protected SourceInfo location(File f) { return SourceInfo.point(f, 0, 0); }
 104    }.run(files);
 105  0 return result;
 106    }
 107   
 108  0 private TypeContext makeContext(Iterable<CompilationUnit> sources, Iterable<? extends File> cp) {
 109  0 Library classLib = SymbolUtil.classLibrary(new PathClassLoader(null, cp));
 110  0 debug.logStart("creating TreeLibrary");
 111  0 Library sourceLib = new TreeLibrary(sources, classLib.classLoader(), _opt);
 112  0 debug.logEnd("creating TreeLibrary");
 113  0 return new ImportContext(new LibraryContext(new LibraryContext(classLib), sourceLib), _opt);
 114    }
 115   
 116  0 private Relation<TypeDeclaration, ClassChecker> extractDeclarations(Iterable<CompilationUnit> sources,
 117    TypeContext context)
 118    throws InterpreterException {
 119  0 final CompilationUnitChecker unitChecker = new CompilationUnitChecker(context, _opt);
 120  0 final Relation<TypeDeclaration, ClassChecker> checkers = UnindexedRelation.makeLinkedHashBased();
 121  0 new Phase<CompilationUnit>("Resolving imports") {
 122  0 protected void step(CompilationUnit u) throws InterpreterException {
 123  0 checkers.addAll(unitChecker.extractDeclarations(u));
 124    }
 125  0 protected SourceInfo location(CompilationUnit arg) { return arg.getSourceInfo(); }
 126    }.run(sources);
 127  0 return checkers;
 128    }
 129   
 130  0 private void initializeClassSignatures(Relation<TypeDeclaration, ClassChecker> decls) throws InterpreterException {
 131  0 new ClassCheckerPhase("Checking class signatures") {
 132  0 protected void step(TypeDeclaration ast, ClassChecker checker) { checker.initializeClassSignatures(ast); }
 133    }.run(decls);
 134    }
 135   
 136  0 private void checkSignatures(Relation<TypeDeclaration, ClassChecker> decls) throws InterpreterException {
 137  0 new ClassCheckerPhase("Checking class member signatures") {
 138  0 protected void step(TypeDeclaration ast, ClassChecker checker) { checker.checkSignatures(ast); }
 139    }.run(decls);
 140    }
 141   
 142  0 private void checkBodies(Relation<TypeDeclaration, ClassChecker> decls) throws InterpreterException {
 143  0 new ClassCheckerPhase("Checking class member bodies") {
 144  0 protected void step(TypeDeclaration ast, ClassChecker checker) { checker.checkBodies(ast); }
 145    }.run(decls);
 146    }
 147   
 148   
 149  0 private void startStatus(String description) {
 150  0 _statusCount = 0;
 151  0 if (!_quiet) {
 152  0 String fullDesc = TextUtil.padRight(description + "...", ' ', 36);
 153  0 System.out.print(fullDesc);
 154  0 System.out.flush();
 155    }
 156    }
 157   
 158  0 private void incrementStatus() {
 159  0 _statusCount++;
 160    // arbitrarily chose 10 as the interval for status printouts
 161  0 if (!_quiet && (_statusCount % 10 == 0)) { System.out.print('*'); System.out.flush(); }
 162    }
 163   
 164  0 private void endStatus() {
 165  0 if (!_quiet) { System.out.println(); }
 166    }
 167   
 168   
 169    private abstract class Phase<T> {
 170    private final String _description;
 171  0 protected Phase(String description) { _description = description; }
 172   
 173    protected abstract void step(T arg) throws InterpreterException;
 174    protected abstract SourceInfo location(T arg);
 175   
 176  0 public void run(Iterable<? extends T> args) throws InterpreterException {
 177  0 List<InterpreterException> errors = new ArrayList<InterpreterException>();
 178  0 debug.logStart(_description);
 179  0 startStatus(_description);
 180  0 for (T arg : args) {
 181  0 debug.logStart("location", location(arg));
 182  0 try { step(arg); }
 183  0 catch (InterpreterException e) { errors.add(e); }
 184  0 catch (RuntimeException e) { errors.add(new InternalException(e, location(arg))); }
 185  0 incrementStatus();
 186  0 debug.logEnd();
 187    }
 188  0 endStatus();
 189  0 debug.logEnd(_description);
 190  0 if (!errors.isEmpty()) { throw CompositeException.make(errors); }
 191    }
 192    }
 193   
 194    private abstract class ClassCheckerPhase extends Phase<Pair<TypeDeclaration, ClassChecker>> {
 195  0 protected ClassCheckerPhase(String description) { super(description); }
 196  0 protected final void step(Pair<TypeDeclaration, ClassChecker> arg) throws InterpreterException {
 197  0 try { step(arg.first(), arg.second()); }
 198  0 catch (ExecutionError e) { throw extractErrors(arg.first()); }
 199    }
 200  0 protected final SourceInfo location(Pair<TypeDeclaration, ClassChecker> arg) {
 201  0 return arg.first().getSourceInfo();
 202    }
 203    protected abstract void step(TypeDeclaration ast, ClassChecker checker);
 204    }
 205   
 206   
 207    /** Get all ERROR values associated with the given AST. */
 208  0 private static InterpreterException extractErrors(Node ast) {
 209    // accumulate in a set to avoid duplicates from DAGs
 210  0 final Set<ExecutionError> result = new LinkedHashSet<ExecutionError>();
 211  0 new PropertiesDepthFirstVisitor() {
 212  0 public void run(Node node) {
 213  0 if (NodeProperties.hasError(node)) { result.add(NodeProperties.getError(node)); }
 214  0 super.run(node);
 215    }
 216    }.run(ast);
 217  0 return CompositeException.make(IterUtil.map(result, CheckerException.FACTORY));
 218    }
 219   
 220    /**
 221    * A DepthFirstVisitor extension that recurs on Node-typed properties. Note that this may lead to
 222    * multiple invocations of the same node (because the properties are often used to create DAGs).
 223    */
 224    private static class PropertiesDepthFirstVisitor extends DepthFirstVisitor {
 225  0 public void run(Node node) {
 226  0 try { node.acceptVisitor(this); }
 227    catch (IllegalArgumentException e) { /* thrown by "empty" stub nodes -- ignore */ }
 228  0 if (NodeProperties.hasLeftExpression(node)) { recur(NodeProperties.getLeftExpression(node)); }
 229  0 if (NodeProperties.hasTranslation(node)) { recur(NodeProperties.getTranslation(node)); }
 230  0 if (NodeProperties.hasStatementTranslation(node)) { recur(NodeProperties.getStatementTranslation(node)); }
 231    }
 232    }
 233   
 234    private static final Map<String, Options> _options;
 235    static {
 236    abstract class SourceCheckerOptions extends Options {
 237    protected abstract TypeSystem makeTypeSystem();
 238  0 @Override protected Thunk<? extends TypeSystem> typeSystemFactory() {
 239  0 return LambdaUtil.valueLambda(makeTypeSystem());
 240    }
 241  0 @Override public boolean enforceAllAccess() { return true; }
 242  0 @Override public boolean prohibitUncheckedCasts() { return false; }
 243    }
 244   
 245  0 _options = new LinkedHashMap<String, Options>();
 246  0 _options.put("jls", new SourceCheckerOptions() {
 247  0 public TypeSystem makeTypeSystem() { return new JLSTypeSystem(this, true, true, true, true, true, false); }
 248    });
 249  0 _options.put("ext", new SourceCheckerOptions() {
 250  0 public TypeSystem makeTypeSystem() { return new ExtendedTypeSystem(this, true, true, true, false); }
 251    });
 252  0 _options.put("jls-inferred", new SourceCheckerOptions() {
 253  0 public TypeSystem makeTypeSystem() { return new JLSTypeSystem(this, true, true, true, true, false, false); }
 254    });
 255  0 _options.put("ext-inferred", new SourceCheckerOptions() {
 256  0 public TypeSystem makeTypeSystem() { return new ExtendedTypeSystem(this, true, true, false, false); }
 257    });
 258    }
 259   
 260  0 public static void main(String... args) {
 261  0 debug.logStart();
 262   
 263  0 ArgumentParser argParser = new ArgumentParser();
 264  0 argParser.supportOption("classpath", "");
 265  0 argParser.supportAlias("cp", "classpath");
 266  0 argParser.supportOption("opt", 1);
 267  0 argParser.supportOption("verbose");
 268  0 argParser.requireParams(1);
 269  0 final ArgumentParser.Result parsedArgs = argParser.parse(args);
 270  0 Iterable<File> cp = IOUtil.parsePath(parsedArgs.getUnaryOption("classpath"));
 271  0 Iterable<File> sources = IterUtil.map(parsedArgs.params(), IOUtil.FILE_FACTORY);
 272  0 boolean verbose = parsedArgs.hasOption("verbose");
 273   
 274  0 if (parsedArgs.hasOption("opt")) {
 275  0 Options opt = _options.get(parsedArgs.getUnaryOption("opt"));
 276  0 if (opt == null) { System.out.println("Unrecognized options name: " + parsedArgs.getUnaryOption("opt")); }
 277  0 else { processFiles(sources, cp, opt); }
 278    }
 279   
 280    else {
 281  0 String canonical = IterUtil.first(_options.keySet());
 282  0 Map<String, Iterable<CompilationUnit>> results = new LinkedHashMap<String, Iterable<CompilationUnit>>();
 283  0 for (String n : _options.keySet()) {
 284  0 System.out.println("============ Checking with type system " + n + " ============");
 285  0 results.put(n, processFiles(sources, cp, _options.get(n)));
 286    }
 287  0 for (Map.Entry<String, Iterable<CompilationUnit>> e : results.entrySet()) {
 288  0 if (e.getKey().equals(canonical)) continue;
 289  0 String compareTo;
 290  0 if (e.getKey().endsWith("-inferred")) {
 291  0 compareTo = e.getKey().substring(0, e.getKey().length() - "-inferred".length());
 292    }
 293  0 else { compareTo = canonical; }
 294   
 295  0 NodeDiffLog log = new NodeDiffLog(compareTo, _options.get(compareTo).typeSystem(),
 296    e.getKey(), _options.get(e.getKey()).typeSystem(), verbose);
 297  0 new NodeDiff(log).compare(results.get(compareTo), e.getValue());
 298   
 299  0 String firstInferred = compareTo + "-inferred";
 300  0 String secondInferred = e.getKey() + "-inferred";
 301  0 if (results.containsKey(firstInferred) && results.containsKey(secondInferred)) {
 302  0 NodeDiffLog log2 = new NodeDiffLog(firstInferred, _options.get(firstInferred).typeSystem(),
 303    secondInferred, _options.get(secondInferred).typeSystem(), verbose);
 304  0 new NodeDiff(log2).compare(results.get(firstInferred), results.get(secondInferred));
 305    }
 306    }
 307    }
 308   
 309  0 debug.logEnd();
 310    }
 311   
 312   
 313  0 private static Iterable<CompilationUnit> processFiles(Iterable<File> sources, Iterable<File> cp, Options opt) {
 314  0 SourceChecker checker = new SourceChecker(opt, false);
 315  0 try {
 316  0 checker.check(sources, cp);
 317  0 System.out.println("Completed checking successfully.");
 318    }
 319    catch (InterpreterException e) {
 320  0 debug.log(e);
 321  0 e.printUserMessage(new PrintWriter(System.out, true));
 322    }
 323  0 return checker.processed();
 324    }
 325   
 326    static class NodeDiffLog {
 327   
 328    private final String _leftName;
 329    private final TypeSystem _leftTS;
 330    private final String _rightName;
 331    private final TypeSystem _rightTS;
 332    private final boolean _verbose;
 333   
 334    private final Relation<SourceInfo, Location> _commonErrors;
 335    private final Relation<SourceInfo, Location> _leftErrors;
 336    private final Relation<SourceInfo, Location> _rightErrors;
 337    private final Relation<SourceInfo, Location> _polymorphicDeclarations;
 338    private final Relation<SourceInfo, Location> _inferredInvocations;
 339    private final Relation<SourceInfo, Location> _explicitInvocations;
 340    private final Relation<SourceInfo, Location> _simpleWildcards;
 341    private final Relation<SourceInfo, Location> _extendsWildcards;
 342    private final Relation<SourceInfo, Location> _superWildcards;
 343    private final Relation<SourceInfo, MismatchedType> _mismatchedTypes;
 344    private final Relation<SourceInfo, Cast> _leftCasts;
 345    private final Relation<SourceInfo, Cast> _rightCasts;
 346   
 347  0 public NodeDiffLog(String leftName, TypeSystem leftTS, String rightName, TypeSystem rightTS, boolean verbose) {
 348  0 _leftName = leftName;
 349  0 _leftTS = leftTS;
 350  0 _rightName = rightName;
 351  0 _rightTS = rightTS;
 352  0 _verbose = verbose;
 353    // we use relations because two AST nodes may have the same SourceInfo
 354  0 Thunk<Map<SourceInfo, PredicateSet<MismatchedType>>> mapFactory1 = CollectUtil.treeMapFactory();
 355  0 Thunk<Map<SourceInfo, PredicateSet<Location>>> mapFactory2 = CollectUtil.treeMapFactory();
 356  0 Thunk<Map<SourceInfo, PredicateSet<Cast>>> mapFactory3 = CollectUtil.treeMapFactory();
 357  0 Thunk<Set<MismatchedType>> setFactory1 = CollectUtil.hashSetFactory();
 358  0 Thunk<Set<Location>> setFactory2 = CollectUtil.hashSetFactory();
 359  0 Thunk<Set<Cast>> setFactory3 = CollectUtil.hashSetFactory();
 360  0 _commonErrors = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 361  0 _leftErrors = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 362  0 _rightErrors = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 363  0 _polymorphicDeclarations = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 364  0 _inferredInvocations = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 365  0 _explicitInvocations = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 366  0 _simpleWildcards = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 367  0 _extendsWildcards = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 368  0 _superWildcards = new IndexedRelation<SourceInfo, Location>(mapFactory2, setFactory2);
 369  0 _mismatchedTypes = new IndexedRelation<SourceInfo, MismatchedType>(mapFactory1, setFactory1);
 370  0 _leftCasts = new IndexedRelation<SourceInfo, Cast>(mapFactory3, setFactory3);
 371  0 _rightCasts = new IndexedRelation<SourceInfo, Cast>(mapFactory3, setFactory3);
 372    }
 373   
 374  0 public void start() {
 375  0 System.out.println("\n**********************************************************");
 376  0 System.out.println("Comparing " + _leftName + " with " + _rightName);
 377  0 System.out.println("**********************************************************");
 378    }
 379   
 380  0 public void end() {
 381  0 System.out.println();
 382  0 System.out.println("Common statements with errors: " + sizeString(_commonErrors));
 383  0 if (_verbose) { dump(_commonErrors.secondSet()); }
 384  0 System.out.println("Left statements with errors: " + sizeString(_leftErrors));
 385  0 if (_verbose) { dump(_leftErrors.secondSet()); }
 386  0 System.out.println("Right statements with errors: " + sizeString(_rightErrors));
 387  0 if (_verbose) { dump(_rightErrors.secondSet()); }
 388  0 System.out.println("Polymorphic declarations: " + sizeString(_polymorphicDeclarations));
 389  0 if (_verbose) { dump(_polymorphicDeclarations.secondSet()); }
 390  0 System.out.println("Inferred polymorphic invocations: " + sizeString(_inferredInvocations));
 391  0 if (_verbose) { dump(_inferredInvocations.secondSet()); }
 392  0 System.out.println("Explicit polymorphic invocations: " + sizeString(_explicitInvocations));
 393  0 if (_verbose) { dump(_explicitInvocations.secondSet()); }
 394  0 System.out.println("Simple wildcards: " + sizeString(_simpleWildcards));
 395  0 if (_verbose) { dump(_simpleWildcards.secondSet()); }
 396  0 System.out.println("Upper-bounded wildcards: " + sizeString(_extendsWildcards));
 397  0 if (_verbose) { dump(_extendsWildcards.secondSet()); }
 398  0 System.out.println("Lower-bounded wildcards: " + sizeString(_superWildcards));
 399  0 if (_verbose) { dump(_superWildcards.secondSet()); }
 400  0 System.out.println("Mismatched types: " + sizeString(_mismatchedTypes));
 401  0 if (_verbose) { dump(_mismatchedTypes.secondSet()); }
 402  0 System.out.println("Left extra casts: " + sizeString(_leftCasts));
 403  0 if (_verbose) { dump(_leftCasts.secondSet()); }
 404  0 System.out.println("Right extra casts: " + sizeString(_rightCasts));
 405  0 if (_verbose) { dump(_rightCasts.secondSet()); }
 406  0 System.out.println();
 407    }
 408   
 409  0 private String sizeString(Relation<?,?> r) {
 410  0 int size = r.size();
 411  0 int unique = r.firstSet().size();
 412  0 if (size == unique) { return "" + size; }
 413  0 else { return size + " (" + unique + " unique)"; }
 414    }
 415   
 416  0 private void dump(Set<?> s) {
 417  0 int counter = 0;
 418  0 for (Object obj: s) {
 419  0 counter++;
 420  0 if (counter > 100) { System.out.println("..."); break; }
 421  0 else { System.out.println(obj.toString()); }
 422    }
 423    }
 424   
 425  0 public void mismatchedCompilationUnits() {
 426  0 System.out.println("Can't compare results: mismatched CompilationUnit lists");
 427    }
 428   
 429  0 public void commonErrorStatement(String context, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 430  0 _commonErrors.add(left.getSourceInfo(), new Location(context, left, right));
 431    }
 432   
 433  0 public void leftErrorStatement(String context, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 434  0 _leftErrors.add(left.getSourceInfo(), new Location(context, left, right));
 435    }
 436   
 437  0 public void rightErrorStatement(String context, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 438  0 _rightErrors.add(left.getSourceInfo(), new Location(context, left, right));
 439    }
 440   
 441  0 public void polymorphicDeclaration(String context, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 442  0 _polymorphicDeclarations.add(left.getSourceInfo(), new Location(context, left, right));
 443    }
 444   
 445  0 public void polymorphicInvocation(String context, SourceInfo.Wrapper left, SourceInfo.Wrapper right,
 446    boolean inferred) {
 447  0 if (inferred) { _inferredInvocations.add(left.getSourceInfo(), new Location(context, left, right)); }
 448  0 else { _explicitInvocations.add(left.getSourceInfo(), new Location(context, left, right)); }
 449    }
 450   
 451  0 public void wildcard(String context, SourceInfo.Wrapper left, SourceInfo.Wrapper right,
 452    boolean upper, boolean lower) {
 453  0 if (lower) { _superWildcards.add(left.getSourceInfo(), new Location(context, left, right)); }
 454  0 else if (upper) { _extendsWildcards.add(left.getSourceInfo(), new Location(context, left, right)); }
 455  0 else { _simpleWildcards.add(left.getSourceInfo(), new Location(context, left, right)); }
 456    }
 457   
 458  0 public void extraLeftCast(String context, Class<?> target, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 459  0 _leftCasts.add(left.getSourceInfo(), new Cast(context, target, left, right));
 460    }
 461   
 462  0 public void extraRightCast(String context, Class<?> target, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 463  0 _rightCasts.add(left.getSourceInfo(), new Cast(context, target, left, right));
 464    }
 465   
 466  0 public void mismatchedType(String context, Type leftType, SourceInfo.Wrapper left,
 467    Type rightType, SourceInfo.Wrapper right) {
 468  0 _mismatchedTypes.add(left.getSourceInfo(), new MismatchedType(context, leftType, left, rightType, right));
 469    }
 470   
 471  0 public void mismatch(String description, String context, String leftData, SourceInfo.Wrapper left,
 472    String rightData, SourceInfo.Wrapper right) {
 473  0 System.out.println("*** " + description + " in " + context);
 474  0 System.out.println("Left (" + left.getSourceInfo() + "): " + leftData);
 475  0 System.out.println("Right (" + right.getSourceInfo() + "): " + rightData);
 476    }
 477   
 478    private static class Location {
 479    private final String _context;
 480    private final SourceInfo _left;
 481    private final SourceInfo _right;
 482  0 public Location(String context, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 483  0 _context = context; _left = left.getSourceInfo(); _right = right.getSourceInfo();
 484    }
 485  0 public String toString() {
 486  0 if (_left.equals(_right)) { return _left + ": " + _context; }
 487  0 else { return _left + "/" + _right + ": " + _context; }
 488    }
 489    }
 490   
 491    private class MismatchedType {
 492    private final Location _location;
 493    private final Type _leftType;
 494    private final Type _rightType;
 495  0 public MismatchedType(String context, Type leftType, SourceInfo.Wrapper left,
 496    Type rightType, SourceInfo.Wrapper right) {
 497  0 _location = new Location(context, left, right);
 498  0 _leftType = leftType;
 499  0 _rightType = rightType;
 500    }
 501  0 public String toString() {
 502  0 return _location + " has types " +
 503    _leftTS.typePrinter().print(_leftType) + "/" +
 504    _rightTS.typePrinter().print(_rightType);
 505    }
 506    }
 507   
 508    private class Cast {
 509    private final Location _location;
 510    private final Class<?> _target;
 511  0 public Cast(String context, Class<?> target, SourceInfo.Wrapper left, SourceInfo.Wrapper right) {
 512  0 _location = new Location(context, left, right);
 513  0 _target = target;
 514    }
 515  0 public String toString() {
 516  0 return _location + " cast to " + _target.getName();
 517    }
 518    }
 519   
 520    }
 521   
 522    static class NodeDiff {
 523   
 524    private final NodeDiffLog _log;
 525   
 526  0 public NodeDiff(NodeDiffLog log) { _log = log; }
 527   
 528  0 public void compare(Iterable<CompilationUnit> left, Iterable<CompilationUnit> right) {
 529  0 _log.start();
 530  0 if (IterUtil.sizeOf(left) != IterUtil.sizeOf(right)) {
 531  0 _log.mismatchedCompilationUnits();
 532    }
 533    else {
 534  0 for (Pair<CompilationUnit, CompilationUnit> p : IterUtil.zip(left, right)) {
 535  0 compare("Compilation unit", p.first(), p.second());
 536    }
 537    }
 538  0 _log.end();
 539    }
 540   
 541  0 private void compare(String context, Node left, Node right) {
 542  0 if (left.getClass().equals(right.getClass())) {
 543  0 if ((left instanceof Statement && !(left instanceof BlockStatement)) ||
 544    left instanceof VariableDeclaration ||
 545    left instanceof FieldDeclaration ||
 546    left instanceof Expression) {
 547  0 if (hasNestedError(left)) {
 548  0 if (hasNestedError(right)) { _log.commonErrorStatement(context, left, right); }
 549  0 else { _log.leftErrorStatement(context, left, right); }
 550  0 return;
 551    }
 552  0 else if (hasNestedError(right)) {
 553  0 _log.rightErrorStatement(context, left, right);
 554  0 return;
 555    }
 556    }
 557  0 if (NodeProperties.hasMethod(left)) {
 558  0 DJMethod m = NodeProperties.getMethod(left);
 559  0 if (left instanceof MethodCall && !IterUtil.isEmpty(m.typeParameters())) {
 560  0 _log.polymorphicInvocation(context, left, right, ((MethodCall) left).getTypeArgs().isNone());
 561    }
 562  0 else if (left instanceof MethodDeclaration && !IterUtil.isEmpty(m.typeParameters())) {
 563  0 _log.polymorphicDeclaration(context, left, right);
 564    }
 565    }
 566  0 if (NodeProperties.hasConstructor(left)) {
 567  0 DJConstructor k = NodeProperties.getConstructor(left);
 568  0 if (!IterUtil.isEmpty(k.typeParameters())) {
 569  0 Boolean inferred = null;
 570  0 if (left instanceof ConstructorCall) { inferred = true; } // doesn't support targs for now
 571  0 else if (left instanceof SimpleAllocation) { inferred = ((SimpleAllocation) left).getTypeArgs().isNone(); }
 572  0 else if (left instanceof InnerAllocation) { inferred = ((InnerAllocation) left).getTypeArgs().isNone(); }
 573  0 if (inferred != null) { _log.polymorphicInvocation(context, left, right, inferred); }
 574  0 if (left instanceof ConstructorDeclaration) { _log.polymorphicDeclaration(context, left, right); }
 575    }
 576    }
 577  0 if (left instanceof HookTypeName) {
 578  0 HookTypeName t = (HookTypeName) left;
 579  0 _log.wildcard(context, left, right, t.getUpperBound().isSome(), t.getLowerBound().isSome());
 580    }
 581  0 Class<?> c = left.getClass();
 582  0 while (!c.equals(Node.class)) {
 583  0 compareDeclaredFields(c, left, right);
 584  0 c = c.getSuperclass();
 585    }
 586  0 Field props;
 587  0 try { props = Node.class.getDeclaredField("properties"); }
 588  0 catch (NoSuchFieldException e) { throw new RuntimeException(e); }
 589  0 compareProperties(left.getClass(), (Map<?,?>) fieldValue(props, left), left,
 590    (Map<?,?>) fieldValue(props, right), right);
 591    }
 592    else {
 593  0 _log.mismatch("Different classes", context, left.getClass().getName(), left,
 594    right.getClass().getName(), right);
 595    }
 596    }
 597   
 598  0 private boolean hasNestedError(Node n) {
 599  0 final Box<Boolean> result = new SimpleBox<Boolean>(false);
 600  0 new PropertiesDepthFirstVisitor() {
 601  0 @Override public void run(Node n) {
 602  0 if (!result.value()) {
 603  0 if (NodeProperties.hasError(n)) { result.set(true); }
 604  0 else { super.run(n); }
 605    }
 606    }
 607    }.run(n);
 608  0 return result.value();
 609    }
 610   
 611  0 private void compareDeclaredFields(Class<?> c, Node left, Node right) {
 612  0 if (c.equals(ArrayAllocation.class)) {
 613    // special case -- must use accessors to compare private ArrayInitializers
 614  0 ArrayAllocation leftAlloc = (ArrayAllocation) left;
 615  0 ArrayAllocation rightAlloc = (ArrayAllocation) right;
 616  0 compareObjects("field ArrayAllocation.elementType",
 617    leftAlloc.getElementType(), left, rightAlloc.getElementType(), right);
 618  0 compareObjects("field ArrayAllocation.typeDescriptor.dimension",
 619    leftAlloc.getDimension(), left, rightAlloc.getDimension(), right);
 620  0 compareObjects("field ArrayAllocation.typeDescriptor.sizes",
 621    leftAlloc.getSizes(), left, rightAlloc.getSizes(), right);
 622  0 compareObjects("field ArrayAllocation.typeDescriptor.initialization",
 623    leftAlloc.getInitialization(), left, rightAlloc.getInitialization(), right);
 624    }
 625    else {
 626  0 for (Field f : c.getDeclaredFields()) {
 627  0 String name = "field " + c.getName() + "." + f.getName();
 628  0 compareObjects(name, fieldValue(f, left), left, fieldValue(f, right), right);
 629    }
 630    }
 631    }
 632   
 633  0 private void compareProperties(Class<?> c, Map<?,?> leftProps, SourceInfo.Wrapper left,
 634    Map<?,?> rightProps, SourceInfo.Wrapper right) {
 635  0 Set<Object> keys = new HashSet<Object>(leftProps.keySet());
 636  0 keys.retainAll(rightProps.keySet());
 637  0 Set<Object> leftKeys = new HashSet<Object>(leftProps.keySet());
 638  0 leftKeys.removeAll(keys);
 639  0 Set<Object> rightKeys = new HashSet<Object>(rightProps.keySet());
 640  0 rightKeys.removeAll(keys);
 641  0 for (Object k : keys) {
 642  0 compareObjects("property " + k + " of " + c.getName(), leftProps.get(k), left, rightProps.get(k), right);
 643    }
 644  0 if (leftKeys.contains("assertedType")) {
 645    // asserted on left, checked on right
 646  0 _log.extraRightCast(c.getName(), (Class<?>) ((Thunk<?>) leftProps.get("assertedType")).value(), left, right);
 647  0 leftKeys.remove("assertedType");
 648  0 rightKeys.remove("checkedType");
 649    }
 650  0 if (rightKeys.contains("assertedType")) {
 651    // asserted on right, checked on left
 652  0 _log.extraLeftCast(c.getName(), (Class<?>) ((Thunk<?>) rightProps.get("assertedType")).value(), left, right);
 653  0 leftKeys.remove("checkedType");
 654  0 rightKeys.remove("assertedType");
 655    }
 656  0 if (leftKeys.contains("checkedType")) {
 657    // incidental internal conversion
 658  0 leftKeys.remove("checkedType");
 659    }
 660  0 if (rightKeys.contains("checkedType")) {
 661    // incidental internal conversion
 662  0 rightKeys.remove("checkedType");
 663    }
 664  0 if (!leftKeys.isEmpty() || !rightKeys.isEmpty()) {
 665  0 _log.mismatch("Extra properties", c.getName(), leftKeys.toString(), left, rightKeys.toString(), right);
 666    }
 667    }
 668   
 669  0 private void compareObjects(String context, Object leftVal, SourceInfo.Wrapper left,
 670    Object rightVal, SourceInfo.Wrapper right) {
 671   
 672  0 if (leftVal == null || rightVal == null) {
 673  0 if (leftVal != null || rightVal != null) {
 674  0 _log.mismatch("Different value", context, ""+leftVal, left, ""+rightVal, right);
 675    }
 676    }
 677   
 678  0 else if (leftVal instanceof Object[] && rightVal instanceof Object[]) {
 679  0 compareObjects(context, Arrays.asList((Object[]) leftVal), left, Arrays.asList((Object[]) rightVal), right);
 680    }
 681   
 682  0 else if (leftVal instanceof Thunk<?> && rightVal instanceof Thunk<?> ||
 683    leftVal instanceof Lambda<?,?> && rightVal instanceof Lambda<?,?> ||
 684    leftVal instanceof Lambda2<?,?,?> && rightVal instanceof Lambda2<?,?,?>) {} // ignore
 685   
 686  0 else if (leftVal instanceof IdentifierToken && rightVal instanceof IdentifierToken) {
 687  0 String leftName = ((IdentifierToken) leftVal).image();
 688  0 String rightName = ((IdentifierToken) rightVal).image();
 689  0 if (!leftName.equals(rightName)) {
 690  0 _log.mismatch("Different value", context, leftName, left, rightName, right);
 691    }
 692    }
 693   
 694  0 else if (leftVal instanceof Node && rightVal instanceof Node) {
 695  0 compare(context, (Node) leftVal, (Node) rightVal);
 696    }
 697   
 698  0 else if (leftVal instanceof List<?> && rightVal instanceof List<?>) {
 699  0 List<?> leftList = (List<?>) leftVal;
 700  0 List<?> rightList = (List<?>) rightVal;
 701  0 if (leftList.size() == rightList.size()) {
 702  0 for (Pair<Object, Object> p : IterUtil.zip(leftList, rightList)) {
 703  0 compareObjects("element of " + context, p.first(), left, p.second(), right);
 704    }
 705    }
 706    else {
 707  0 _log.mismatch("Different lengths", context, ""+leftList.size(), left, ""+rightList.size(), right);
 708    }
 709    }
 710   
 711  0 else if (leftVal instanceof Option<?> && rightVal instanceof Option<?>) {
 712  0 Option<?> leftOpt = (Option<?>) leftVal;
 713  0 Option<?> rightOpt = (Option<?>) rightVal;
 714  0 if (leftOpt.isSome() && rightOpt.isSome()) {
 715  0 compareObjects(context, leftOpt.unwrap(), left, rightOpt.unwrap(), right);
 716    }
 717  0 else if (!leftOpt.isNone() || !rightOpt.isNone()) {
 718  0 _log.mismatch("Different value", context, leftVal.toString(), left, rightVal.toString(), right);
 719    }
 720    }
 721   
 722  0 else if (leftVal instanceof Pair<?,?> && rightVal instanceof Pair<?,?>) {
 723  0 Pair<?,?> leftPair = (Pair<?,?>) leftVal;
 724  0 Pair<?,?> rightPair = (Pair<?,?>) rightVal;
 725  0 compareObjects(context, leftPair.first(), left, rightPair.first(), right);
 726  0 compareObjects(context, leftPair.second(), left, rightPair.second(), right);
 727    }
 728   
 729  0 else if (leftVal instanceof DJClass && rightVal instanceof DJClass) {
 730  0 if (!sameClass((DJClass) leftVal, (DJClass) rightVal)) {
 731  0 _log.mismatch("Different value", context, leftVal.toString(), left, rightVal.toString(), right);
 732    }
 733    }
 734   
 735  0 else if (leftVal instanceof Variable && rightVal instanceof Variable) {
 736  0 if (!sameVariable((Variable) leftVal, (Variable) rightVal)) {
 737  0 _log.mismatch("Different value", context, leftVal.toString(), left, rightVal.toString(), right);
 738    }
 739    }
 740   
 741  0 else if (leftVal instanceof Function && rightVal instanceof Function) {
 742  0 if (!sameFunction((Function) leftVal, (Function) rightVal)) {
 743  0 _log.mismatch("Different value", context, leftVal.toString(), left, rightVal.toString(), right);
 744    }
 745    }
 746   
 747  0 else if (leftVal instanceof Type && rightVal instanceof Type) {
 748  0 if (!sameType((Type) leftVal, (Type) rightVal)) {
 749  0 _log.mismatchedType(context, (Type) leftVal, left, (Type) rightVal, right);
 750    }
 751    }
 752   
 753  0 else if (supportedAtom(leftVal) && supportedAtom(rightVal)) {
 754  0 if (!leftVal.equals(rightVal)) {
 755  0 _log.mismatch("Different value", context, leftVal.toString(), left, rightVal.toString(), right);
 756    }
 757    }
 758   
 759    else {
 760  0 _log.mismatch("Unsupported object type", context,
 761    leftVal.getClass().getName(), left, rightVal.getClass().getName(), right);
 762    }
 763    }
 764   
 765  0 private boolean supportedAtom(Object val) {
 766  0 return val instanceof String || val instanceof Number || val instanceof Boolean ||
 767    val instanceof Character || val instanceof Class<?> ||
 768    val instanceof EnumSet<?> || val instanceof Enum<?>;
 769    }
 770   
 771  0 private boolean sameClass(DJClass left, DJClass right) {
 772  0 return left.getClass().equals(right.getClass()) && left.fullName().equals(right.fullName());
 773    }
 774   
 775  0 private boolean sameVariable(Variable left, Variable right) {
 776  0 if (left.getClass().equals(right.getClass())) {
 777  0 boolean result = true;
 778  0 if (left instanceof DJField) {
 779  0 DJClass leftClass = ((DJField) left).declaringClass();
 780  0 DJClass rightClass = ((DJField) right).declaringClass();
 781  0 result &= (leftClass == rightClass) ||
 782    (leftClass != null && rightClass != null && sameClass(leftClass, rightClass));
 783    }
 784  0 result &= left.declaredName().equals(right.declaredName());
 785  0 result &= sameType(left.type(), right.type());
 786    // doesn't handle shadowed variables, but that shouldn't be an issue in practice
 787  0 return result;
 788    }
 789  0 else { return false; }
 790    }
 791   
 792  0 private boolean sameFunction(Function left, Function right) {
 793  0 if (left.getClass().equals(right.getClass())) {
 794  0 boolean result = true;
 795  0 if (left instanceof DJMethod) {
 796    // used declared signatures; differences in substitution types are handled by type comparisons elsewhere
 797  0 left = ((DJMethod) left).declaredSignature();
 798  0 right = ((DJMethod) right).declaredSignature();
 799  0 result &= left.getClass().equals(right.getClass());
 800  0 DJClass leftClass = ((DJMethod) left).declaringClass();
 801  0 DJClass rightClass = ((DJMethod) right).declaringClass();
 802  0 result &= (leftClass == rightClass) ||
 803    (leftClass != null && rightClass != null && sameClass(leftClass, rightClass));
 804    }
 805  0 result &= left.declaredName().equals(right.declaredName());
 806  0 result &= sameType(left.returnType(), right.returnType());
 807  0 result &= IterUtil.sizeOf(left.typeParameters()) == IterUtil.sizeOf(right.typeParameters());
 808  0 result &= IterUtil.sizeOf(left.parameters()) == IterUtil.sizeOf(right.parameters());
 809  0 if (result) {
 810  0 for (Pair<VariableType, VariableType> p : IterUtil.zip(left.typeParameters(), right.typeParameters())) {
 811  0 result &= sameType(p.first(), p.second());
 812    }
 813  0 for (Pair<LocalVariable, LocalVariable> p : IterUtil.zip(left.parameters(), right.parameters())) {
 814  0 result &= sameVariable(p.first(), p.second());
 815    }
 816    }
 817  0 return result;
 818    }
 819  0 else { return false; }
 820    }
 821   
 822  0 private boolean sameType(Type left, final Type right) {
 823  0 return new TypeStructComparer().contains(left, right);
 824    }
 825   
 826  0 private Object fieldValue(Field f, Object receiver) {
 827  0 try { f.setAccessible(true); }
 828    catch (SecurityException e) { /* ignore -- we can't relax accessibility */ }
 829  0 try { return f.get(receiver); }
 830    catch (IllegalAccessException e) {
 831  0 throw new RuntimeException(e);
 832    }
 833    }
 834   
 835    private class TypeStructComparer implements Predicate2<Type, Type> {
 836    private final RecursionStack2<Type, Type> _stack = new RecursionStack2<Type, Type>();
 837   
 838  0 public boolean contains(Type left, final Type right) {
 839  0 if (left.getClass().equals(right.getClass())) {
 840  0 return left.apply(new TypeAbstractVisitor<Boolean>() {
 841  0 @Override public Boolean defaultCase(Type left) { return true; }
 842  0 @Override public Boolean forArrayType(ArrayType left) {
 843  0 return contains(left.ofType(), ((ArrayType) right).ofType());
 844    }
 845  0 @Override public Boolean forClassType(ClassType left) {
 846  0 return sameClass(left.ofClass(), ((ClassType) right).ofClass());
 847    }
 848  0 @Override public Boolean forParameterizedClassType(ParameterizedClassType left) {
 849  0 return forClassType(left) && compareList(left.typeArguments(),
 850    ((ParameterizedClassType) right).typeArguments());
 851    }
 852  0 @Override public Boolean forBoundType(BoundType left) {
 853  0 return compareList(left.ofTypes(), ((BoundType) right).ofTypes());
 854    }
 855  0 @Override public Boolean forVariableType(VariableType left) {
 856  0 return handleBoundedSymbol(left.symbol(), ((VariableType) right).symbol());
 857    }
 858  0 @Override public Boolean forWildcard(Wildcard left) {
 859  0 return handleBoundedSymbol(left.symbol(), ((Wildcard) right).symbol());
 860    }
 861   
 862  0 private boolean handleBoundedSymbol(BoundedSymbol left, BoundedSymbol right) {
 863  0 boolean result = true;
 864  0 if (left.generated()) { result &= right.generated(); }
 865  0 else { result &= !right.generated() && left.name().equals(right.name()); }
 866  0 if (result) {
 867  0 Lambda2<Type, Type, Boolean> recur = LambdaUtil.asLambda(TypeStructComparer.this);
 868  0 result &= _stack.apply(recur, true, left.upperBound(), right.upperBound());
 869  0 result &= _stack.apply(recur, true, left.lowerBound(), right.lowerBound());
 870    }
 871  0 return result;
 872    }
 873   
 874    });
 875    }
 876  0 else { return false; }
 877    }
 878   
 879  0 private boolean compareList(Iterable<? extends Type> lefts, Iterable<? extends Type> rights) {
 880  0 return IterUtil.sizeOf(lefts) == IterUtil.sizeOf(rights) && IterUtil.and(lefts, rights, this);
 881    }
 882   
 883    }
 884   
 885    }
 886   
 887    }