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