1 /*
   2  * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javac.comp;
  27 
  28 import java.util.ArrayList;
  29 
  30 import com.sun.source.tree.LambdaExpressionTree;
  31 import com.sun.tools.javac.code.Source;
  32 import com.sun.tools.javac.code.Source.Feature;
  33 import com.sun.tools.javac.code.Type;
  34 import com.sun.tools.javac.code.Types;
  35 import com.sun.tools.javac.comp.ArgumentAttr.LocalCacheContext;
  36 import com.sun.tools.javac.resources.CompilerProperties.Warnings;
  37 import com.sun.tools.javac.tree.JCTree;
  38 import com.sun.tools.javac.tree.JCTree.JCBlock;
  39 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
  40 import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
  41 import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
  42 import com.sun.tools.javac.tree.JCTree.JCForLoop;
  43 import com.sun.tools.javac.tree.JCTree.JCIf;
  44 import com.sun.tools.javac.tree.JCTree.JCLambda;
  45 import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind;
  46 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
  47 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
  48 import com.sun.tools.javac.tree.JCTree.JCNewClass;
  49 import com.sun.tools.javac.tree.JCTree.JCStatement;
  50 import com.sun.tools.javac.tree.JCTree.JCSwitch;
  51 import com.sun.tools.javac.tree.JCTree.JCTypeApply;
  52 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
  53 import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
  54 import com.sun.tools.javac.tree.JCTree.Tag;
  55 import com.sun.tools.javac.tree.TreeCopier;
  56 import com.sun.tools.javac.tree.TreeInfo;
  57 import com.sun.tools.javac.tree.TreeMaker;
  58 import com.sun.tools.javac.tree.TreeScanner;
  59 import com.sun.tools.javac.util.Context;
  60 import com.sun.tools.javac.util.DefinedBy;
  61 import com.sun.tools.javac.util.DefinedBy.Api;
  62 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
  63 import com.sun.tools.javac.util.List;
  64 import com.sun.tools.javac.util.ListBuffer;
  65 import com.sun.tools.javac.util.Log;
  66 import com.sun.tools.javac.util.Options;
  67 import com.sun.tools.javac.util.Position;
  68 
  69 import java.util.EnumSet;
  70 import java.util.HashMap;
  71 import java.util.Map;
  72 import java.util.function.Predicate;
  73 
  74 import com.sun.source.tree.NewClassTree;
  75 import com.sun.tools.javac.code.Flags;
  76 import com.sun.tools.javac.code.Kinds.Kind;
  77 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  78 import com.sun.tools.javac.tree.JCTree.JCTry;
  79 import com.sun.tools.javac.tree.JCTree.JCUnary;
  80 import com.sun.tools.javac.util.Assert;
  81 import com.sun.tools.javac.util.DiagnosticSource;
  82 
  83 import static com.sun.tools.javac.code.Flags.GENERATEDCONSTR;
  84 import static com.sun.tools.javac.code.TypeTag.CLASS;
  85 import static com.sun.tools.javac.tree.JCTree.Tag.APPLY;
  86 import static com.sun.tools.javac.tree.JCTree.Tag.FOREACHLOOP;
  87 import static com.sun.tools.javac.tree.JCTree.Tag.LABELLED;
  88 import static com.sun.tools.javac.tree.JCTree.Tag.METHODDEF;
  89 import static com.sun.tools.javac.tree.JCTree.Tag.NEWCLASS;
  90 import static com.sun.tools.javac.tree.JCTree.Tag.NULLCHK;
  91 import static com.sun.tools.javac.tree.JCTree.Tag.TYPEAPPLY;
  92 import static com.sun.tools.javac.tree.JCTree.Tag.VARDEF;
  93 
  94 /**
  95  * Helper class for defining custom code analysis, such as finding instance creation expression
  96  * that can benefit from diamond syntax.
  97  */
  98 public class Analyzer {
  99     protected static final Context.Key<Analyzer> analyzerKey = new Context.Key<>();
 100 
 101     final Types types;
 102     final Log log;
 103     final Attr attr;
 104     final DeferredAttr deferredAttr;
 105     final ArgumentAttr argumentAttr;
 106     final TreeMaker make;
 107     final AnalyzerCopier copier;
 108     private final boolean allowDiamondWithAnonymousClassCreation;
 109 
 110     final EnumSet<AnalyzerMode> analyzerModes;
 111 
 112     public static Analyzer instance(Context context) {
 113         Analyzer instance = context.get(analyzerKey);
 114         if (instance == null)
 115             instance = new Analyzer(context);
 116         return instance;
 117     }
 118 
 119     protected Analyzer(Context context) {
 120         context.put(analyzerKey, this);
 121         types = Types.instance(context);
 122         log = Log.instance(context);
 123         attr = Attr.instance(context);
 124         deferredAttr = DeferredAttr.instance(context);
 125         argumentAttr = ArgumentAttr.instance(context);
 126         make = TreeMaker.instance(context);
 127         copier = new AnalyzerCopier();
 128         Options options = Options.instance(context);
 129         String findOpt = options.get("find");
 130         //parse modes
 131         Source source = Source.instance(context);
 132         allowDiamondWithAnonymousClassCreation = Feature.DIAMOND_WITH_ANONYMOUS_CLASS_CREATION.allowedInSource(source);
 133         analyzerModes = AnalyzerMode.getAnalyzerModes(findOpt, source);
 134     }
 135 
 136     /**
 137      * This enum defines supported analyzer modes, as well as defining the logic for decoding
 138      * the {@code -XDfind} option.
 139      */
 140     enum AnalyzerMode {
 141         DIAMOND("diamond", Feature.DIAMOND),
 142         LAMBDA("lambda", Feature.LAMBDA),
 143         METHOD("method", Feature.GRAPH_INFERENCE),
 144         LOCAL("local", Feature.LOCAL_VARIABLE_TYPE_INFERENCE);
 145 
 146         final String opt;
 147         final Feature feature;
 148 
 149         AnalyzerMode(String opt, Feature feature) {
 150             this.opt = opt;
 151             this.feature = feature;
 152         }
 153 
 154         /**
 155          * This method is used to parse the {@code find} option.
 156          * Possible modes are separated by colon; a mode can be excluded by
 157          * prepending '-' to its name. Finally, the special mode 'all' can be used to
 158          * add all modes to the resulting enum.
 159          */
 160         static EnumSet<AnalyzerMode> getAnalyzerModes(String opt, Source source) {
 161             if (opt == null) {
 162                 return EnumSet.noneOf(AnalyzerMode.class);
 163             }
 164             List<String> modes = List.from(opt.split(","));
 165             EnumSet<AnalyzerMode> res = EnumSet.noneOf(AnalyzerMode.class);
 166             if (modes.contains("all")) {
 167                 res = EnumSet.allOf(AnalyzerMode.class);
 168             }
 169             for (AnalyzerMode mode : values()) {
 170                 if (modes.contains(mode.opt)) {
 171                     res.add(mode);
 172                 } else if (modes.contains("-" + mode.opt) || !mode.feature.allowedInSource(source)) {
 173                     res.remove(mode);
 174                 }
 175             }
 176             return res;
 177         }
 178     }
 179 
 180     /**
 181      * A statement analyzer is a work-unit that matches certain AST nodes (of given type {@code S}),
 182      * rewrites them to different AST nodes (of type {@code T}) and then generates some meaningful
 183      * messages in case the analysis has been successful.
 184      */
 185     abstract class StatementAnalyzer<S extends JCTree, T extends JCTree> {
 186 
 187         AnalyzerMode mode;
 188         JCTree.Tag tag;
 189 
 190         StatementAnalyzer(AnalyzerMode mode, Tag tag) {
 191             this.mode = mode;
 192             this.tag = tag;
 193         }
 194 
 195         /**
 196          * Is this analyzer allowed to run?
 197          */
 198         boolean isEnabled() {
 199             return analyzerModes.contains(mode);
 200         }
 201 
 202         /**
 203          * Should this analyzer be rewriting the given tree?
 204          */
 205         abstract boolean match(S tree);
 206 
 207         /**
 208          * Rewrite a given AST node into a new one(s)
 209          */
 210         abstract List<T> rewrite(S oldTree);
 211 
 212         /**
 213          * Entry-point for comparing results and generating diagnostics.
 214          */
 215         abstract void process(S oldTree, T newTree, boolean hasErrors);
 216     }
 217 
 218     /**
 219      * This analyzer checks if generic instance creation expression can use diamond syntax.
 220      */
 221     class DiamondInitializer extends StatementAnalyzer<JCNewClass, JCNewClass> {
 222 
 223         DiamondInitializer() {
 224             super(AnalyzerMode.DIAMOND, NEWCLASS);
 225         }
 226 
 227         @Override
 228         boolean match(JCNewClass tree) {
 229             return tree.clazz.hasTag(TYPEAPPLY) &&
 230                     !TreeInfo.isDiamond(tree) &&
 231                     (tree.def == null || allowDiamondWithAnonymousClassCreation);
 232         }
 233 
 234         @Override
 235         List<JCNewClass> rewrite(JCNewClass oldTree) {
 236             if (oldTree.clazz.hasTag(TYPEAPPLY)) {
 237                 JCNewClass nc = copier.copy(oldTree);
 238                 ((JCTypeApply)nc.clazz).arguments = List.nil();
 239                 return List.of(nc);
 240             } else {
 241                 return List.of(oldTree);
 242             }
 243         }
 244 
 245         @Override
 246         void process(JCNewClass oldTree, JCNewClass newTree, boolean hasErrors) {
 247             if (!hasErrors) {
 248                 List<Type> inferredArgs, explicitArgs;
 249                 if (oldTree.def != null) {
 250                     inferredArgs = newTree.def.implementing.nonEmpty()
 251                                       ? newTree.def.implementing.get(0).type.getTypeArguments()
 252                                       : newTree.def.extending.type.getTypeArguments();
 253                     explicitArgs = oldTree.def.implementing.nonEmpty()
 254                                       ? oldTree.def.implementing.get(0).type.getTypeArguments()
 255                                       : oldTree.def.extending.type.getTypeArguments();
 256                 } else {
 257                     inferredArgs = newTree.type.getTypeArguments();
 258                     explicitArgs = oldTree.type.getTypeArguments();
 259                 }
 260                 for (Type t : inferredArgs) {
 261                     if (!types.isSameType(t, explicitArgs.head)) {
 262                         return;
 263                     }
 264                     explicitArgs = explicitArgs.tail;
 265                 }
 266                 //exact match
 267                 log.warning(oldTree.clazz, Warnings.DiamondRedundantArgs);
 268             }
 269         }
 270     }
 271 
 272     /**
 273      * This analyzer checks if anonymous instance creation expression can replaced by lambda.
 274      */
 275     class LambdaAnalyzer extends StatementAnalyzer<JCNewClass, JCLambda> {
 276 
 277         LambdaAnalyzer() {
 278             super(AnalyzerMode.LAMBDA, NEWCLASS);
 279         }
 280 
 281         @Override
 282         boolean match (JCNewClass tree){
 283             Type clazztype = tree.clazz.type;
 284             return tree.def != null &&
 285                     clazztype.hasTag(CLASS) &&
 286                     types.isFunctionalInterface(clazztype.tsym) &&
 287                     decls(tree.def).length() == 1;
 288         }
 289         //where
 290             private List<JCTree> decls(JCClassDecl decl) {
 291                 ListBuffer<JCTree> decls = new ListBuffer<>();
 292                 for (JCTree t : decl.defs) {
 293                     if (t.hasTag(METHODDEF)) {
 294                         JCMethodDecl md = (JCMethodDecl)t;
 295                         if ((md.getModifiers().flags & GENERATEDCONSTR) == 0) {
 296                             decls.add(md);
 297                         }
 298                     } else {
 299                         decls.add(t);
 300                     }
 301                 }
 302                 return decls.toList();
 303             }
 304 
 305         @Override
 306         List<JCLambda> rewrite(JCNewClass oldTree){
 307             JCMethodDecl md = (JCMethodDecl)decls(oldTree.def).head;
 308             List<JCVariableDecl> params = md.params;
 309             JCBlock body = md.body;
 310             JCLambda newTree = make.at(oldTree).Lambda(params, body);
 311             return List.of(newTree);
 312         }
 313 
 314         @Override
 315         void process (JCNewClass oldTree, JCLambda newTree, boolean hasErrors){
 316             if (!hasErrors) {
 317                 log.warning(oldTree.def, Warnings.PotentialLambdaFound);
 318             }
 319         }
 320     }
 321 
 322     /**
 323      * This analyzer checks if generic method call has redundant type arguments.
 324      */
 325     class RedundantTypeArgAnalyzer extends StatementAnalyzer<JCMethodInvocation, JCMethodInvocation> {
 326 
 327         RedundantTypeArgAnalyzer() {
 328             super(AnalyzerMode.METHOD, APPLY);
 329         }
 330 
 331         @Override
 332         boolean match (JCMethodInvocation tree){
 333             return tree.typeargs != null &&
 334                     tree.typeargs.nonEmpty();
 335         }
 336         @Override
 337         List<JCMethodInvocation> rewrite(JCMethodInvocation oldTree){
 338             JCMethodInvocation app = copier.copy(oldTree);
 339             app.typeargs = List.nil();
 340             return List.of(app);
 341         }
 342 
 343         @Override
 344         void process (JCMethodInvocation oldTree, JCMethodInvocation newTree, boolean hasErrors){
 345             if (!hasErrors) {
 346                 //exact match
 347                 log.warning(oldTree, Warnings.MethodRedundantTypeargs);
 348             }
 349         }
 350     }
 351 
 352     /**
 353      * Base class for local variable inference analyzers.
 354      */
 355     abstract class RedundantLocalVarTypeAnalyzerBase<X extends JCStatement> extends StatementAnalyzer<X, X> {
 356 
 357         RedundantLocalVarTypeAnalyzerBase(JCTree.Tag tag) {
 358             super(AnalyzerMode.LOCAL, tag);
 359         }
 360 
 361         /**
 362          * Map a variable tree into a new declaration using implicit type.
 363          */
 364         JCVariableDecl rewriteVarType(JCVariableDecl oldTree) {
 365             JCVariableDecl newTree = copier.copy(oldTree);
 366             newTree.vartype = null;
 367             return newTree;
 368         }
 369 
 370         /**
 371          * Analyze results of local variable inference.
 372          */
 373         void processVar(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors) {
 374             if (!hasErrors) {
 375                 if (types.isSameType(oldTree.type, newTree.type)) {
 376                     log.warning(oldTree, Warnings.LocalRedundantType);
 377                 }
 378             }
 379         }
 380     }
 381 
 382     /**
 383      * This analyzer checks if a local variable declaration has redundant type.
 384      */
 385     class RedundantLocalVarTypeAnalyzer extends RedundantLocalVarTypeAnalyzerBase<JCVariableDecl> {
 386 
 387         RedundantLocalVarTypeAnalyzer() {
 388             super(VARDEF);
 389         }
 390 
 391         boolean match(JCVariableDecl tree){
 392             return tree.sym.owner.kind == Kind.MTH &&
 393                     tree.init != null && !tree.isImplicitlyTyped() &&
 394                     attr.canInferLocalVarType(tree) == null;
 395         }
 396         @Override
 397         List<JCVariableDecl> rewrite(JCVariableDecl oldTree) {
 398             return List.of(rewriteVarType(oldTree));
 399         }
 400         @Override
 401         void process(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors){
 402             processVar(oldTree, newTree, hasErrors);
 403         }
 404     }
 405 
 406     /**
 407      * This analyzer checks if a for each variable declaration has redundant type.
 408      */
 409     class RedundantLocalVarTypeAnalyzerForEach extends RedundantLocalVarTypeAnalyzerBase<JCEnhancedForLoop> {
 410 
 411         RedundantLocalVarTypeAnalyzerForEach() {
 412             super(FOREACHLOOP);
 413         }
 414 
 415         @Override
 416         boolean match(JCEnhancedForLoop tree){
 417             return !tree.var.isImplicitlyTyped();
 418         }
 419         @Override
 420         List<JCEnhancedForLoop> rewrite(JCEnhancedForLoop oldTree) {
 421             JCEnhancedForLoop newTree = copier.copy(oldTree);
 422             newTree.var = rewriteVarType(oldTree.var);
 423             newTree.body = make.at(oldTree.body).Block(0, List.nil());
 424             return List.of(newTree);
 425         }
 426         @Override
 427         void process(JCEnhancedForLoop oldTree, JCEnhancedForLoop newTree, boolean hasErrors){
 428             processVar(oldTree.var, newTree.var, hasErrors);
 429         }
 430     }
 431 
 432     @SuppressWarnings({"unchecked", "rawtypes"})
 433     StatementAnalyzer<JCTree, JCTree>[] analyzers = new StatementAnalyzer[] {
 434             new DiamondInitializer(),
 435             new LambdaAnalyzer(),
 436             new RedundantTypeArgAnalyzer(),
 437             new RedundantLocalVarTypeAnalyzer(),
 438             new RedundantLocalVarTypeAnalyzerForEach()
 439     };
 440 
 441     /**
 442      * Create a copy of Env if needed.
 443      */
 444     Env<AttrContext> copyEnvIfNeeded(JCTree tree, Env<AttrContext> env) {
 445         if (!analyzerModes.isEmpty() &&
 446                 !env.info.isSpeculative &&
 447                 TreeInfo.isStatement(tree) &&
 448                 !tree.hasTag(LABELLED)) {
 449             Env<AttrContext> analyzeEnv =
 450                     env.dup(env.tree, env.info.dup(env.info.scope.dupUnshared(env.info.scope.owner)));
 451             analyzeEnv.info.returnResult = analyzeEnv.info.returnResult != null ?
 452                     attr.new ResultInfo(analyzeEnv.info.returnResult.pkind,
 453                                         analyzeEnv.info.returnResult.pt) : null;
 454             return analyzeEnv;
 455         } else {
 456             return null;
 457         }
 458     }
 459 
 460     /**
 461      * Analyze an AST node if needed.
 462      */
 463     void analyzeIfNeeded(JCTree tree, Env<AttrContext> env) {
 464         if (env != null) {
 465             JCStatement stmt = (JCStatement)tree;
 466             analyze(stmt, env);
 467         }
 468     }
 469 
 470     /**
 471      * Analyze an AST node; this involves collecting a list of all the nodes that needs rewriting,
 472      * and speculatively type-check the rewritten code to compare results against previously attributed code.
 473      */
 474     void analyze(JCStatement statement, Env<AttrContext> env) {
 475         StatementScanner statementScanner = new StatementScanner(statement, env);
 476         statementScanner.scan();
 477 
 478         if (!statementScanner.rewritings.isEmpty()) {
 479             for (RewritingContext rewriting : statementScanner.rewritings) {
 480                 deferredAnalysisHelper.queue(rewriting);
 481             }
 482         }
 483     }
 484 
 485     /**
 486      * Helper interface to handle deferral of analysis tasks.
 487      */
 488     interface DeferredAnalysisHelper {
 489         /**
 490          * Add a new analysis task to the queue.
 491          */
 492         void queue(RewritingContext rewriting);
 493         /**
 494          * Flush queue with given attribution env.
 495          */
 496         void flush(Env<AttrContext> flushEnv);
 497     }
 498 
 499     /**
 500      * Dummy deferral handler.
 501      */
 502     DeferredAnalysisHelper flushDeferredHelper = new DeferredAnalysisHelper() {
 503         @Override
 504         public void queue(RewritingContext rewriting) {
 505             //do nothing
 506         }
 507 
 508         @Override
 509         public void flush(Env<AttrContext> flushEnv) {
 510             //do nothing
 511         }
 512     };
 513 
 514     /**
 515      * Simple deferral handler. All tasks belonging to the same outermost class are added to
 516      * the same queue. The queue is flushed after flow analysis (only if no error occurred).
 517      */
 518     DeferredAnalysisHelper queueDeferredHelper = new DeferredAnalysisHelper() {
 519 
 520         Map<ClassSymbol, ArrayList<RewritingContext>> Q = new HashMap<>();
 521 
 522         @Override
 523         public void queue(RewritingContext rewriting) {
 524             ArrayList<RewritingContext> s = Q.computeIfAbsent(rewriting.env.enclClass.sym.outermostClass(), k -> new ArrayList<>());
 525             s.add(rewriting);
 526         }
 527 
 528         @Override
 529         public void flush(Env<AttrContext> flushEnv) {
 530             if (!Q.isEmpty()) {
 531                 DeferredAnalysisHelper prevHelper = deferredAnalysisHelper;
 532                 try {
 533                     deferredAnalysisHelper = flushDeferredHelper;
 534                     ArrayList<RewritingContext> rewritings = Q.get(flushEnv.enclClass.sym.outermostClass());
 535                     while (rewritings != null && !rewritings.isEmpty()) {
 536                         doAnalysis(rewritings.remove(0));
 537                     }
 538                 } finally {
 539                     deferredAnalysisHelper = prevHelper;
 540                 }
 541             }
 542         }
 543     };
 544 
 545     DeferredAnalysisHelper deferredAnalysisHelper = queueDeferredHelper;
 546 
 547     void doAnalysis(RewritingContext rewriting) {
 548         DiagnosticSource prevSource = log.currentSource();
 549         LocalCacheContext localCacheContext = argumentAttr.withLocalCacheContext();
 550         try {
 551             log.useSource(rewriting.env.toplevel.getSourceFile());
 552 
 553             JCStatement treeToAnalyze = (JCStatement)rewriting.originalTree;
 554             if (rewriting.env.info.scope.owner.kind == Kind.TYP) {
 555                 //add a block to hoist potential dangling variable declarations
 556                 treeToAnalyze = make.at(Position.NOPOS)
 557                                     .Block(Flags.SYNTHETIC, List.of((JCStatement)rewriting.originalTree));
 558             }
 559 
 560             //TODO: to further refine the analysis, try all rewriting combinations
 561             deferredAttr.attribSpeculative(treeToAnalyze, rewriting.env, attr.statInfo, new TreeRewriter(rewriting),
 562                     t -> rewriting.diagHandler(), argumentAttr.withLocalCacheContext());
 563             rewriting.analyzer.process(rewriting.oldTree, rewriting.replacement, rewriting.erroneous);
 564         } catch (Throwable ex) {
 565             Assert.error("Analyzer error when processing: " + rewriting.originalTree);
 566         } finally {
 567             log.useSource(prevSource.getFile());
 568             localCacheContext.leave();
 569         }
 570     }
 571 
 572     public void flush(Env<AttrContext> flushEnv) {
 573         deferredAnalysisHelper.flush(flushEnv);
 574     }
 575 
 576     /**
 577      * Subclass of {@link com.sun.tools.javac.tree.TreeScanner} which visit AST-nodes w/o crossing
 578      * statement boundaries.
 579      */
 580     class StatementScanner extends TreeScanner {
 581         /** Tree rewritings (generated by analyzers). */
 582         ListBuffer<RewritingContext> rewritings = new ListBuffer<>();
 583         JCTree originalTree;
 584         Env<AttrContext> env;
 585 
 586         StatementScanner(JCTree originalTree, Env<AttrContext> env) {
 587             this.originalTree = originalTree;
 588             this.env = attr.copyEnv(env);
 589         }
 590 
 591         public void scan() {
 592             scan(originalTree);
 593         }
 594 
 595         @Override
 596         @SuppressWarnings("unchecked")
 597         public void scan(JCTree tree) {
 598             if (tree != null) {
 599                 for (StatementAnalyzer<JCTree, JCTree> analyzer : analyzers) {
 600                     if (analyzer.isEnabled() &&
 601                             tree.hasTag(analyzer.tag) &&
 602                             analyzer.match(tree)) {
 603                         for (JCTree t : analyzer.rewrite(tree)) {
 604                             rewritings.add(new RewritingContext(originalTree, tree, t, analyzer, env));
 605                         }
 606                         break; //TODO: cover cases where multiple matching analyzers are found
 607                     }
 608                 }
 609             }
 610             super.scan(tree);
 611         }
 612 
 613         @Override
 614         public void visitClassDef(JCClassDecl tree) {
 615             //do nothing (prevents seeing same stuff twice)
 616         }
 617 
 618         @Override
 619         public void visitMethodDef(JCMethodDecl tree) {
 620             //do nothing (prevents seeing same stuff twice)
 621         }
 622 
 623         @Override
 624         public void visitBlock(JCBlock tree) {
 625             //do nothing (prevents seeing same stuff twice)
 626         }
 627 
 628         @Override
 629         public void visitSwitch(JCSwitch tree) {
 630             scan(tree.getExpression());
 631         }
 632 
 633         @Override
 634         public void visitForLoop(JCForLoop tree) {
 635             //skip body and var decl (to prevents same statements to be analyzed twice)
 636             scan(tree.getCondition());
 637             scan(tree.getUpdate());
 638         }
 639 
 640         @Override
 641         public void visitTry(JCTry tree) {
 642             //skip resources (to prevents same statements to be analyzed twice)
 643             scan(tree.getBlock());
 644             scan(tree.getCatches());
 645             scan(tree.getFinallyBlock());
 646         }
 647 
 648         @Override
 649         public void visitForeachLoop(JCEnhancedForLoop tree) {
 650             //skip body (to prevents same statements to be analyzed twice)
 651             scan(tree.getExpression());
 652         }
 653 
 654         @Override
 655         public void visitWhileLoop(JCWhileLoop tree) {
 656             //skip body (to prevents same statements to be analyzed twice)
 657             scan(tree.getCondition());
 658         }
 659 
 660         @Override
 661         public void visitDoLoop(JCDoWhileLoop tree) {
 662             //skip body (to prevents same statements to be analyzed twice)
 663             scan(tree.getCondition());
 664         }
 665 
 666         @Override
 667         public void visitIf(JCIf tree) {
 668             //skip body (to prevents same statements to be analyzed twice)
 669             scan(tree.getCondition());
 670         }
 671     }
 672 
 673     class RewritingContext {
 674         // the whole tree being analyzed
 675         JCTree originalTree;
 676         // a subtree, old tree, that will be rewritten
 677         JCTree oldTree;
 678         // the replacement for the old tree
 679         JCTree replacement;
 680         // did the compiler find any error
 681         boolean erroneous;
 682         // the env
 683         Env<AttrContext> env;
 684         // the corresponding analyzer
 685         StatementAnalyzer<JCTree, JCTree> analyzer;
 686 
 687         RewritingContext(
 688                 JCTree originalTree,
 689                 JCTree oldTree,
 690                 JCTree replacement,
 691                 StatementAnalyzer<JCTree, JCTree> analyzer,
 692                 Env<AttrContext> env) {
 693             this.originalTree = originalTree;
 694             this.oldTree = oldTree;
 695             this.replacement = replacement;
 696             this.analyzer = analyzer;
 697             this.env = attr.copyEnv(env);
 698             /*  this is a temporary workaround that should be removed once we have a truly independent
 699              *  clone operation
 700              */
 701             if (originalTree.hasTag(VARDEF)) {
 702                 // avoid redefinition clashes
 703                 this.env.info.scope.remove(((JCVariableDecl)originalTree).sym);
 704             }
 705         }
 706 
 707         /**
 708          * Simple deferred diagnostic handler which filters out all messages and keep track of errors.
 709          */
 710         Log.DeferredDiagnosticHandler diagHandler() {
 711             return new Log.DeferredDiagnosticHandler(log, d -> {
 712                 if (d.getType() == DiagnosticType.ERROR) {
 713                     erroneous = true;
 714                 }
 715                 return true;
 716             });
 717         }
 718     }
 719 
 720     /**
 721      * Subclass of TreeCopier that maps nodes matched by analyzers onto new AST nodes.
 722      */
 723     class AnalyzerCopier extends TreeCopier<Void> {
 724 
 725         public AnalyzerCopier() {
 726             super(make);
 727         }
 728 
 729         @Override @DefinedBy(Api.COMPILER_TREE)
 730         public JCTree visitLambdaExpression(LambdaExpressionTree node, Void _unused) {
 731             JCLambda oldLambda = (JCLambda)node;
 732             JCLambda newLambda = (JCLambda)super.visitLambdaExpression(node, _unused);
 733             if (oldLambda.paramKind == ParameterKind.IMPLICIT) {
 734                 //reset implicit lambda parameters (whose type might have been set during attr)
 735                 newLambda.paramKind = ParameterKind.IMPLICIT;
 736                 newLambda.params.forEach(p -> p.vartype = null);
 737             }
 738             return newLambda;
 739         }
 740 
 741         @Override @DefinedBy(Api.COMPILER_TREE)
 742         public JCTree visitNewClass(NewClassTree node, Void aVoid) {
 743             JCNewClass oldNewClazz = (JCNewClass)node;
 744             JCNewClass newNewClazz = (JCNewClass)super.visitNewClass(node, aVoid);
 745             if (!oldNewClazz.args.isEmpty() && oldNewClazz.args.head.hasTag(NULLCHK)) {
 746                 //workaround to Attr generating trees
 747                 newNewClazz.encl = ((JCUnary)newNewClazz.args.head).arg;
 748                 newNewClazz.args = newNewClazz.args.tail;
 749             }
 750             return newNewClazz;
 751         }
 752     }
 753 
 754    class TreeRewriter extends AnalyzerCopier {
 755 
 756         RewritingContext rewriting;
 757 
 758         TreeRewriter(RewritingContext rewriting) {
 759             this.rewriting = rewriting;
 760         }
 761 
 762         @Override
 763         @SuppressWarnings("unchecked")
 764         public <Z extends JCTree> Z copy(Z tree, Void _unused) {
 765             Z newTree = super.copy(tree, null);
 766             if (tree != null && tree == rewriting.oldTree) {
 767                 Assert.checkNonNull(rewriting.replacement);
 768                 newTree = (Z)rewriting.replacement;
 769             }
 770             return newTree;
 771         }
 772     }
 773 }