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)copier.copy(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         boolean isImplicitlyTyped(JCVariableDecl decl) {
 362             return decl.vartype.pos == Position.NOPOS;
 363         }
 364 
 365         /**
 366          * Map a variable tree into a new declaration using implicit type.
 367          */
 368         JCVariableDecl rewriteVarType(JCVariableDecl oldTree) {
 369             JCVariableDecl newTree = copier.copy(oldTree);
 370             newTree.vartype = null;
 371             return newTree;
 372         }
 373 
 374         /**
 375          * Analyze results of local variable inference.
 376          */
 377         void processVar(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors) {
 378             if (!hasErrors) {
 379                 if (types.isSameType(oldTree.type, newTree.type)) {
 380                     log.warning(oldTree, Warnings.LocalRedundantType);
 381                 }
 382             }
 383         }
 384     }
 385 
 386     /**
 387      * This analyzer checks if a local variable declaration has redundant type.
 388      */
 389     class RedundantLocalVarTypeAnalyzer extends RedundantLocalVarTypeAnalyzerBase<JCVariableDecl> {
 390 
 391         RedundantLocalVarTypeAnalyzer() {
 392             super(VARDEF);
 393         }
 394 
 395         boolean match(JCVariableDecl tree){
 396             return tree.sym.owner.kind == Kind.MTH &&
 397                     tree.init != null && !isImplicitlyTyped(tree) &&
 398                     attr.canInferLocalVarType(tree) == null;
 399         }
 400         @Override
 401         List<JCVariableDecl> rewrite(JCVariableDecl oldTree) {
 402             return List.of(rewriteVarType(oldTree));
 403         }
 404         @Override
 405         void process(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors){
 406             processVar(oldTree, newTree, hasErrors);
 407         }
 408     }
 409 
 410     /**
 411      * This analyzer checks if a for each variable declaration has redundant type.
 412      */
 413     class RedundantLocalVarTypeAnalyzerForEach extends RedundantLocalVarTypeAnalyzerBase<JCEnhancedForLoop> {
 414 
 415         RedundantLocalVarTypeAnalyzerForEach() {
 416             super(FOREACHLOOP);
 417         }
 418 
 419         @Override
 420         boolean match(JCEnhancedForLoop tree){
 421             return !isImplicitlyTyped(tree.var);
 422         }
 423         @Override
 424         List<JCEnhancedForLoop> rewrite(JCEnhancedForLoop oldTree) {
 425             JCEnhancedForLoop newTree = copier.copy(oldTree);
 426             newTree.var = rewriteVarType(oldTree.var);
 427             newTree.body = make.at(oldTree.body).Block(0, List.nil());
 428             return List.of(newTree);
 429         }
 430         @Override
 431         void process(JCEnhancedForLoop oldTree, JCEnhancedForLoop newTree, boolean hasErrors){
 432             processVar(oldTree.var, newTree.var, hasErrors);
 433         }
 434     }
 435 
 436     @SuppressWarnings({"unchecked", "rawtypes"})
 437     StatementAnalyzer<JCTree, JCTree>[] analyzers = new StatementAnalyzer[] {
 438             new DiamondInitializer(),
 439             new LambdaAnalyzer(),
 440             new RedundantTypeArgAnalyzer(),
 441             new RedundantLocalVarTypeAnalyzer(),
 442             new RedundantLocalVarTypeAnalyzerForEach()
 443     };
 444 
 445     /**
 446      * Create a copy of Env if needed.
 447      */
 448     Env<AttrContext> copyEnvIfNeeded(JCTree tree, Env<AttrContext> env) {
 449         if (!analyzerModes.isEmpty() &&
 450                 !env.info.isSpeculative &&
 451                 TreeInfo.isStatement(tree) &&
 452                 !tree.hasTag(LABELLED)) {
 453             Env<AttrContext> analyzeEnv =
 454                     env.dup(env.tree, env.info.dup(env.info.scope.dupUnshared(env.info.scope.owner)));
 455             analyzeEnv.info.returnResult = analyzeEnv.info.returnResult != null ?
 456                     attr.new ResultInfo(analyzeEnv.info.returnResult.pkind,
 457                                         analyzeEnv.info.returnResult.pt) : null;
 458             return analyzeEnv;
 459         } else {
 460             return null;
 461         }
 462     }
 463 
 464     /**
 465      * Analyze an AST node if needed.
 466      */
 467     void analyzeIfNeeded(JCTree tree, Env<AttrContext> env) {
 468         if (env != null) {
 469             JCStatement stmt = (JCStatement)tree;
 470             analyze(stmt, env);
 471         }
 472     }
 473 
 474     /**
 475      * Analyze an AST node; this involves collecting a list of all the nodes that needs rewriting,
 476      * and speculatively type-check the rewritten code to compare results against previously attributed code.
 477      */
 478     void analyze(JCStatement statement, Env<AttrContext> env) {
 479         StatementScanner statementScanner = new StatementScanner(statement, env);
 480         statementScanner.scan();
 481 
 482         if (!statementScanner.rewritings.isEmpty()) {
 483             for (RewritingContext rewriting : statementScanner.rewritings) {
 484                 deferredAnalysisHelper.queue(rewriting);
 485             }
 486         }
 487     }
 488 
 489     /**
 490      * Helper interface to handle deferral of analysis tasks.
 491      */
 492     interface DeferredAnalysisHelper {
 493         /**
 494          * Add a new analysis task to the queue.
 495          */
 496         void queue(RewritingContext rewriting);
 497         /**
 498          * Flush queue with given attribution env.
 499          */
 500         void flush(Env<AttrContext> flushEnv);
 501     }
 502 
 503     /**
 504      * Dummy deferral handler.
 505      */
 506     DeferredAnalysisHelper flushDeferredHelper = new DeferredAnalysisHelper() {
 507         @Override
 508         public void queue(RewritingContext rewriting) {
 509             //do nothing
 510         }
 511 
 512         @Override
 513         public void flush(Env<AttrContext> flushEnv) {
 514             //do nothing
 515         }
 516     };
 517 
 518     /**
 519      * Simple deferral handler. All tasks belonging to the same outermost class are added to
 520      * the same queue. The queue is flushed after flow analysis (only if no error occurred).
 521      */
 522     DeferredAnalysisHelper queueDeferredHelper = new DeferredAnalysisHelper() {
 523 
 524         Map<ClassSymbol, ArrayList<RewritingContext>> Q = new HashMap<>();
 525 
 526         @Override
 527         public void queue(RewritingContext rewriting) {
 528             ArrayList<RewritingContext> s = Q.computeIfAbsent(rewriting.env.enclClass.sym.outermostClass(), k -> new ArrayList<>());
 529             s.add(rewriting);
 530         }
 531 
 532         @Override
 533         public void flush(Env<AttrContext> flushEnv) {
 534             if (!Q.isEmpty()) {
 535                 DeferredAnalysisHelper prevHelper = deferredAnalysisHelper;
 536                 try {
 537                     deferredAnalysisHelper = flushDeferredHelper;
 538                     ArrayList<RewritingContext> rewritings = Q.get(flushEnv.enclClass.sym.outermostClass());
 539                     while (rewritings != null && !rewritings.isEmpty()) {
 540                         doAnalysis(rewritings.remove(0));
 541                     }
 542                 } finally {
 543                     deferredAnalysisHelper = prevHelper;
 544                 }
 545             }
 546         }
 547     };
 548 
 549     DeferredAnalysisHelper deferredAnalysisHelper = queueDeferredHelper;
 550 
 551     void doAnalysis(RewritingContext rewriting) {
 552         DiagnosticSource prevSource = log.currentSource();
 553         LocalCacheContext localCacheContext = argumentAttr.withLocalCacheContext();
 554         try {
 555             log.useSource(rewriting.env.toplevel.getSourceFile());
 556 
 557             JCStatement treeToAnalyze = (JCStatement)rewriting.originalTree;
 558             if (rewriting.env.info.scope.owner.kind == Kind.TYP) {
 559                 //add a block to hoist potential dangling variable declarations
 560                 treeToAnalyze = make.at(Position.NOPOS)
 561                                     .Block(Flags.SYNTHETIC, List.of((JCStatement)rewriting.originalTree));
 562             }
 563 
 564             //TODO: to further refine the analysis, try all rewriting combinations
 565             deferredAttr.attribSpeculative(treeToAnalyze, rewriting.env, attr.statInfo, new TreeRewriter(rewriting),
 566                     t -> rewriting.diagHandler(), argumentAttr.withLocalCacheContext());
 567             rewriting.analyzer.process(rewriting.oldTree, rewriting.replacement, rewriting.erroneous);
 568         } catch (Throwable ex) {
 569             Assert.error("Analyzer error when processing: " + rewriting.originalTree);
 570         } finally {
 571             log.useSource(prevSource.getFile());
 572             localCacheContext.leave();
 573         }
 574     }
 575 
 576     public void flush(Env<AttrContext> flushEnv) {
 577         deferredAnalysisHelper.flush(flushEnv);
 578     }
 579 
 580     /**
 581      * Subclass of {@link com.sun.tools.javac.tree.TreeScanner} which visit AST-nodes w/o crossing
 582      * statement boundaries.
 583      */
 584     class StatementScanner extends TreeScanner {
 585         /** Tree rewritings (generated by analyzers). */
 586         ListBuffer<RewritingContext> rewritings = new ListBuffer<>();
 587         JCTree originalTree;
 588         Env<AttrContext> env;
 589 
 590         StatementScanner(JCTree originalTree, Env<AttrContext> env) {
 591             this.originalTree = originalTree;
 592             this.env = attr.copyEnv(env);
 593         }
 594 
 595         public void scan() {
 596             scan(originalTree);
 597         }
 598 
 599         @Override
 600         @SuppressWarnings("unchecked")
 601         public void scan(JCTree tree) {
 602             if (tree != null) {
 603                 for (StatementAnalyzer<JCTree, JCTree> analyzer : analyzers) {
 604                     if (analyzer.isEnabled() &&
 605                             tree.hasTag(analyzer.tag) &&
 606                             analyzer.match(tree)) {
 607                         for (JCTree t : analyzer.rewrite(tree)) {
 608                             rewritings.add(new RewritingContext(originalTree, tree, t, analyzer, env));
 609                         }
 610                         break; //TODO: cover cases where multiple matching analyzers are found
 611                     }
 612                 }
 613             }
 614             super.scan(tree);
 615         }
 616 
 617         @Override
 618         public void visitClassDef(JCClassDecl tree) {
 619             //do nothing (prevents seeing same stuff twice)
 620         }
 621 
 622         @Override
 623         public void visitMethodDef(JCMethodDecl tree) {
 624             //do nothing (prevents seeing same stuff twice)
 625         }
 626 
 627         @Override
 628         public void visitBlock(JCBlock tree) {
 629             //do nothing (prevents seeing same stuff twice)
 630         }
 631 
 632         @Override
 633         public void visitSwitch(JCSwitch tree) {
 634             scan(tree.getExpression());
 635         }
 636 
 637         @Override
 638         public void visitForLoop(JCForLoop tree) {
 639             //skip body and var decl (to prevents same statements to be analyzed twice)
 640             scan(tree.getCondition());
 641             scan(tree.getUpdate());
 642         }
 643 
 644         @Override
 645         public void visitTry(JCTry tree) {
 646             //skip resources (to prevents same statements to be analyzed twice)
 647             scan(tree.getBlock());
 648             scan(tree.getCatches());
 649             scan(tree.getFinallyBlock());
 650         }
 651 
 652         @Override
 653         public void visitForeachLoop(JCEnhancedForLoop tree) {
 654             //skip body (to prevents same statements to be analyzed twice)
 655             scan(tree.getExpression());
 656         }
 657 
 658         @Override
 659         public void visitWhileLoop(JCWhileLoop tree) {
 660             //skip body (to prevents same statements to be analyzed twice)
 661             scan(tree.getCondition());
 662         }
 663 
 664         @Override
 665         public void visitDoLoop(JCDoWhileLoop tree) {
 666             //skip body (to prevents same statements to be analyzed twice)
 667             scan(tree.getCondition());
 668         }
 669 
 670         @Override
 671         public void visitIf(JCIf tree) {
 672             //skip body (to prevents same statements to be analyzed twice)
 673             scan(tree.getCondition());
 674         }
 675     }
 676 
 677     class RewritingContext {
 678         // the whole tree being analyzed
 679         JCTree originalTree;
 680         // a subtree, old tree, that will be rewritten
 681         JCTree oldTree;
 682         // the replacement for the old tree
 683         JCTree replacement;
 684         // did the compiler find any error
 685         boolean erroneous;
 686         // the env
 687         Env<AttrContext> env;
 688         // the corresponding analyzer
 689         StatementAnalyzer<JCTree, JCTree> analyzer;
 690 
 691         RewritingContext(
 692                 JCTree originalTree,
 693                 JCTree oldTree,
 694                 JCTree replacement,
 695                 StatementAnalyzer<JCTree, JCTree> analyzer,
 696                 Env<AttrContext> env) {
 697             this.originalTree = originalTree;
 698             this.oldTree = oldTree;
 699             this.replacement = replacement;
 700             this.analyzer = analyzer;
 701             this.env = attr.copyEnv(env);
 702             /*  this is a temporary workaround that should be removed once we have a truly independent
 703              *  clone operation
 704              */
 705             if (originalTree.hasTag(VARDEF)) {
 706                 // avoid redefinition clashes
 707                 this.env.info.scope.remove(((JCVariableDecl)originalTree).sym);
 708             }
 709         }
 710 
 711         /**
 712          * Simple deferred diagnostic handler which filters out all messages and keep track of errors.
 713          */
 714         Log.DeferredDiagnosticHandler diagHandler() {
 715             return new Log.DeferredDiagnosticHandler(log, d -> {
 716                 if (d.getType() == DiagnosticType.ERROR) {
 717                     erroneous = true;
 718                 }
 719                 return true;
 720             });
 721         }
 722     }
 723 
 724     /**
 725      * Subclass of TreeCopier that maps nodes matched by analyzers onto new AST nodes.
 726      */
 727     class AnalyzerCopier extends TreeCopier<Void> {
 728 
 729         public AnalyzerCopier() {
 730             super(make);
 731         }
 732 
 733         @Override @DefinedBy(Api.COMPILER_TREE)
 734         public JCTree visitLambdaExpression(LambdaExpressionTree node, Void _unused) {
 735             JCLambda oldLambda = (JCLambda)node;
 736             JCLambda newLambda = (JCLambda)super.visitLambdaExpression(node, _unused);
 737             if (oldLambda.paramKind == ParameterKind.IMPLICIT) {
 738                 //reset implicit lambda parameters (whose type might have been set during attr)
 739                 newLambda.paramKind = ParameterKind.IMPLICIT;
 740                 newLambda.params.forEach(p -> p.vartype = null);
 741             }
 742             return newLambda;
 743         }
 744 
 745         @Override @DefinedBy(Api.COMPILER_TREE)
 746         public JCTree visitNewClass(NewClassTree node, Void aVoid) {
 747             JCNewClass oldNewClazz = (JCNewClass)node;
 748             JCNewClass newNewClazz = (JCNewClass)super.visitNewClass(node, aVoid);
 749             if (!oldNewClazz.args.isEmpty() && oldNewClazz.args.head.hasTag(NULLCHK)) {
 750                 //workaround to Attr generating trees
 751                 newNewClazz.encl = ((JCUnary)newNewClazz.args.head).arg;
 752                 newNewClazz.args = newNewClazz.args.tail;
 753             }
 754             return newNewClazz;
 755         }
 756     }
 757 
 758    class TreeRewriter extends AnalyzerCopier {
 759 
 760         RewritingContext rewriting;
 761 
 762         TreeRewriter(RewritingContext rewriting) {
 763             this.rewriting = rewriting;
 764         }
 765 
 766         @Override
 767         @SuppressWarnings("unchecked")
 768         public <Z extends JCTree> Z copy(Z tree, Void _unused) {
 769             Z newTree = super.copy(tree, null);
 770             if (tree != null && tree == rewriting.oldTree) {
 771                 Assert.checkNonNull(rewriting.replacement);
 772                 newTree = (Z)rewriting.replacement;
 773             }
 774             return newTree;
 775         }
 776     }
 777 }