1 /* 2 * Copyright (c) 2016, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @library /tools/lib 27 * @modules jdk.compiler/com.sun.tools.javac.api 28 * jdk.compiler/com.sun.tools.javac.code 29 * jdk.compiler/com.sun.tools.javac.comp 30 * jdk.compiler/com.sun.tools.javac.tree 31 * jdk.compiler/com.sun.tools.javac.util 32 */ 33 34 import java.io.StringWriter; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.util.Arrays; 39 import java.util.Comparator; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.TreeSet; 45 46 import javax.tools.JavaFileObject; 47 import javax.tools.ToolProvider; 48 49 import com.sun.source.tree.IdentifierTree; 50 import com.sun.source.tree.Tree; 51 import com.sun.source.tree.VariableTree; 52 import com.sun.source.util.JavacTask; 53 import com.sun.tools.javac.api.JavacTool; 54 import com.sun.tools.javac.code.Symbol; 55 import com.sun.tools.javac.comp.AttrContext; 56 import com.sun.tools.javac.comp.Env; 57 import com.sun.tools.javac.comp.Lower; 58 import com.sun.tools.javac.tree.JCTree; 59 import com.sun.tools.javac.tree.JCTree.JCBlock; 60 import com.sun.tools.javac.tree.JCTree.JCExpression; 61 import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 62 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; 63 import com.sun.tools.javac.tree.JCTree.JCModifiers; 64 import com.sun.tools.javac.tree.JCTree.JCStatement; 65 import com.sun.tools.javac.tree.JCTree.JCVariableDecl; 66 import com.sun.tools.javac.tree.JCTree.LetExpr; 67 import com.sun.tools.javac.tree.JCTree.Tag; 68 import com.sun.tools.javac.tree.TreeCopier; 69 import com.sun.tools.javac.tree.TreeInfo; 70 import com.sun.tools.javac.tree.TreeMaker; 71 import com.sun.tools.javac.tree.TreeScanner; 72 import com.sun.tools.javac.util.Context; 73 import com.sun.tools.javac.util.List; 74 import com.sun.tools.javac.util.Log; 75 import com.sun.tools.javac.util.Log.WriterKind; 76 import com.sun.tools.javac.util.Names; 77 78 import toolbox.ToolBox; 79 80 public class BoxingAndSuper { 81 public static void main(String... args) throws Exception { 82 new BoxingAndSuper().testSuper(); 83 new BoxingAndSuper().testThis(); 84 } 85 86 public void testSuper() throws Exception { 87 //super, same package: 88 runTest("package p;\n" + 89 "class Test extends Parent {\n" + 90 " protected Integer i=20;\n" + 91 " private Integer dump() {\n" + 92 " return super.i++;\n" + 93 " }\n" + 94 "}\n" + 95 "---" + 96 "package p;\n" + 97 "class Parent {\n" + 98 " protected Integer i=10;\n" + 99 "} ", 100 "p.Test.dump()java.lang.Integer\n" + 101 "{\n" + 102 " return (let /*synthetic*/ final Integer $le0 = (Integer)super.i " + 103 "in (let /*synthetic*/ final Integer $le1 = super.i = Integer.valueOf((int)(super.i.intValue() + 1)) " + 104 "in $le0));\n" + 105 "}\n"); 106 //qualified super, same package: 107 runTest("package p;\n" + 108 "class Test extends Parent {\n" + 109 " protected Integer i=20;\n" + 110 " class Inner {\n" + 111 " private Integer dump() {\n" + 112 " return Test.super.i++;\n" + 113 " }\n" + 114 " }\n" + 115 "}\n" + 116 "---" + 117 "package p;\n" + 118 "class Parent {\n" + 119 " protected Integer i=10;\n" + 120 "} ", 121 "p.Test.Inner.dump()java.lang.Integer\n" + 122 "{\n" + 123 " return (let /*synthetic*/ final Integer $le0 = (Integer)Test.access$001(this$0) " + 124 "in (let /*synthetic*/ final Integer $le1 = Test.access$103(this$0, Integer.valueOf((int)(Test.access$201(this$0).intValue() + 1))) " + 125 "in $le0));\n" + 126 "}\n" + 127 "p.Test.access$001(p.Test)java.lang.Integer\n" + 128 "{\n" + 129 " return x0.i;\n" + 130 "}\n" + 131 "p.Test.access$103(p.Test,java.lang.Integer)java.lang.Integer\n" + 132 "{\n" + 133 " return x0.i = x1;\n" + 134 "}\n" + 135 "p.Test.access$201(p.Test)java.lang.Integer\n" + 136 "{\n" + 137 " return x0.i;\n" + 138 "}\n"); 139 //super, different packages: 140 runTest("package p1;\n" + 141 "class Test extends p2.Parent {\n" + 142 " protected Integer i=20;\n" + 143 " private Integer dump() {\n" + 144 " return super.i++;\n" + 145 " }\n" + 146 "}\n" + 147 "---" + 148 "package p2;\n" + 149 "public class Parent {\n" + 150 " protected Integer i=10;\n" + 151 "} ", 152 "p1.Test.dump()java.lang.Integer\n" + 153 "{\n" + 154 " return (let /*synthetic*/ final Integer $le0 = (Integer)super.i " + 155 "in (let /*synthetic*/ final Integer $le1 = super.i = Integer.valueOf((int)(super.i.intValue() + 1)) " + 156 "in $le0));\n" + 157 "}\n"); 158 //qualified super, different packages: 159 runTest("package p1;\n" + 160 "class Test extends p2.Parent {\n" + 161 " protected Integer i=20;\n" + 162 " class Inner {\n" + 163 " private Integer dump() {\n" + 164 " return Test.super.i++;\n" + 165 " }\n" + 166 " }\n" + 167 "}\n" + 168 "---" + 169 "package p2;\n" + 170 "public class Parent {\n" + 171 " protected Integer i=10;\n" + 172 "} ", 173 "p1.Test.Inner.dump()java.lang.Integer\n" + 174 "{\n" + 175 " return (let /*synthetic*/ final Integer $le0 = (Integer)Test.access$001(this$0) " + 176 "in (let /*synthetic*/ final Integer $le1 = Test.access$103(this$0, Integer.valueOf((int)(Test.access$201(this$0).intValue() + 1))) " + 177 "in $le0));\n" + 178 "}\n" + 179 "p1.Test.access$001(p1.Test)java.lang.Integer\n" + 180 "{\n" + 181 " return x0.i;\n" + 182 "}\n" + 183 "p1.Test.access$103(p1.Test,java.lang.Integer)java.lang.Integer\n" + 184 "{\n" + 185 " return x0.i = x1;\n" + 186 "}\n" + 187 "p1.Test.access$201(p1.Test)java.lang.Integer\n" + 188 "{\n" + 189 " return x0.i;\n" + 190 "}\n"); 191 } 192 193 public void testThis() throws Exception { 194 String code = "public class Test {\n" + 195 " Integer i;\n" + 196 " private void dump() {\n" + 197 " i++;\n" + 198 " this.i++;\n" + 199 " }\n" + 200 "}"; 201 String expected = 202 "Test.dump()void\n" + 203 "{\n" + 204 " (let /*synthetic*/ final Integer $le0 = i in (let /*synthetic*/ final Integer $le1 = i = Integer.valueOf((int)(i.intValue() + 1)) in $le0));\n" + 205 " (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" + 206 "}\n"; 207 runTest(code, expected); 208 //qualified this: 209 runTest("public class Test {\n" + 210 " Integer i;\n" + 211 " class Inner1 {\n" + 212 " class Inner2 {\n" + 213 " private Integer dump() {\n" + 214 " return Test.this.i++;\n" + 215 " }\n" + 216 " }\n" + 217 " }\n" + 218 "}", 219 "Test.Inner1.Inner2.dump()java.lang.Integer\n" + 220 "{\n" + 221 " return (let /*synthetic*/ final Integer $le0 = (Integer)this$1.this$0.i" + 222 " in (let /*synthetic*/ final Integer $le1 = this$1.this$0.i = " + 223 "Integer.valueOf((int)(this$1.this$0.i.intValue() + 1)) " + 224 "in $le0));\n" + 225 "}\n" 226 ); 227 } 228 229 private final ToolBox tb = new ToolBox(); 230 231 private void runTest(String code, String expectedDesugar) throws Exception { 232 List<JavaFileObject> files = List.nil(); 233 234 for (String file : code.split("---")) { 235 files = files.prepend(new ToolBox.JavaSource(file)); 236 } 237 238 Path classes = Paths.get("classes"); 239 240 if (Files.exists(classes)) { 241 tb.cleanDirectory(classes); 242 } else { 243 Files.createDirectories(classes); 244 } 245 246 JavacTool compiler = (JavacTool) ToolProvider.getSystemJavaCompiler(); 247 StringWriter out = new StringWriter(); 248 Context context = new Context(); 249 TestLower.preRegister(context); 250 Iterable<String> options = Arrays.asList("-d", classes.toString()); 251 JavacTask task = (JavacTask) compiler.getTask(out, null, null, options, null, files, context); 252 253 task.generate(); 254 255 out.flush(); 256 257 String actual = out.toString().replace(System.getProperty("line.separator"), "\n"); 258 259 if (!expectedDesugar.equals(actual)) { 260 throw new IllegalStateException("Actual does not match expected: " + actual); 261 } 262 } 263 264 private static final class TestLower extends Lower { 265 266 public static void preRegister(Context context) { 267 context.put(lowerKey, new Context.Factory<Lower>() { 268 public Lower make(Context c) { 269 return new TestLower(c); 270 } 271 }); 272 } 273 274 private final TreeMaker make; 275 private final Names names; 276 private final Log log; 277 278 public TestLower(Context context) { 279 super(context); 280 make = TreeMaker.instance(context); 281 names = Names.instance(context); 282 log = Log.instance(context); 283 } 284 285 @Override 286 public List<JCTree> translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) { 287 List<JCTree> result = super.translateTopLevelClass(env, cdef, make); 288 Map<Symbol, JCMethodDecl> declarations = new HashMap<>(); 289 Set<Symbol> toDump = new TreeSet<>(symbolComparator); 290 291 new TreeScanner() { 292 @Override 293 public void visitMethodDef(JCMethodDecl tree) { 294 if (tree.name.toString().startsWith("dump")) { 295 toDump.add(tree.sym); 296 } 297 declarations.put(tree.sym, tree); 298 super.visitMethodDef(tree); 299 } 300 }.scan(result); 301 302 for (Symbol d : toDump) { 303 dump(d, declarations, new HashSet<>()); 304 } 305 306 return result; 307 } 308 309 private void dump(Symbol methodSym, Map<Symbol, JCMethodDecl> declarations, Set<Symbol> alreadyPrinted) { 310 if (!alreadyPrinted.add(methodSym)) 311 return ; 312 313 JCMethodDecl method = declarations.get(methodSym); 314 315 if (method == null) { 316 return ; 317 } 318 319 log.getWriter(WriterKind.NOTICE).println(symbol2String(methodSym)); 320 321 JCBlock body = new TreeCopier<Void>(make) { 322 private final Map<String, String> letExprRemap = new HashMap<>(); 323 private int i; 324 325 @Override 326 public JCTree visitOther(Tree node, Void p) { 327 JCTree tree = (JCTree) node; 328 if (tree.hasTag(Tag.LETEXPR)) { 329 LetExpr le = (LetExpr) tree; 330 331 for (JCStatement var : le.defs) { 332 letExprRemap.put(((JCVariableDecl) var).name.toString(), "$le" + i++); 333 } 334 } 335 return super.visitOther(node, p); 336 } 337 338 @Override 339 public JCTree visitVariable(VariableTree node, Void p) { 340 String newName = letExprRemap.get(node.getName().toString()); 341 if (newName != null) { 342 node = make.VarDef((JCModifiers) node.getModifiers(), names.fromString(newName), (JCExpression) node.getType(), (JCExpression) node.getInitializer()); 343 } 344 return super.visitVariable(node, p); 345 } 346 347 @Override 348 public JCTree visitIdentifier(IdentifierTree node, Void p) { 349 String newName = letExprRemap.get(node.getName().toString()); 350 if (newName != null) { 351 node = make.Ident(names.fromString(newName)); 352 } 353 return super.visitIdentifier(node, p); 354 } 355 356 @Override 357 public <T extends JCTree> T copy(T tree, Void p) { 358 if (tree.hasTag(Tag.LETEXPR)) { 359 return (T) visitOther(tree, p); 360 } 361 return super.copy(tree, p); 362 } 363 364 }.copy(method.body); 365 log.getWriter(WriterKind.NOTICE).println(body.toString()); 366 367 Set<Symbol> invoked = new TreeSet<>(symbolComparator); 368 369 new TreeScanner() { 370 @Override 371 public void visitApply(JCMethodInvocation tree) { 372 invoked.add(TreeInfo.symbol(tree.meth)); 373 super.visitApply(tree); 374 } 375 }.scan(method); 376 377 for (Symbol search : invoked) { 378 dump(search, declarations, alreadyPrinted); 379 } 380 } 381 382 private String symbol2String(Symbol sym) { 383 switch (sym.kind) { 384 case TYP: 385 return sym.getQualifiedName().toString(); 386 case MTH: 387 return symbol2String(sym.owner) + "." + sym.name + sym.type.toString(); 388 default: 389 throw new UnsupportedOperationException(); 390 } 391 } 392 393 private final Comparator<Symbol> symbolComparator = (s1, s2) -> symbol2String(s1).compareTo(symbol2String(s2)); 394 } 395 396 }