8183896: improve analyzers to allow for the generation of multiple mutations of the same tree
authorvromero
Thu, 26 Oct 2017 22:54:25 -0400
changeset 47465 bc25e62f4794
parent 47464 36de9c637393
child 47467 118c900a45a4
8183896: improve analyzers to allow for the generation of multiple mutations of the same tree Reviewed-by: mcimadamore, jlahoda
src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java	Fri Oct 27 08:21:53 2017 +0530
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java	Thu Oct 26 22:54:25 2017 -0400
@@ -58,12 +58,10 @@
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.DefinedBy;
 import com.sun.tools.javac.util.DefinedBy.Api;
-import com.sun.tools.javac.util.JCDiagnostic;
 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
 import com.sun.tools.javac.util.List;
 import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Log;
-import com.sun.tools.javac.util.Names;
 import com.sun.tools.javac.util.Options;
 
 import java.util.EnumSet;
@@ -104,7 +102,7 @@
     final DeferredAttr deferredAttr;
     final ArgumentAttr argumentAttr;
     final TreeMaker make;
-    final Names names;
+    final AnalyzerCopier copier;
     private final boolean allowDiamondWithAnonymousClassCreation;
 
     final EnumSet<AnalyzerMode> analyzerModes;
@@ -124,7 +122,7 @@
         deferredAttr = DeferredAttr.instance(context);
         argumentAttr = ArgumentAttr.instance(context);
         make = TreeMaker.instance(context);
-        names = Names.instance(context);
+        copier = new AnalyzerCopier();
         Options options = Options.instance(context);
         String findOpt = options.get("find");
         //parse modes
@@ -205,15 +203,14 @@
         abstract boolean match(S tree);
 
         /**
-         * Rewrite a given AST node into a new one
+         * Rewrite a given AST node into a new one(s)
          */
-        abstract T map(S oldTree, S newTree);
+        abstract List<T> rewrite(S oldTree);
 
         /**
          * Entry-point for comparing results and generating diagnostics.
          */
         abstract void process(S oldTree, T newTree, boolean hasErrors);
-
     }
 
     /**
@@ -233,11 +230,14 @@
         }
 
         @Override
-        JCNewClass map(JCNewClass oldTree, JCNewClass newTree) {
-            if (newTree.clazz.hasTag(TYPEAPPLY)) {
-                ((JCTypeApply)newTree.clazz).arguments = List.nil();
+        List<JCNewClass> rewrite(JCNewClass oldTree) {
+            if (oldTree.clazz.hasTag(TYPEAPPLY)) {
+                JCNewClass nc = copier.copy(oldTree);
+                ((JCTypeApply)nc.clazz).arguments = List.nil();
+                return List.of(nc);
+            } else {
+                return List.of(oldTree);
             }
-            return newTree;
         }
 
         @Override
@@ -301,12 +301,14 @@
             }
 
         @Override
-        JCLambda map (JCNewClass oldTree, JCNewClass newTree){
-            JCMethodDecl md = (JCMethodDecl)decls(newTree.def).head;
+        List<JCLambda> rewrite(JCNewClass oldTree){
+            JCMethodDecl md = (JCMethodDecl)decls(oldTree.def).head;
             List<JCVariableDecl> params = md.params;
             JCBlock body = md.body;
-            return make.Lambda(params, body);
+            JCLambda newTree = make.Lambda(params, body);
+            return List.of(newTree);
         }
+
         @Override
         void process (JCNewClass oldTree, JCLambda newTree, boolean hasErrors){
             if (!hasErrors) {
@@ -330,10 +332,12 @@
                     tree.typeargs.nonEmpty();
         }
         @Override
-        JCMethodInvocation map (JCMethodInvocation oldTree, JCMethodInvocation newTree){
-            newTree.typeargs = List.nil();
-            return newTree;
+        List<JCMethodInvocation> rewrite(JCMethodInvocation oldTree){
+            JCMethodInvocation app = copier.copy(oldTree);
+            app.typeargs = List.nil();
+            return List.of(app);
         }
+
         @Override
         void process (JCMethodInvocation oldTree, JCMethodInvocation newTree, boolean hasErrors){
             if (!hasErrors) {
@@ -355,7 +359,8 @@
         /**
          * Map a variable tree into a new declaration using implicit type.
          */
