Analyzer.java revision 2758:3c1b5fcf6fad
11556Srgrimes/*
21556Srgrimes * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
31556Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
41556Srgrimes *
51556Srgrimes * This code is free software; you can redistribute it and/or modify it
61556Srgrimes * under the terms of the GNU General Public License version 2 only, as
71556Srgrimes * published by the Free Software Foundation.  Oracle designates this
81556Srgrimes * particular file as subject to the "Classpath" exception as provided
91556Srgrimes * by Oracle in the LICENSE file that accompanied this code.
101556Srgrimes *
111556Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
121556Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
131556Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
141556Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
151556Srgrimes * accompanied this code).
161556Srgrimes *
171556Srgrimes * You should have received a copy of the GNU General Public License version
181556Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
191556Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
201556Srgrimes *
211556Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
221556Srgrimes * or visit www.oracle.com if you need additional information or have any
231556Srgrimes * questions.
241556Srgrimes */
251556Srgrimes
261556Srgrimespackage com.sun.tools.javac.comp;
271556Srgrimes
281556Srgrimesimport com.sun.tools.javac.code.Source;
291556Srgrimesimport com.sun.tools.javac.code.Type;
301556Srgrimesimport com.sun.tools.javac.code.Types;
311556Srgrimesimport com.sun.tools.javac.tree.JCTree;
321556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCBlock;
331556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCClassDecl;
341556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
3520413Ssteveimport com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
361556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCForLoop;
371556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCIf;
381556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCLambda;
391556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCMethodDecl;
401556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
4135773Scharnierimport com.sun.tools.javac.tree.JCTree.JCNewClass;
4236006Scharnierimport com.sun.tools.javac.tree.JCTree.JCStatement;
4335773Scharnierimport com.sun.tools.javac.tree.JCTree.JCSwitch;
441556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCTypeApply;
4599109Sobrienimport com.sun.tools.javac.tree.JCTree.JCVariableDecl;
4699109Sobrienimport com.sun.tools.javac.tree.JCTree.JCWhileLoop;
471556Srgrimesimport com.sun.tools.javac.tree.JCTree.Tag;
4836006Scharnierimport com.sun.tools.javac.tree.TreeCopier;
491556Srgrimesimport com.sun.tools.javac.tree.TreeInfo;
501556Srgrimesimport com.sun.tools.javac.tree.TreeMaker;
511556Srgrimesimport com.sun.tools.javac.tree.TreeScanner;
521556Srgrimesimport com.sun.tools.javac.util.Context;
5391079Smarkmimport com.sun.tools.javac.util.Filter;
5491079Smarkmimport com.sun.tools.javac.util.JCDiagnostic;
551556Srgrimesimport com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
561556Srgrimesimport com.sun.tools.javac.util.List;
5739925Saleximport com.sun.tools.javac.util.ListBuffer;
581556Srgrimesimport com.sun.tools.javac.util.Log;
591556Srgrimesimport com.sun.tools.javac.util.Names;
601556Srgrimesimport com.sun.tools.javac.util.Options;
611556Srgrimes
6227874Sbrianimport java.util.EnumSet;
631556Srgrimesimport java.util.HashMap;
6455225Ssheldonhimport java.util.Map;
6555225Ssheldonhimport java.util.function.Predicate;
6655225Ssheldonh
6755225Ssheldonhimport static com.sun.tools.javac.code.Flags.GENERATEDCONSTR;
68105396Smarkmimport static com.sun.tools.javac.code.Flags.SYNTHETIC;
6947129Sjmgimport static com.sun.tools.javac.code.TypeTag.CLASS;
701556Srgrimesimport static com.sun.tools.javac.tree.JCTree.Tag.APPLY;
7190108Simpimport static com.sun.tools.javac.tree.JCTree.Tag.CLASSDEF;
7290108Simpimport static com.sun.tools.javac.tree.JCTree.Tag.METHODDEF;
7390108Simpimport static com.sun.tools.javac.tree.JCTree.Tag.NEWCLASS;
741556Srgrimesimport static com.sun.tools.javac.tree.JCTree.Tag.TYPEAPPLY;
751556Srgrimes
7690108Simp/**
771556Srgrimes * Helper class for defining custom code analysis, such as finding instance creation expression
781556Srgrimes * that can benefit from diamond syntax.
7930073Sdanny */
8047129Sjmgpublic class Analyzer {
8191079Smarkm    protected static final Context.Key<Analyzer> analyzerKey = new Context.Key<>();
8291079Smarkm
8328037Sbrian    final Types types;
8485615Sdillon    final Log log;
855233Sbde    final Attr attr;
8627874Sbrian    final DeferredAttr deferredAttr;
8727874Sbrian    final TreeMaker make;
8827874Sbrian    final Names names;
891556Srgrimes
9027874Sbrian    final EnumSet<AnalyzerMode> analyzerModes;
9128037Sbrian
9211738Sache    public static Analyzer instance(Context context) {
931556Srgrimes        Analyzer instance = context.get(analyzerKey);
9430073Sdanny        if (instance == null)
9547129Sjmg            instance = new Analyzer(context);
965233Sbde        return instance;
9747129Sjmg    }
981556Srgrimes
991556Srgrimes    protected Analyzer(Context context) {
1005233Sbde        context.put(analyzerKey, this);
1015233Sbde        types = Types.instance(context);
1025233Sbde        log = Log.instance(context);
1035233Sbde        attr = Attr.instance(context);
1041556Srgrimes        deferredAttr = DeferredAttr.instance(context);
10528037Sbrian        make = TreeMaker.instance(context);
10628037Sbrian        names = Names.instance(context);
10728037Sbrian        Options options = Options.instance(context);
10847129Sjmg        String findOpt = options.get("find");
10947129Sjmg        //parse modes
11047129Sjmg        Source source = Source.instance(context);
1111556Srgrimes        analyzerModes = AnalyzerMode.getAnalyzerModes(findOpt, source);
1121556Srgrimes    }
1131556Srgrimes
1141556Srgrimes    /**
1151556Srgrimes     * This enum defines supported analyzer modes, as well as defining the logic for decoding
11685615Sdillon     * the {@code -XDfind} option.
11785615Sdillon     */
11863761Sjwd    enum AnalyzerMode {
1191556Srgrimes        DIAMOND("diamond", Source::allowDiamond),
12060718Sdbaker        LAMBDA("lambda", Source::allowLambda),
1211556Srgrimes        METHOD("method", Source::allowGraphInference);
1225233Sbde
1235233Sbde        final String opt;
1245233Sbde        final Predicate<Source> sourceFilter;
1255233Sbde
1265233Sbde        AnalyzerMode(String opt, Predicate<Source> sourceFilter) {
12760718Sdbaker            this.opt = opt;
12860718Sdbaker            this.sourceFilter = sourceFilter;
12928037Sbrian        }
13028025Sbrian
13128025Sbrian        /**
13227874Sbrian         * This method is used to parse the {@code find} option.
1331556Srgrimes         * Possible modes are separated by colon; a mode can be excluded by
1341556Srgrimes         * prepending '-' to its name. Finally, the special mode 'all' can be used to
1351556Srgrimes         * add all modes to the resulting enum.
1361556Srgrimes         */
1371556Srgrimes        static EnumSet<AnalyzerMode> getAnalyzerModes(String opt, Source source) {
1381556Srgrimes            if (opt == null) {
1391556Srgrimes                return EnumSet.noneOf(AnalyzerMode.class);
1401556Srgrimes            }
14124976Sdanny            List<String> modes = List.from(opt.split(","));
1421556Srgrimes            EnumSet<AnalyzerMode> res = EnumSet.noneOf(AnalyzerMode.class);
1435233Sbde            if (modes.contains("all")) {
1445233Sbde                res = EnumSet.allOf(AnalyzerMode.class);
1451556Srgrimes            }
1461556Srgrimes            for (AnalyzerMode mode : values()) {
1471556Srgrimes                if (modes.contains(mode.opt)) {
1481556Srgrimes                    res.add(mode);
1499944Sache                } else if (modes.contains("-" + mode.opt) || !mode.sourceFilter.test(source)) {
1501556Srgrimes                    res.remove(mode);
1511556Srgrimes                }
1521556Srgrimes            }
1531556Srgrimes            return res;
1541556Srgrimes        }
1551556Srgrimes    }
1561556Srgrimes
1571556Srgrimes    /**
15847129Sjmg     * A statement analyzer is a work-unit that matches certain AST nodes (of given type {@code S}),
1591556Srgrimes     * rewrites them to different AST nodes (of type {@code T}) and then generates some meaningful
16028037Sbrian     * messages in case the analysis has been successful.
16128037Sbrian     */
1621556Srgrimes    abstract class StatementAnalyzer<S extends JCTree, T extends JCTree> {
1631556Srgrimes
1641556Srgrimes        AnalyzerMode mode;
1651556Srgrimes        JCTree.Tag tag;
16627874Sbrian
16727874Sbrian        StatementAnalyzer(AnalyzerMode mode, Tag tag) {
16827874Sbrian            this.mode = mode;
16928025Sbrian            this.tag = tag;
17028025Sbrian        }
17127874Sbrian
17227874Sbrian        /**
17327874Sbrian         * Is this analyzer allowed to run?
17427874Sbrian         */
17527874Sbrian        boolean isEnabled() {
17630073Sdanny            return analyzerModes.contains(mode);
1771556Srgrimes        }
1781556Srgrimes
1791556Srgrimes        /**
18055225Ssheldonh         * Should this analyzer be rewriting the given tree?
18155225Ssheldonh         */
182105396Smarkm        abstract boolean match(S tree);
18390108Simp
1841556Srgrimes        /**
18590108Simp         * Rewrite a given AST node into a new one
1861556Srgrimes         */
18728037Sbrian        abstract T map(S oldTree, S newTree);
18855225Ssheldonh
1891556Srgrimes        /**
19028037Sbrian         * Entry-point for comparing results and generating diagnostics.
19128037Sbrian         */
19228037Sbrian        abstract void process(S oldTree, T newTree, boolean hasErrors);
19328037Sbrian
19428037Sbrian    }
19528037Sbrian
19648214Scracauer    /**
19728037Sbrian     * This analyzer checks if generic instance creation expression can use diamond syntax.
19832756Sjb     */
19928037Sbrian    class DiamondInitializer extends StatementAnalyzer<JCNewClass, JCNewClass> {
20032756Sjb
20128037Sbrian        DiamondInitializer() {
20228037Sbrian            super(AnalyzerMode.DIAMOND, NEWCLASS);
20328037Sbrian        }
20428037Sbrian
20528037Sbrian        @Override
20628037Sbrian        boolean match(JCNewClass tree) {
20728037Sbrian            return tree.clazz.hasTag(TYPEAPPLY) &&
20828037Sbrian                    !TreeInfo.isDiamond(tree) &&
20928037Sbrian                    tree.def == null;
2101556Srgrimes        }
2111556Srgrimes
21228037Sbrian        @Override
2131556Srgrimes        JCNewClass map(JCNewClass oldTree, JCNewClass newTree) {
21428037Sbrian            if (newTree.clazz.hasTag(TYPEAPPLY)) {
21528037Sbrian                ((JCTypeApply)newTree.clazz).arguments = List.nil();
21628037Sbrian            }
21728037Sbrian            return newTree;
21828037Sbrian        }
21928037Sbrian
22028037Sbrian        @Override
22128037Sbrian        void process(JCNewClass oldTree, JCNewClass newTree, boolean hasErrors) {
22228037Sbrian            if (!hasErrors) {
2231556Srgrimes                List<Type> inferredArgs = newTree.type.getTypeArguments();
22455225Ssheldonh                List<Type> explicitArgs = oldTree.type.getTypeArguments();
22530013Sjoerg                for (Type t : inferredArgs) {
22630013Sjoerg                    if (!types.isSameType(t, explicitArgs.head)) {
22753082Ssheldonh                        log.warning(oldTree.clazz, "diamond.redundant.args.1",
22855225Ssheldonh                                oldTree.clazz.type, newTree.clazz.type);
22955225Ssheldonh                        return;
23055225Ssheldonh                    }
23128037Sbrian                    explicitArgs = explicitArgs.tail;
23255225Ssheldonh                }
23355225Ssheldonh                //exact match
23455225Ssheldonh                log.warning(oldTree.clazz, "diamond.redundant.args");
23555225Ssheldonh            }
23655225Ssheldonh        }
23755225Ssheldonh    }
23855225Ssheldonh
23955225Ssheldonh    /**
24055225Ssheldonh     * This analyzer checks if anonymous instance creation expression can replaced by lambda.
24155225Ssheldonh     */
24228037Sbrian    class LambdaAnalyzer extends StatementAnalyzer<JCNewClass, JCLambda> {
24328037Sbrian
24428037Sbrian        LambdaAnalyzer() {
24528037Sbrian            super(AnalyzerMode.LAMBDA, NEWCLASS);
24628037Sbrian        }
24728037Sbrian
24828037Sbrian        @Override
24928037Sbrian        boolean match (JCNewClass tree){
25028037Sbrian            Type clazztype = tree.clazz.type;
25128037Sbrian            return tree.def != null &&
25228037Sbrian                    clazztype.hasTag(CLASS) &&
25328037Sbrian                    types.isFunctionalInterface(clazztype.tsym) &&
25428037Sbrian                    decls(tree.def).length() == 1;
25528037Sbrian        }
25628037Sbrian        //where
25728037Sbrian            private List<JCTree> decls(JCClassDecl decl) {
25828037Sbrian                ListBuffer<JCTree> decls = new ListBuffer<>();
25928037Sbrian                for (JCTree t : decl.defs) {
26028037Sbrian                    if (t.hasTag(METHODDEF)) {
26128037Sbrian                        JCMethodDecl md = (JCMethodDecl)t;
26228037Sbrian                        if ((md.getModifiers().flags & GENERATEDCONSTR) == 0) {
26328037Sbrian                            decls.add(md);
2641556Srgrimes                        }
26528037Sbrian                    } else {
2661556Srgrimes                        decls.add(t);
2671556Srgrimes                    }
26876749Sru                }
26976749Sru                return decls.toList();
27076749Sru            }
2711556Srgrimes
2721556Srgrimes        @Override
27315068Sache        JCLambda map (JCNewClass oldTree, JCNewClass newTree){
2741556Srgrimes            JCMethodDecl md = (JCMethodDecl)decls(newTree.def).head;
27547129Sjmg            List<JCVariableDecl> params = md.params;
27647129Sjmg            JCBlock body = md.body;
27747129Sjmg            return make.Lambda(params, body);
27847129Sjmg        }
27947129Sjmg        @Override
28047129Sjmg        void process (JCNewClass oldTree, JCLambda newTree, boolean hasErrors){
28147129Sjmg            if (!hasErrors) {
28247129Sjmg                log.warning(oldTree.def, "potential.lambda.found");
28347129Sjmg            }
28447129Sjmg        }
28547129Sjmg    }
28647129Sjmg
28747129Sjmg    /**
28847129Sjmg     * This analyzer checks if generic method call has redundant type arguments.
2891556Srgrimes     */
2901556Srgrimes    class RedundantTypeArgAnalyzer extends StatementAnalyzer<JCMethodInvocation, JCMethodInvocation> {
2911556Srgrimes
2921556Srgrimes        RedundantTypeArgAnalyzer() {
29390108Simp            super(AnalyzerMode.METHOD, APPLY);
2941556Srgrimes        }
2951556Srgrimes
2961556Srgrimes        @Override
2971556Srgrimes        boolean match (JCMethodInvocation tree){
2981556Srgrimes            return tree.typeargs != null &&
2991556Srgrimes                    tree.typeargs.nonEmpty();
30090108Simp        }
3011556Srgrimes        @Override
30226465Scharnier        JCMethodInvocation map (JCMethodInvocation oldTree, JCMethodInvocation newTree){
30377934Sdd            newTree.typeargs = List.nil();
30444598Sbrian            return newTree;
30553082Ssheldonh        }
30653082Ssheldonh        @Override
3071556Srgrimes        void process (JCMethodInvocation oldTree, JCMethodInvocation newTree, boolean hasErrors){
3081556Srgrimes            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}
494