changeset 3811:e5e4064d037d

8147527: Wrong code generated for postfix unary operators Summary: Avoiding use of duplicated tree nodes when these may be changed in place. Reviewed-by: mcimadamore, jlahoda Contributed-by: bsrbnd@gmail.com
author jlahoda
date Mon, 12 Dec 2016 13:27:39 +0100
parents 27be91e4393b
children 4d4cd7cd731c
files src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java test/tools/javac/boxing/QualBoxedPostOp.java test/tools/javac/boxing/QualBoxedPostOp2.java test/tools/javac/boxing/QualBoxedPostOp3.java test/tools/javac/boxing/QualBoxedPostOp3Parent.java test/tools/javac/desugar/BoxingAndSuper.java
diffstat 6 files changed, 647 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java	Fri Dec 09 16:24:50 2016 -0800
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java	Mon Dec 12 13:27:39 2016 +0100
@@ -2218,7 +2218,7 @@
                 return builder.build(rval);
         }
         Name name = TreeInfo.name(rval);
-        if (name == names._super)
+        if (name == names._super || name == names._this)
             return builder.build(rval);
         VarSymbol var =
             new VarSymbol(FINAL|SYNTHETIC,
@@ -3206,7 +3206,11 @@
                                                                       newTag,
                                                                       tree.type,
                                                                       tree.rhs.type);
-                        JCExpression expr = lhs;
+                        //Need to use the "lhs" at two places, once on the future left hand side
+                        //and once in the future binary operator. But further processing may change
+                        //the components of the tree in place (see visitSelect for e.g. <Class>.super.<ident>),
+                        //so cloning the tree to avoid interference between the uses:
+                        JCExpression expr = (JCExpression) lhs.clone();
                         if (expr.type != tree.type)
                             expr = make.TypeCast(tree.type, expr);
                         JCBinary opResult = make.Binary(newTag, expr, tree.rhs);