-        JCVariableDecl mapVar(JCVariableDecl oldTree, JCVariableDecl newTree){
+        JCVariableDecl rewriteVarType(JCVariableDecl oldTree) {
+            JCVariableDecl newTree = copier.copy(oldTree);
             newTree.vartype = null;
             return newTree;
         }
@@ -363,7 +368,7 @@
         /**
          * Analyze results of local variable inference.
          */
-        void processVar(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors){
+        void processVar(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors) {
             if (!hasErrors) {
                 if (types.isSameType(oldTree.type, newTree.type)) {
                     log.warning(oldTree, Warnings.LocalRedundantType);
@@ -387,8 +392,8 @@
                     attr.canInferLocalVarType(tree) == null;
         }
         @Override
-        JCVariableDecl map(JCVariableDecl oldTree, JCVariableDecl newTree){
-            return mapVar(oldTree, newTree);
+        List<JCVariableDecl> rewrite(JCVariableDecl oldTree) {
+            return List.of(rewriteVarType(oldTree));
         }
         @Override
         void process(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors){
@@ -410,10 +415,11 @@
             return !tree.var.isImplicitlyTyped();
         }
         @Override
-        JCEnhancedForLoop map(JCEnhancedForLoop oldTree, JCEnhancedForLoop newTree){
-            newTree.var = mapVar(oldTree.var, newTree.var);
-            newTree.body = make.Block(0, List.nil()); //ignore body for analysis purpose
-            return newTree;
+        List<JCEnhancedForLoop> rewrite(JCEnhancedForLoop oldTree) {
+            JCEnhancedForLoop newTree = copier.copy(oldTree);
+            newTree.var = rewriteVarType(oldTree.var);
+            newTree.body = make.Block(0, List.nil());
+            return List.of(newTree);
         }
         @Override
         void process(JCEnhancedForLoop oldTree, JCEnhancedForLoop newTree, boolean hasErrors){
@@ -464,12 +470,13 @@
      * and speculatively type-check the rewritten code to compare results against previously attributed code.
      */
     void analyze(JCStatement statement, Env<AttrContext> env) {
-        AnalysisContext context = new AnalysisContext(statement, env);
-        StatementScanner statementScanner = new StatementScanner(context);
-        statementScanner.scan(statement);
+        StatementScanner statementScanner = new StatementScanner(statement, env);
+        statementScanner.scan();
 
-        if (!context.treesToAnalyzer.isEmpty()) {
-            deferredAnalysisHelper.queue(context);
+        if (!statementScanner.rewritings.isEmpty()) {
+            for (RewritingContext rewriting : statementScanner.rewritings) {
+                deferredAnalysisHelper.queue(rewriting);
+            }
         }
     }
 
@@ -480,7 +487,7 @@
         /**
          * Add a new analysis task to the queue.
          */
-        void queue(AnalysisContext context);
+        void queue(RewritingContext rewriting);
         /**
          * Flush queue with given attribution env.
          */
@@ -492,7 +499,7 @@
      */
     DeferredAnalysisHelper flushDeferredHelper = new DeferredAnalysisHelper() {
         @Override
-        public void queue(AnalysisContext context) {
+        public void queue(RewritingContext rewriting) {
             //do nothing
         }
 
@@ -508,12 +515,12 @@
      */
     DeferredAnalysisHelper queueDeferredHelper = new DeferredAnalysisHelper() {
 
-        Map<ClassSymbol, ArrayList<AnalysisContext>> Q = new HashMap<>();
+        Map<ClassSymbol, ArrayList<RewritingContext>> Q = new HashMap<>();
 
         @Override
-        public void queue(AnalysisContext context) {
-            ArrayList<AnalysisContext> s = Q.computeIfAbsent(context.env.enclClass.sym.outermostClass(), k -> new ArrayList<>());
-            s.add(context);
+        public void queue(RewritingContext rewriting) {
+            ArrayList<RewritingContext> s = Q.computeIfAbsent(rewriting.env.enclClass.sym.outermostClass(), k -> new ArrayList<>());
+            s.add(rewriting);
         }
 
         @Override
@@ -522,9 +529,9 @@
                 DeferredAnalysisHelper prevHelper = deferredAnalysisHelper;
                 try {
                     deferredAnalysisHelper = flushDeferredHelper;
-                    ArrayList<AnalysisContext> s = Q.get(flushEnv.enclClass.sym.outermostClass());
-                    while (s != null && !s.isEmpty()) {
-                        doAnalysis(s.remove(0));
+                    ArrayList<RewritingContext> rewritings = Q.get(flushEnv.enclClass.sym.outermostClass());
+                    while (rewritings != null && !rewritings.isEmpty()) {
+                        doAnalysis(rewritings.remove(0));
                     }
                 } finally {
                     deferredAnalysisHelper = prevHelper;
@@ -535,28 +542,24 @@
 
     DeferredAnalysisHelper deferredAnalysisHelper = queueDeferredHelper;
 
-    void doAnalysis(AnalysisContext context) {
+    void doAnalysis(RewritingContext rewriting) {
         DiagnosticSource prevSource = log.currentSource();
         LocalCacheContext localCacheContext = argumentAttr.withLocalCacheContext();
         try {
-            log.useSource(context.env.toplevel.getSourceFile());
+            log.useSource(rewriting.env.toplevel.getSourceFile());
 
-            JCStatement treeToAnalyze = (JCStatement)context.tree;
-            if (context.env.info.scope.owner.kind == Kind.TYP) {
+            JCStatement treeToAnalyze = (JCStatement)rewriting.originalTree;
+            if (rewriting.env.info.scope.owner.kind == Kind.TYP) {
                 //add a block to hoist potential dangling variable declarations
-                treeToAnalyze = make.Block(Flags.SYNTHETIC, List.of((JCStatement)context.tree));
+                treeToAnalyze = make.Block(Flags.SYNTHETIC, List.of((JCStatement)rewriting.originalTree));
             }
 
-            TreeMapper treeMapper = new TreeMapper(context);
             //TODO: to further refine the analysis, try all rewriting combinations
-            deferredAttr.attribSpeculative(treeToAnalyze, context.env, attr.statInfo, treeMapper,
-                    t -> new AnalyzeDeferredDiagHandler(context), argumentAttr.withLocalCacheContext());
-            context.treeMap.entrySet().forEach(e -> {
-                context.treesToAnalyzer.get(e.getKey())
-                        .process(e.getKey(), e.getValue(), context.errors.nonEmpty());
-            });
+            deferredAttr.attribSpeculative(treeToAnalyze, rewriting.env, attr.statInfo, new TreeRewriter(rewriting),
+                    t -> rewriting.diagHandler(), argumentAttr.withLocalCacheContext());
+            rewriting.analyzer.process(rewriting.oldTree, rewriting.replacement, rewriting.erroneous);
         } catch (Throwable ex) {
-            Assert.error("Analyzer error when processing: " + context.tree);
+            Assert.error("Analyzer error when processing: " + rewriting.originalTree);
         } finally {
             log.useSource(prevSource.getFile());
             localCacheContext.leave();
@@ -568,65 +571,22 @@
     }
 
     /**
-     * Simple deferred diagnostic handler which filters out all messages and keep track of errors.
-     */
-    class AnalyzeDeferredDiagHandler extends Log.DeferredDiagnosticHandler {
-        AnalysisContext context;
-
-        public AnalyzeDeferredDiagHandler(AnalysisContext context) {
-            super(log, d -> {
-                if (d.getType() == DiagnosticType.ERROR) {
-                    context.errors.add(d);
-                }
-                return true;
-            });
-            this.context = context;
-        }
-    }
-
-    /**
-     * This class is used to pass around contextual information bewteen analyzer classes, such as
-     * trees to be rewritten, errors occurred during the speculative attribution step, etc.
-     */
-    class AnalysisContext {
-
-        JCTree tree;
-
-        Env<AttrContext> env;
-
-        AnalysisContext(JCTree tree, Env<AttrContext> env) {
-            this.tree = tree;
-            this.env = attr.copyEnv(env);
-            /*  this is a temporary workaround that should be removed once we have a truly independent
-             *  clone operation
-             */
-            if (tree.hasTag(VARDEF)) {
-                // avoid redefinition clashes
-                this.env.info.scope.remove(((JCVariableDecl)tree).sym);
-            }
-        }
-
-        /** Map from trees to analyzers. */
-        Map<JCTree, StatementAnalyzer<JCTree, JCTree>> treesToAnalyzer = new HashMap<>();
-
-        /** Map from original AST nodes to rewritten AST nodes */
-        Map<JCTree, JCTree> treeMap = new HashMap<>();
-
-        /** Errors in rewritten tree */
-        ListBuffer<JCDiagnostic> errors = new ListBuffer<>();
-    }
-
-    /**
      * Subclass of {@link com.sun.tools.javac.tree.TreeScanner} which visit AST-nodes w/o crossing
      * statement boundaries.
      */
     class StatementScanner extends TreeScanner {
+        /** Tree rewritings (generated by analyzers). */
+        ListBuffer<RewritingContext> rewritings = new ListBuffer<>();
+        JCTree originalTree;
+        Env<AttrContext> env;
 
-        /** context */
-        AnalysisContext context;
+        StatementScanner(JCTree originalTree, Env<AttrContext> env) {
+            this.originalTree = originalTree;
+            this.env = attr.copyEnv(env);
+        }
 
-        StatementScanner(AnalysisContext context) {
-            this.context = context;
+        public void scan() {
+            scan(originalTree);
         }
 
         @Override
@@ -637,7 +597,9 @@
                     if (analyzer.isEnabled() &&
                             tree.hasTag(analyzer.tag) &&
                             analyzer.match(tree)) {
-                        context.treesToAnalyzer.put(tree, analyzer);
+                        for (JCTree t : analyzer.rewrite(tree)) {
+                            rewritings.add(new RewritingContext(originalTree, tree, t, analyzer, env));
+                        }
                         break; //TODO: cover cases where multiple matching analyzers are found
                     }
                 }
@@ -705,28 +667,60 @@
         }
     }
 
+    class RewritingContext {
+        // the whole tree being analyzed
+        JCTree originalTree;
+        // a subtree, old tree, that will be rewritten
+        JCTree oldTree;
+        // the replacement for the old tree
+        JCTree replacement;
+        // did the compiler find any error
+        boolean erroneous;
+        // the env
+        Env<AttrContext> env;
+        // the corresponding analyzer
+        StatementAnalyzer<JCTree, JCTree> analyzer;
+
+        RewritingContext(
+                JCTree originalTree,
+                JCTree oldTree,
+                JCTree replacement,
+                StatementAnalyzer<JCTree, JCTree> analyzer,
+                Env<AttrContext> env) {
+            this.originalTree = originalTree;
+            this.oldTree = oldTree;
+            this.replacement = replacement;
+            this.analyzer = analyzer;
+            this.env = attr.copyEnv(env);
+            /*  this is a temporary workaround that should be removed once we have a truly independent
+             *  clone operation
+             */
+            if (originalTree.hasTag(VARDEF)) {
+                // avoid redefinition clashes
+                this.env.info.scope.remove(((JCVariableDecl)originalTree).sym);
+            }
+        }
+
+        /**
+         * Simple deferred diagnostic handler which filters out all messages and keep track of errors.
+         */
+        Log.DeferredDiagnosticHandler diagHandler() {
+            return new Log.DeferredDiagnosticHandler(log, d -> {
+                if (d.getType() == DiagnosticType.ERROR) {
+                    erroneous = true;
+                }
+                return true;
+            });
+        }
+    }
+
     /**
      * Subclass of TreeCopier that maps nodes matched by analyzers onto new AST nodes.
      */
-    class TreeMapper extends TreeCopier<Void> {
-
-        AnalysisContext context;
-
-        TreeMapper(AnalysisContext context) {
-            super(make);
-            this.context = context;
-        }
+    class AnalyzerCopier extends TreeCopier<Void> {
 
-        @Override
-        @SuppressWarnings("unchecked")
-        public <Z extends JCTree> Z copy(Z tree, Void _unused) {
-            Z newTree = super.copy(tree, _unused);
-            StatementAnalyzer<JCTree, JCTree> analyzer = context.treesToAnalyzer.get(tree);
-            if (analyzer != null) {
-                newTree = (Z)analyzer.map(tree, newTree);
-                context.treeMap.put(tree, newTree);
-            }
-            return newTree;
+        public AnalyzerCopier() {
+            super(make);
         }
 
         @Override @DefinedBy(Api.COMPILER_TREE)
@@ -753,4 +747,24 @@
             return newNewClazz;
         }
     }
+
+   class TreeRewriter extends AnalyzerCopier {
+
+        RewritingContext rewriting;
+
+        TreeRewriter(RewritingContext rewriting) {
+            this.rewriting = rewriting;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <Z extends JCTree> Z copy(Z tree, Void _unused) {
+            Z newTree = super.copy(tree, null);
+            if (tree != null && tree == rewriting.oldTree) {
+                Assert.checkNonNull(rewriting.replacement);
+                newTree = (Z)rewriting.replacement;
+            }
+            return newTree;
+        }
+    }
 }