langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java
changeset 28142 32a6b1af81b1
child 28146 adf3a039cb7d
equal deleted inserted replaced
28141:a9bd48f9cf07 28142:32a6b1af81b1
       
     1 /*
       
     2  * Copyright (c) 2014, 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 com.sun.tools.javac.code.Source;
       
    29 import com.sun.tools.javac.code.Type;
       
    30 import com.sun.tools.javac.code.Types;
       
    31 import com.sun.tools.javac.tree.JCTree;
       
    32 import com.sun.tools.javac.tree.JCTree.JCBlock;
       
    33 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
       
    34 import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
       
    35 import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
       
    36 import com.sun.tools.javac.tree.JCTree.JCForLoop;
       
    37 import com.sun.tools.javac.tree.JCTree.JCIf;
       
    38 import com.sun.tools.javac.tree.JCTree.JCLambda;
       
    39 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
       
    40 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
       
    41 import com.sun.tools.javac.tree.JCTree.JCNewClass;
       
    42 import com.sun.tools.javac.tree.JCTree.JCStatement;
       
    43 import com.sun.tools.javac.tree.JCTree.JCSwitch;
       
    44 import com.sun.tools.javac.tree.JCTree.JCTypeApply;
       
    45 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
       
    46 import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
       
    47 import com.sun.tools.javac.tree.JCTree.Tag;
       
    48 import com.sun.tools.javac.tree.TreeCopier;
       
    49 import com.sun.tools.javac.tree.TreeInfo;
       
    50 import com.sun.tools.javac.tree.TreeMaker;
       
    51 import com.sun.tools.javac.tree.TreeScanner;
       
    52 import com.sun.tools.javac.util.Context;
       
    53 import com.sun.tools.javac.util.Filter;
       
    54 import com.sun.tools.javac.util.JCDiagnostic;
       
    55 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
       
    56 import com.sun.tools.javac.util.List;
       
    57 import com.sun.tools.javac.util.ListBuffer;
       
    58 import com.sun.tools.javac.util.Log;
       
    59 import com.sun.tools.javac.util.Names;
       
    60 import com.sun.tools.javac.util.Options;
       
    61 
       
    62 import java.util.EnumSet;
       
    63 import java.util.HashMap;
       
    64 import java.util.Map;
       
    65 import java.util.function.Predicate;
       
    66 
       
    67 import static com.sun.tools.javac.code.Flags.GENERATEDCONSTR;
       
    68 import static com.sun.tools.javac.code.Flags.SYNTHETIC;
       
    69 import static com.sun.tools.javac.code.TypeTag.CLASS;
       
    70 import static com.sun.tools.javac.tree.JCTree.Tag.APPLY;
       
    71 import static com.sun.tools.javac.tree.JCTree.Tag.CLASSDEF;
       
    72 import static com.sun.tools.javac.tree.JCTree.Tag.METHODDEF;
       
    73 import static com.sun.tools.javac.tree.JCTree.Tag.NEWCLASS;
       
    74 import static com.sun.tools.javac.tree.JCTree.Tag.TYPEAPPLY;
       
    75 
       
    76 /**
       
    77  * Helper class for defining custom code analysis, such as finding instance creation expression
       
    78  * that can benefit from diamond syntax.
       
    79  */
       
    80 public class Analyzer {
       
    81     protected static final Context.Key<Analyzer> analyzerKey = new Context.Key<>();
       
    82 
       
    83     final Types types;
       
    84     final Log log;
       
    85     final Attr attr;
       
    86     final DeferredAttr deferredAttr;
       
    87     final TreeMaker make;
       
    88     final Names names;
       
    89 
       
    90     final EnumSet<AnalyzerMode> analyzerModes;
       
    91 
       
    92     public static Analyzer instance(Context context) {
       
    93         Analyzer instance = context.get(analyzerKey);
       
    94         if (instance == null)
       
    95             instance = new Analyzer(context);
       
    96         return instance;
       
    97     }
       
    98 
       
    99     protected Analyzer(Context context) {
       
   100         context.put(analyzerKey, this);
       
   101         types = Types.instance(context);
       
   102         log = Log.instance(context);
       
   103         attr = Attr.instance(context);
       
   104         deferredAttr = DeferredAttr.instance(context);
       
   105         make = TreeMaker.instance(context);
       
   106         names = Names.instance(context);
       
   107         Options options = Options.instance(context);
       
   108         String findOpt = options.get("find");
       
   109         //parse modes
       
   110         Source source = Source.instance(context);
       
   111         analyzerModes = AnalyzerMode.getAnalyzerModes(findOpt, source);
       
   112     }
       
   113 
       
   114     /**
       
   115      * This enum defines supported analyzer modes, as well as defining the logic for decoding
       
   116      * the {@code -XDfind} option.
       
   117      */
       
   118     enum AnalyzerMode {
       
   119         DIAMOND("diamond", Source::allowDiamond),
       
   120         LAMBDA("lambda", Source::allowLambda),
       
   121         METHOD("method", Source::allowGraphInference);
       
   122 
       
   123         final String opt;
       
   124         final Predicate<Source> sourceFilter;
       
   125 
       
   126         AnalyzerMode(String opt, Predicate<Source> sourceFilter) {
       
   127             this.opt = opt;
       
   128             this.sourceFilter = sourceFilter;
       
   129         }
       
   130 
       
   131         /**
       
   132          * This method is used to parse the {@code find} option.
       
   133          * Possible modes are separated by colon; a mode can be excluded by
       
   134          * prepending '-' to its name. Finally, the special mode 'all' can be used to
       
   135          * add all modes to the resulting enum.
       
   136          */
       
   137         static EnumSet<AnalyzerMode> getAnalyzerModes(String opt, Source source) {
       
   138             if (opt == null) {
       
   139                 return EnumSet.noneOf(AnalyzerMode.class);
       
   140             }
       
   141             List<String> modes = List.from(opt.split(","));
       
   142             EnumSet<AnalyzerMode> res = EnumSet.noneOf(AnalyzerMode.class);
       
   143             if (modes.contains("all")) {
       
   144                 res = EnumSet.allOf(AnalyzerMode.class);
       
   145             }
       
   146             for (AnalyzerMode mode : values()) {
       
   147                 if (modes.contains(mode.opt)) {
       
   148                     res.add(mode);
       
   149                 } else if (modes.contains("-" + mode.opt) || !mode.sourceFilter.test(source)) {
       
   150                     res.remove(mode);
       
   151                 }
       
   152             }
       
   153             return res;
       
   154         }
       
   155     }
       
   156 
       
   157     /**
       
   158      * A statement analyzer is a work-unit that matches certain AST nodes (of given type {@code S}),
       
   159      * rewrites them to different AST nodes (of type {@code T}) and then generates some meaningful
       
   160      * messages in case the analysis has been successful.
       
   161      */
       
   162     abstract class StatementAnalyzer<S extends JCTree, T extends JCTree> {
       
   163 
       
   164         AnalyzerMode mode;
       
   165         JCTree.Tag tag;
       
   166 
       
   167         StatementAnalyzer(AnalyzerMode mode, Tag tag) {
       
   168             this.mode = mode;
       
   169             this.tag = tag;
       
   170         }
       
   171 
       
   172         /**
       
   173          * Is this analyzer allowed to run?
       
   174          */
       
   175         boolean isEnabled() {
       
   176             return analyzerModes.contains(mode);
       
   177         }
       
   178 
       
   179         /**
       
   180          * Should this analyzer be rewriting the given tree?
       
   181          */
       
   182         abstract boolean match(S tree);
       
   183 
       
   184         /**
       
   185          * Rewrite a given AST node into a new one
       
   186          */
       
   187         abstract T map(S oldTree, S newTree);
       
   188 
       
   189         /**
       
   190          * Entry-point for comparing results and generating diagnostics.
       
   191          */
       
   192         abstract void process(S oldTree, T newTree, boolean hasErrors);
       
   193 
       
   194     }
       
   195 
       
   196     /**
       
   197      * This analyzer checks if generic instance creation expression can use diamond syntax.
       
   198      */
       
   199     class DiamondInitializer extends StatementAnalyzer<JCNewClass, JCNewClass> {
       
   200 
       
   201         DiamondInitializer() {
       
   202             super(AnalyzerMode.DIAMOND, NEWCLASS);
       
   203         }
       
   204 
       
   205         @Override
       
   206         boolean match(JCNewClass tree) {
       
   207             return tree.clazz.hasTag(TYPEAPPLY) &&
       
   208                     !TreeInfo.isDiamond(tree) &&
       
   209                     tree.def == null;
       
   210         }
       
   211 
       
   212         @Override
       
   213         JCNewClass map(JCNewClass oldTree, JCNewClass newTree) {
       
   214             if (newTree.clazz.hasTag(TYPEAPPLY)) {
       
   215                 ((JCTypeApply)newTree.clazz).arguments = List.nil();
       
   216             }
       
   217             return newTree;
       
   218         }
       
   219 
       
   220         @Override
       
   221         void process(JCNewClass oldTree, JCNewClass newTree, boolean hasErrors) {
       
   222             if (!hasErrors) {
       
   223                 List<Type> inferredArgs = newTree.type.getTypeArguments();
       
   224                 List<Type> explicitArgs = oldTree.type.getTypeArguments();
       
   225                 for (Type t : inferredArgs) {
       
   226                     if (!types.isSameType(t, explicitArgs.head)) {
       
   227                         log.warning(oldTree.clazz, "diamond.redundant.args.1",
       
   228                                 oldTree.clazz.type, newTree.clazz.type);
       
   229                         return;
       
   230                     }
       
   231                     explicitArgs = explicitArgs.tail;
       
   232                 }
       
   233                 //exact match
       
   234                 log.warning(oldTree.clazz, "diamond.redundant.args");
       
   235             }
       
   236         }
       
   237     }
       
   238 
       
   239     /**
       
   240      * This analyzer checks if anonymous instance creation expression can replaced by lambda.
       
   241      */
       
   242     class LambdaAnalyzer extends StatementAnalyzer<JCNewClass, JCLambda> {
       
   243 
       
   244         LambdaAnalyzer() {
       
   245             super(AnalyzerMode.LAMBDA, NEWCLASS);
       
   246         }
       
   247 
       
   248         @Override
       
   249         boolean match (JCNewClass tree){
       
   250             Type clazztype = tree.clazz.type;
       
   251             return tree.def != null &&
       
   252                     clazztype.hasTag(CLASS) &&
       
   253                     types.isFunctionalInterface(clazztype.tsym) &&
       
   254                     decls(tree.def).length() == 1;
       
   255         }
       
   256         //where
       
   257             private List<JCTree> decls(JCClassDecl decl) {
       
   258                 ListBuffer<JCTree> decls = new ListBuffer<>();
       
   259                 for (JCTree t : decl.defs) {
       
   260                     if (t.hasTag(METHODDEF)) {
       
   261                         JCMethodDecl md = (JCMethodDecl)t;
       
   262                         if ((md.getModifiers().flags & GENERATEDCONSTR) == 0) {
       
   263                             decls.add(md);
       
   264                         }
       
   265                     } else {
       
   266                         decls.add(t);
       
   267                     }
       
   268                 }
       
   269                 return decls.toList();
       
   270             }
       
   271 
       
   272         @Override
       
   273         JCLambda map (JCNewClass oldTree, JCNewClass newTree){
       
   274             JCMethodDecl md = (JCMethodDecl)decls(newTree.def).head;
       
   275             List<JCVariableDecl> params = md.params;
       
   276             JCBlock body = md.body;
       
   277             return make.Lambda(params, body);
       
   278         }
       
   279         @Override
       
   280         void process (JCNewClass oldTree, JCLambda newTree, boolean hasErrors){
       
   281             if (!hasErrors) {
       
   282                 log.warning(oldTree.def, "potential.lambda.found");
       
   283             }
       
   284         }
       
   285     }
       
   286 
       
   287     /**
       
   288      * This analyzer checks if generic method call has redundant type arguments.
       
   289      */
       
   290     class RedundantTypeArgAnalyzer extends StatementAnalyzer<JCMethodInvocation, JCMethodInvocation> {
       
   291 
       
   292         RedundantTypeArgAnalyzer() {
       
   293             super(AnalyzerMode.METHOD, APPLY);
       
   294         }
       
   295 
       
   296         @Override
       
   297         boolean match (JCMethodInvocation tree){
       
   298             return tree.typeargs != null &&
       
   299                     tree.typeargs.nonEmpty();
       
   300         }
       
   301         @Override
       
   302         JCMethodInvocation map (JCMethodInvocation oldTree, JCMethodInvocation newTree){
       
   303             newTree.typeargs = List.nil();
       
   304             return newTree;
       
   305         }
       
   306         @Override
       
   307         void process (JCMethodInvocation oldTree, JCMethodInvocation newTree, boolean hasErrors){
       
   308             if (!hasErrors) {
       
   309                 //exact match
       
   310                 log.warning(oldTree, "method.redundant.typeargs");
       
   311             }
       
   312         }
       
   313     }
       
   314 
       
   315     @SuppressWarnings({"unchecked", "rawtypes"})
       
   316     StatementAnalyzer<JCTree, JCTree>[] analyzers = new StatementAnalyzer[] {
       
   317             new DiamondInitializer(),
       
   318             new LambdaAnalyzer(),
       
   319             new RedundantTypeArgAnalyzer()
       
   320     };
       
   321 
       
   322     /**
       
   323      * Analyze an AST node if needed.
       
   324      */
       
   325     void analyzeIfNeeded(JCTree tree, Env<AttrContext> env) {
       
   326         if (!analyzerModes.isEmpty() &&
       
   327                 !env.info.isSpeculative &&
       
   328                 TreeInfo.isStatement(tree)) {
       
   329             JCStatement stmt = (JCStatement)tree;
       
   330             analyze(stmt, env);
       
   331         }
       
   332     }
       
   333 
       
   334     /**
       
   335      * Analyze an AST node; this involves collecting a list of all the nodes that needs rewriting,
       
   336      * and speculatively type-check the rewritten code to compare results against previously attributed code.
       
   337      */
       
   338     void analyze(JCStatement statement, Env<AttrContext> env) {
       
   339         AnalysisContext context = new AnalysisContext();
       
   340         StatementScanner statementScanner = new StatementScanner(context);
       
   341         statementScanner.scan(statement);
       
   342 
       
   343         if (!context.treesToAnalyzer.isEmpty()) {
       
   344 
       
   345             //add a block to hoist potential dangling variable declarations
       
   346             JCBlock fakeBlock = make.Block(SYNTHETIC, List.of(statement));
       
   347 
       
   348             TreeMapper treeMapper = new TreeMapper(context);
       
   349             //TODO: to further refine the analysis, try all rewriting combinations
       
   350             deferredAttr.attribSpeculative(fakeBlock, env, attr.statInfo, treeMapper,
       
   351                     t -> new AnalyzeDeferredDiagHandler(context));
       
   352 
       
   353             context.treeMap.entrySet().forEach(e -> {
       
   354                 context.treesToAnalyzer.get(e.getKey())
       
   355                         .process(e.getKey(), e.getValue(), context.errors.nonEmpty());
       
   356             });
       
   357         }
       
   358     }
       
   359 
       
   360     /**
       
   361      * Simple deferred diagnostic handler which filters out all messages and keep track of errors.
       
   362      */
       
   363     class AnalyzeDeferredDiagHandler extends Log.DeferredDiagnosticHandler {
       
   364         AnalysisContext context;
       
   365 
       
   366         public AnalyzeDeferredDiagHandler(AnalysisContext context) {
       
   367             super(log, d -> {
       
   368                 if (d.getType() == DiagnosticType.ERROR) {
       
   369                     context.errors.add(d);
       
   370                 }
       
   371                 return true;
       
   372             });
       
   373             this.context = context;
       
   374         }
       
   375     }
       
   376 
       
   377     /**
       
   378      * This class is used to pass around contextual information bewteen analyzer classes, such as
       
   379      * trees to be rewritten, errors occurred during the speculative attribution step, etc.
       
   380      */
       
   381     class AnalysisContext {
       
   382         /** Map from trees to analyzers. */
       
   383         Map<JCTree, StatementAnalyzer<JCTree, JCTree>> treesToAnalyzer = new HashMap<>();
       
   384 
       
   385         /** Map from original AST nodes to rewritten AST nodes */
       
   386         Map<JCTree, JCTree> treeMap = new HashMap<>();
       
   387 
       
   388         /** Errors in rewritten tree */
       
   389         ListBuffer<JCDiagnostic> errors = new ListBuffer<>();
       
   390     }
       
   391 
       
   392     /**
       
   393      * Subclass of {@link com.sun.tools.javac.tree.TreeScanner} which visit AST-nodes w/o crossing
       
   394      * statement boundaries.
       
   395      */
       
   396     class StatementScanner extends TreeScanner {
       
   397 
       
   398         /** context */
       
   399         AnalysisContext context;
       
   400 
       
   401         StatementScanner(AnalysisContext context) {
       
   402             this.context = context;
       
   403         }
       
   404 
       
   405         @Override
       
   406         @SuppressWarnings("unchecked")
       
   407         public void scan(JCTree tree) {
       
   408             if (tree != null) {
       
   409                 for (StatementAnalyzer<JCTree, JCTree> analyzer : analyzers) {
       
   410                     if (analyzer.isEnabled() &&
       
   411                             tree.hasTag(analyzer.tag) &&
       
   412                             analyzer.match(tree)) {
       
   413                         context.treesToAnalyzer.put(tree, analyzer);
       
   414                         break; //TODO: cover cases where multiple matching analyzers are found
       
   415                     }
       
   416                 }
       
   417             }
       
   418             super.scan(tree);
       
   419         }
       
   420 
       
   421         @Override
       
   422         public void visitClassDef(JCClassDecl tree) {
       
   423             //do nothing (prevents seeing same stuff twice
       
   424         }
       
   425 
       
   426         @Override
       
   427         public void visitMethodDef(JCMethodDecl tree) {
       
   428             //do nothing (prevents seeing same stuff twice
       
   429         }
       
   430 
       
   431         @Override
       
   432         public void visitBlock(JCBlock tree) {
       
   433             //do nothing (prevents seeing same stuff twice
       
   434         }
       
   435 
       
   436         @Override
       
   437         public void visitSwitch(JCSwitch tree) {
       
   438             scan(tree.getExpression());
       
   439         }
       
   440 
       
   441         @Override
       
   442         public void visitForLoop(JCForLoop tree) {
       
   443             scan(tree.getInitializer());
       
   444             scan(tree.getCondition());
       
   445             scan(tree.getUpdate());
       
   446         }
       
   447 
       
   448         @Override
       
   449         public void visitForeachLoop(JCEnhancedForLoop tree) {
       
   450             scan(tree.getExpression());
       
   451         }
       
   452 
       
   453         @Override
       
   454         public void visitWhileLoop(JCWhileLoop tree) {
       
   455             scan(tree.getCondition());
       
   456         }
       
   457 
       
   458         @Override
       
   459         public void visitDoLoop(JCDoWhileLoop tree) {
       
   460             scan(tree.getCondition());
       
   461         }
       
   462 
       
   463         @Override
       
   464         public void visitIf(JCIf tree) {
       
   465             scan(tree.getCondition());
       
   466         }
       
   467     }
       
   468 
       
   469     /**
       
   470      * Subclass of TreeCopier that maps nodes matched by analyzers onto new AST nodes.
       
   471      */
       
   472     class TreeMapper extends TreeCopier<Void> {
       
   473 
       
   474         AnalysisContext context;
       
   475 
       
   476         TreeMapper(AnalysisContext context) {
       
   477             super(make);
       
   478             this.context = context;
       
   479         }
       
   480 
       
   481         @Override
       
   482         @SuppressWarnings("unchecked")
       
   483         public <Z extends JCTree> Z copy(Z tree, Void _unused) {
       
   484             Z newTree = super.copy(tree, _unused);
       
   485             StatementAnalyzer<JCTree, JCTree> analyzer = context.treesToAnalyzer.get(tree);
       
   486             if (analyzer != null) {
       
   487                 newTree = (Z)analyzer.map(tree, newTree);
       
   488                 context.treeMap.put(tree, newTree);
       
   489             }
       
   490             return newTree;
       
   491         }
       
   492     }
       
   493 }