@@ -3289,9 +3293,14 @@
                             public JCExpression build(final JCExpression tmp2) {
                                 JCTree.Tag opcode = (tree.hasTag(POSTINC))
                                     ? PLUS_ASG : MINUS_ASG;
-                                JCTree lhs = cast
-                                    ? make.TypeCast(tree.arg.type, tmp1)
-                                    : tmp1;
+                                //"tmp1" and "tmp2" may refer to the same instance
+                                //(for e.g. <Class>.super.<ident>). But further processing may
+                                //change the components of the tree in place (see visitSelect),
+                                //so cloning the tree to avoid interference between the two uses:
+                                JCExpression lhs = (JCExpression)tmp1.clone();
+                                lhs = cast
+                                    ? make.TypeCast(tree.arg.type, lhs)
+                                    : lhs;
                                 JCExpression update = makeAssignop(opcode,
                                                              lhs,
                                                              make.Literal(1));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/boxing/QualBoxedPostOp.java	Mon Dec 12 13:27:39 2016 +0100
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8147527
+ * @summary Qualified "this" and "super" boxed unary post operations.
+ * @compile QualBoxedPostOp.java
+ * @run main QualBoxedPostOp
+ */
+public class QualBoxedPostOp extends Parent {
+    public static void main(String[] args) {
+        new QualBoxedPostOp().testAll();
+    }
+
+    private void testAll() {
+        equals(test(), 0);
+        equals(i, 1);
+
+        Inner in = new Inner();
+        equals(in.test(), 1);
+        equals(i, 2);
+
+        equals(testParent(), 10);
+        equals(super.i, 11);
+
+        equals(in.testParent(), 11);
+        equals(super.i, 12);
+    }
+
+    private void equals(int a, int b) {
+        if (a != b) throw new Error();
+    }
+
+    Integer i=0;
+
+    private Integer test() {
+        return this.i++;
+    }
+    private Integer testParent() {
+        return super.i++;
+    }
+
+    class Inner {
+        private Integer test() {
+            return QualBoxedPostOp.this.i++;
+        }
+        private Integer testParent() {
+            return QualBoxedPostOp.super.i++;
+        }
+    }
+}
+
+class Parent {
+    protected Integer i=10;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/boxing/QualBoxedPostOp2.java	Mon Dec 12 13:27:39 2016 +0100
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8147527
+ * @summary Qualified "super" boxed unary post-operation using a type variable.
+ * @compile QualBoxedPostOp2.java
+ * @run main QualBoxedPostOp2
+ */
+public class QualBoxedPostOp2<T> extends Parent2<Integer> {
+    public static void main(String[] args) {
+        new QualBoxedPostOp2().testAll();
+    }
+
+    private void testAll() {
+        super.i = 10;
+
+        equals(new Inner().testParent(), 10);
+        equals(super.i, 11);
+    }
+
+    private void equals(int a, int b) {
+        if (a != b) throw new Error();
+    }
+
+    T i;
+
+    class Inner {
+        private Integer testParent() {
+            return QualBoxedPostOp2.super.i++;
+        }
+    }
+}
+
+class Parent2<T> {
+    protected T i;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/boxing/QualBoxedPostOp3.java	Mon Dec 12 13:27:39 2016 +0100
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8147527
+ * @summary Verifies the runtime behavior of "super", "this" and "this$n" optimization for boxed unary post-operations.
+ * @compile QualBoxedPostOp3.java QualBoxedPostOp3Parent.java
+ * @run main QualBoxedPostOp3
+ */
+public class QualBoxedPostOp3 extends p.QualBoxedPostOp3Parent {
+    public static void main(String[] args) {
+        new QualBoxedPostOp3().testAll();
+    }
+
+    private void testAll() {
+        equals(test(), 1);
+        equals(i, 2);
+
+        Inner in = new Inner();
+        equals(in.test(), 3);
+        equals(i, 4);
+
+        equals(testParent(), 21);
+        equals(super.j, 22);
+
+        equals(in.testParent(), 23);
+        equals(super.j, 24);
+    }
+
+    private void equals(int a, int b) {
+        if (a != b) throw new Error();
+    }
+
+    Integer i=0;
+
+    private Integer test() {
+        i++;
+        return this.i++;
+    }
+    private Integer testParent() {
+        j++;
+        return super.j++;
+    }
+
+    class Inner {
+        private Integer test() {
+            i++;
+            return QualBoxedPostOp3.this.i++;
+        }
+        private Integer testParent() {
+            j++;
+            return QualBoxedPostOp3.super.j++;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/boxing/QualBoxedPostOp3Parent.java	Mon Dec 12 13:27:39 2016 +0100
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p;
+
+public class QualBoxedPostOp3Parent {
+    protected Integer j=20;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/desugar/BoxingAndSuper.java	Mon Dec 12 13:27:39 2016 +0100
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.code
+ *          jdk.compiler/com.sun.tools.javac.comp
+ *          jdk.compiler/com.sun.tools.javac.tree
+ *          jdk.compiler/com.sun.tools.javac.util
+ */
+
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.JavacTask;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.comp.AttrContext;
+import com.sun.tools.javac.comp.Env;
+import com.sun.tools.javac.comp.Lower;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.JCTree.LetExpr;
+import com.sun.tools.javac.tree.JCTree.Tag;
+import com.sun.tools.javac.tree.TreeCopier;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.tree.TreeScanner;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Log.WriterKind;
+import com.sun.tools.javac.util.Names;
+
+import toolbox.ToolBox;
+
+public class BoxingAndSuper {
+    public static void main(String... args) throws Exception {
+        new BoxingAndSuper().testSuper();
+        new BoxingAndSuper().testThis();
+    }
+
+    public void testSuper() throws Exception {
+        //super, same package:
+        runTest("package p;\n" +
+                "class Test extends Parent {\n" +
+                "     protected Integer i=20;\n" +
+                "     private Integer dump() {\n" +
+                "         return super.i++;\n" +
+                "     }\n" +
+                "}\n" +
+                "---" +
+                "package p;\n" +
+                "class Parent {\n" +
+                "     protected Integer i=10;\n" +
+                "} ",
+                "p.Test.dump()java.lang.Integer\n" +
+                "{\n" +
+                "    return (let /*synthetic*/ final Integer $le0 = (Integer)super.i " +
+                            "in (let /*synthetic*/ final Integer $le1 = super.i = Integer.valueOf((int)(super.i.intValue() + 1)) " +
+                                "in $le0));\n" +
+                "}\n");
+        //qualified super, same package:
+        runTest("package p;\n" +
+                "class Test extends Parent {\n" +
+                "     protected Integer i=20;\n" +
+                "     class Inner {\n" +
+                "         private Integer dump() {\n" +
+                "             return Test.super.i++;\n" +
+                "         }\n" +
+                "     }\n" +
+                "}\n" +
+                "---" +
+                "package p;\n" +
+                "class Parent {\n" +
+                "     protected Integer i=10;\n" +
+                "} ",
+                "p.Test.Inner.dump()java.lang.Integer\n" +
+                "{\n" +
+                "    return (let /*synthetic*/ final Integer $le0 = (Integer)Test.access$001(this$0) " +
+                            "in (let /*synthetic*/ final Integer $le1 = Test.access$103(this$0, Integer.valueOf((int)(Test.access$201(this$0).intValue() + 1))) " +
+                                "in $le0));\n" +
+                "}\n" +
+                "p.Test.access$001(p.Test)java.lang.Integer\n" +
+                "{\n" +
+                "    return x0.i;\n" +
+                "}\n" +
+                "p.Test.access$103(p.Test,java.lang.Integer)java.lang.Integer\n" +
+                "{\n" +
+                "    return x0.i = x1;\n" +
+                "}\n" +
+                "p.Test.access$201(p.Test)java.lang.Integer\n" +
+                "{\n" +
+                "    return x0.i;\n" +
+                "}\n");
+        //super, different packages:
+        runTest("package p1;\n" +
+                "class Test extends p2.Parent {\n" +
+                "     protected Integer i=20;\n" +
+                "     private Integer dump() {\n" +
+                "         return super.i++;\n" +
+                "     }\n" +
+                "}\n" +
+                "---" +
+                "package p2;\n" +
+                "public class Parent {\n" +
+                "     protected Integer i=10;\n" +
+                "} ",
+                "p1.Test.dump()java.lang.Integer\n" +
+                "{\n" +
+                "    return (let /*synthetic*/ final Integer $le0 = (Integer)super.i " +
+                            "in (let /*synthetic*/ final Integer $le1 = super.i = Integer.valueOf((int)(super.i.intValue() + 1)) " +
+                                "in $le0));\n" +
+                "}\n");
+        //qualified super, different packages:
+        runTest("package p1;\n" +
+                "class Test extends p2.Parent {\n" +
+                "     protected Integer i=20;\n" +
+                "     class Inner {\n" +
+                "         private Integer dump() {\n" +
+                "             return Test.super.i++;\n" +
+                "         }\n" +
+                "     }\n" +
+                "}\n" +
+                "---" +
+                "package p2;\n" +
+                "public class Parent {\n" +
+                "     protected Integer i=10;\n" +
+                "} ",
+                "p1.Test.Inner.dump()java.lang.Integer\n" +
+                "{\n" +
+                "    return (let /*synthetic*/ final Integer $le0 = (Integer)Test.access$001(this$0) " +
+                            "in (let /*synthetic*/ final Integer $le1 = Test.access$103(this$0, Integer.valueOf((int)(Test.access$201(this$0).intValue() + 1))) " +
+                                "in $le0));\n" +
+                "}\n" +
+                "p1.Test.access$001(p1.Test)java.lang.Integer\n" +
+                "{\n" +
+                "    return x0.i;\n" +
+                "}\n" +
+                "p1.Test.access$103(p1.Test,java.lang.Integer)java.lang.Integer\n" +
+                "{\n" +
+                "    return x0.i = x1;\n" +
+                "}\n" +
+                "p1.Test.access$201(p1.Test)java.lang.Integer\n" +
+                "{\n" +
+                "    return x0.i;\n" +
+                "}\n");
+    }
+
+    public void testThis() throws Exception {
+        String code = "public class Test {\n" +
+                      "    Integer i;\n" +
+                      "    private void dump() {\n" +
+                      "        i++;\n" +
+                      "        this.i++;\n" +
+                      "    }\n" +
+                      "}";
+        String expected =
+                "Test.dump()void\n" +
+                "{\n" +
+                "    (let /*synthetic*/ final Integer $le0 = i in (let /*synthetic*/ final Integer $le1 = i = Integer.valueOf((int)(i.intValue() + 1)) in $le0));\n" +
+                "    (let /*synthetic*/ final Integer $le2 = (Integer)this.i in (let /*synthetic*/ final Integer $le3 = this.i = Integer.valueOf((int)(this.i.intValue() + 1)) in $le2));\n" +
+                "}\n";
+        runTest(code, expected);
+        //qualified this:
+        runTest("public class Test {\n" +
+                "   Integer i;\n" +
+                "   class Inner1 {\n" +
+                "       class Inner2 {\n" +
+                "           private Integer dump() {\n" +
+                "               return Test.this.i++;\n" +
+                "           }\n" +
+                "       }\n" +
+                "   }\n" +
+                "}",
+                "Test.Inner1.Inner2.dump()java.lang.Integer\n" +
+                "{\n" +
+                "    return (let /*synthetic*/ final Integer $le0 = (Integer)this$1.this$0.i" +
+                           " in (let /*synthetic*/ final Integer $le1 = this$1.this$0.i = " +
+                                "Integer.valueOf((int)(this$1.this$0.i.intValue() + 1)) " +
+                                "in $le0));\n" +
+                "}\n"
+        );
+    }
+
+    private final ToolBox tb = new ToolBox();
+
+    private void runTest(String code, String expectedDesugar) throws Exception {
+        List<JavaFileObject> files = List.nil();
+
+        for (String file : code.split("---")) {
+            files = files.prepend(new ToolBox.JavaSource(file));
+        }
+
+        Path classes = Paths.get("classes");
+
+        if (Files.exists(classes)) {
+            tb.cleanDirectory(classes);
+        } else {
+            Files.createDirectories(classes);
+        }
+
+        JavacTool compiler = (JavacTool) ToolProvider.getSystemJavaCompiler();
+        StringWriter out = new StringWriter();
+        Context context = new Context();
+        TestLower.preRegister(context);
+        Iterable<String> options = Arrays.asList("-d", classes.toString());
+        JavacTask task = (JavacTask) compiler.getTask(out, null, null, options, null, files, context);
+
+        task.generate();
+
+        out.flush();
+
+        String actual = out.toString().replace(System.getProperty("line.separator"), "\n");
+
+        if (!expectedDesugar.equals(actual)) {
+            throw new IllegalStateException("Actual does not match expected: " + actual);
+        }
+    }
+
+    private static final class TestLower extends Lower {
+
+        public static void preRegister(Context context) {
+            context.put(lowerKey, new Context.Factory<Lower>() {
+                   public Lower make(Context c) {
+                       return new TestLower(c);
+                   }
+            });
+        }
+
+        private final TreeMaker make;
+        private final Names names;
+        private final Log log;
+
+        public TestLower(Context context) {
+            super(context);
+            make = TreeMaker.instance(context);
+            names = Names.instance(context);
+            log = Log.instance(context);
+        }
+
+        @Override
+        public List<JCTree> translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
+            List<JCTree> result = super.translateTopLevelClass(env, cdef, make);
+            Map<Symbol, JCMethodDecl> declarations = new HashMap<>();
+            Set<Symbol> toDump = new TreeSet<>(symbolComparator);
+
+            new TreeScanner() {
+                @Override
+                public void visitMethodDef(JCMethodDecl tree) {
+                    if (tree.name.toString().startsWith("dump")) {
+                        toDump.add(tree.sym);
+                    }
+                    declarations.put(tree.sym, tree);
+                    super.visitMethodDef(tree);
+                }
+            }.scan(result);
+
+            for (Symbol d : toDump) {
+                dump(d, declarations, new HashSet<>());
+            }
+
+            return result;
+        }
+
+        private void dump(Symbol methodSym, Map<Symbol, JCMethodDecl> declarations, Set<Symbol> alreadyPrinted) {
+            if (!alreadyPrinted.add(methodSym))
+                return ;
+
+            JCMethodDecl method = declarations.get(methodSym);
+
+            if (method == null) {
+                return ;
+            }
+
+            log.getWriter(WriterKind.NOTICE).println(symbol2String(methodSym));
+
+            JCBlock body = new TreeCopier<Void>(make) {
+                private final Map<String, String> letExprRemap = new HashMap<>();
+                private int i;
+
+                @Override
+                public JCTree visitOther(Tree node, Void p) {
+                    JCTree tree = (JCTree) node;
+                    if (tree.hasTag(Tag.LETEXPR)) {
+                        LetExpr le = (LetExpr) tree;
+
+                        for (JCVariableDecl var : le.defs) {
+                            letExprRemap.put(var.name.toString(), "$le" + i++);
+                        }
+                    }
+                    return super.visitOther(node, p);
+                }
+
+                @Override
+                public JCTree visitVariable(VariableTree node, Void p) {
+                    String newName = letExprRemap.get(node.getName().toString());
+                    if (newName != null) {
+                        node = make.VarDef((JCModifiers) node.getModifiers(), names.fromString(newName), (JCExpression) node.getType(), (JCExpression) node.getInitializer());
+                    }
+                    return super.visitVariable(node, p);
+                }
+
+                @Override
+                public JCTree visitIdentifier(IdentifierTree node, Void p) {
+                    String newName = letExprRemap.get(node.getName().toString());
+                    if (newName != null) {
+                        node = make.Ident(names.fromString(newName));
+                    }
+                    return super.visitIdentifier(node, p);
+                }
+
+                @Override
+                public <T extends JCTree> T copy(T tree, Void p) {
+                    if (tree.hasTag(Tag.LETEXPR)) {
+                        return (T) visitOther(tree, p);
+                    }
+                    return super.copy(tree, p);
+                }
+
+            }.copy(method.body);
+            log.getWriter(WriterKind.NOTICE).println(body.toString());
+
+            Set<Symbol> invoked = new TreeSet<>(symbolComparator);
+
+            new TreeScanner() {
+                @Override
+                public void visitApply(JCMethodInvocation tree) {
+                    invoked.add(TreeInfo.symbol(tree.meth));
+                    super.visitApply(tree);
+                }
+            }.scan(method);
+
+            for (Symbol search : invoked) {
+                dump(search, declarations, alreadyPrinted);
+            }
+        }
+
+        private String symbol2String(Symbol sym) {
+            switch (sym.kind) {
+                case TYP:
+                    return sym.getQualifiedName().toString();
+                case MTH:
+                    return symbol2String(sym.owner) + "." + sym.name + sym.type.toString();
+                default:
+                    throw new UnsupportedOperationException();
+            }
+        }
+
+        private final Comparator<Symbol> symbolComparator = (s1, s2) -> symbol2String(s1).compareTo(symbol2String(s2));
+    }
+
+}