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