1 /* 2 * Copyright (c) 2018, 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 26 package com.sun.tools.javac.jvm; 27 28 import com.sun.source.tree.NewClassTree.CreationMode; 29 import com.sun.tools.javac.code.Flags; 30 import com.sun.tools.javac.code.Scope.LookupKind; 31 import com.sun.tools.javac.code.Scope.WriteableScope; 32 import com.sun.tools.javac.code.Symbol; 33 import com.sun.tools.javac.code.Symbol.MethodSymbol; 34 import com.sun.tools.javac.code.Symbol.VarSymbol; 35 import com.sun.tools.javac.code.Symtab; 36 import com.sun.tools.javac.code.Type.MethodType; 37 import com.sun.tools.javac.code.Types; 38 import com.sun.tools.javac.tree.JCTree; 39 import com.sun.tools.javac.tree.JCTree.JCAssign; 40 import com.sun.tools.javac.tree.JCTree.JCClassDecl; 41 import com.sun.tools.javac.tree.JCTree.JCExpression; 42 import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; 43 import com.sun.tools.javac.tree.JCTree.JCFieldAccess; 44 import com.sun.tools.javac.tree.JCTree.JCIdent; 45 import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 46 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; 47 import com.sun.tools.javac.tree.JCTree.JCNewClass; 48 import com.sun.tools.javac.tree.JCTree.JCReturn; 49 import com.sun.tools.javac.tree.JCTree.JCStatement; 50 import com.sun.tools.javac.tree.TreeInfo; 51 import com.sun.tools.javac.tree.TreeMaker; 52 import com.sun.tools.javac.tree.TreeTranslator; 53 import com.sun.tools.javac.util.Assert; 54 import com.sun.tools.javac.util.Context; 55 import com.sun.tools.javac.util.List; 56 import com.sun.tools.javac.util.Name; 57 import com.sun.tools.javac.util.Names; 58 59 import java.util.HashMap; 60 import java.util.Map; 61 62 import static com.sun.tools.javac.code.Kinds.Kind.MTH; 63 import static com.sun.tools.javac.code.Kinds.Kind.VAR; 64 import static com.sun.tools.javac.tree.JCTree.Tag.APPLY; 65 import static com.sun.tools.javac.tree.JCTree.Tag.EXEC; 66 import static com.sun.tools.javac.tree.JCTree.Tag.IDENT; 67 68 /** 69 * This pass translates value constructors into static factory methods and patches up constructor 70 * calls to become invocations of those static factory methods. 71 * 72 * We get commissioned as a subpass of Gen. Constructor tress undergo plenty of change in Lower 73 * (enclosing instance injection, captured locals ...) and in Gen (instance field initialization, 74 * see normalizeDefs) and so it is most effective to wait until things reach a quiescent state 75 * before undertaking the tinkering that we do. 76 * 77 * See https://bugs.openjdk.java.net/browse/JDK-8198749 for the kind of transformations we do. 78 * 79 */ 80 public class TransValues extends TreeTranslator { 81 82 protected static final Context.Key<TransValues> transValuesKey = new Context.Key<>(); 83 84 private Symtab syms; 85 private TreeMaker make; 86 private Types types; 87 private Names names; 88 89 // class currently undergoing translation. 90 private JCClassDecl currentClass; 91 92 // method currently undergoing translation. 93 private JCMethodDecl currentMethod; 94 95 // list of factories synthesized so far. 96 private List<JCTree> staticFactories; 97 98 // Map from constructor symbols to factory symbols. 99 private Map<MethodSymbol, MethodSymbol> init2factory = new HashMap<>(); 100 101 public static TransValues instance(Context context) { 102 TransValues instance = context.get(transValuesKey); 103 if (instance == null) 104 instance = new TransValues(context); 105 return instance; 106 } 107 108 protected TransValues(Context context) { 109 context.put(transValuesKey, this); 110 syms = Symtab.instance(context); 111 make = TreeMaker.instance(context); 112 types = Types.instance(context); 113 names = Names.instance(context); 114 } 115 116 public JCClassDecl translateTopLevelClass(JCClassDecl classDecl, TreeMaker make) { 117 try { 118 this.make = make; 119 translate(classDecl); 120 } finally { 121 // note that recursive invocations of this method fail hard 122 this.make = null; 123 } 124 init2factory = new HashMap<>(); 125 return classDecl; 126 } 127 128 @Override 129 public void visitClassDef(JCClassDecl classDecl) { 130 JCClassDecl previousClass = currentClass; 131 List<JCTree> previousFactories = staticFactories; 132 staticFactories = List.nil(); 133 currentClass = classDecl; 134 try { 135 super.visitClassDef(classDecl); 136 classDecl.defs = classDecl.defs.appendList(staticFactories); 137 staticFactories = List.nil(); 138 } 139 finally { 140 currentClass = previousClass; 141 staticFactories = previousFactories; 142 } 143 } 144 145 @Override 146 public void visitMethodDef(JCMethodDecl tree) { 147 JCMethodDecl previousMethod = currentMethod; 148 currentMethod = tree; 149 try { 150 if (translatingValueConstructor()) { 151 152 /* Mutate this value constructor into an equivalent static value factory, leaving in place 153 a dummy constructor. (A placeholder constructor is still required so that Attr and 154 other earlier pipeline stages will see the required constructors to bind any reference 155 style instantiations to.) 156 157 The verifier ensures that the `new' bytecode can never be used with a value class, so 158 constructor body can be essentially wiped out except for a call that chains to super 159 constructor. (The latter is mandated by the verifier.) 160 */ 161 162 make.at(tree.pos()); 163 JCExpressionStatement exec = chainedConstructorCall(tree); 164 Assert.check(exec != null && TreeInfo.isSelfCall(exec)); 165 JCMethodInvocation call = (JCMethodInvocation) exec.expr; 166 167 /* Unlike the reference construction sequence where `this' is allocated ahead of time and 168 is passed as an argument into the <init> method, a value factory must allocate the value 169 instance that forms the `product' by itself. We do that by injecting a prologue here. 170 */ 171 VarSymbol product = currentMethod.factoryProduct = new VarSymbol(0, names.dollarValue, currentClass.sym.type, currentMethod.sym); // TODO: owner needs rewiring 172 JCExpression rhs; 173 174 final Name name = TreeInfo.name(call.meth); 175 MethodSymbol symbol = (MethodSymbol)TreeInfo.symbol(call.meth); 176 if (names._super.equals(name)) { // "initial" constructor. 177 // Synthesize code to allocate factory "product" via: V $this = __MakeDefault V(); 178 Assert.check(symbol.owner == syms.objectType.tsym); 179 Assert.check(symbol.type.getParameterTypes().size() == 0); 180 MethodSymbol ctor = getDefaultConstructor(currentClass.sym); 181 JCNewClass newClass = (JCNewClass) make.Create(ctor, List.nil(), CreationMode.DEFAULT_VALUE); 182 newClass.constructorType = ctor.type; 183 rhs = newClass; 184 } else { 185 // This must be a chained call of form `this(args)'; Mutate it into a factory invocation i.e V $this = V.$makeValue$(args); 186 Assert.check(TreeInfo.name(TreeInfo.firstConstructorCall(tree).meth) == names._this); 187 MethodSymbol factory = getValueFactory(symbol); 188 final JCIdent ident = make.Ident(factory); 189 rhs = make.App(ident, call.args); 190 ((JCMethodInvocation)rhs).varargsElement = call.varargsElement; 191 } 192 193 /* The value product allocation prologue must precede any synthetic inits !!! 194 as these may reference `this' which gets pre-allocated for references but 195 not for values. 196 */ 197 JCStatement prologue = make.VarDef(product, rhs); 198 tree.body.stats = tree.body.stats.prepend(prologue).diff(List.of(exec)); 199 tree.body = translate(tree.body); 200 201 /* We may need an epilogue that returns the value product, but we can't eagerly insert 202 a return here, since we don't know much about control flow here. Gen#genMethod 203 will insert a return of the factory product if control does reach the end and would 204 "fall off the cliff" otherwise. 205 */ 206 207 /* Create a factory method declaration and pass on ownership of the translated ctor body 208 to it, wiping out the ctor body itself. 209 */ 210 MethodSymbol factorySym = getValueFactory(tree.sym); 211 JCMethodDecl factoryMethod = make.MethodDef(make.Modifiers(tree.mods.flags | Flags.SYNTHETIC | Flags.STATIC, tree.mods.annotations), 212 factorySym.name, 213 make.Type(factorySym.type.getReturnType()), 214 tree.typarams, 215 null, 216 tree.params, 217 tree.thrown, 218 tree.body, 219 null, 220 factorySym); 221 factoryMethod.setType(factorySym.type); 222 factoryMethod.factoryProduct = product; 223 staticFactories = staticFactories.append(factoryMethod); 224 225 // wipe out the body of the ctor and insert just a super call. 226 MethodSymbol jlOCtor = getDefaultConstructor(syms.objectType.tsym); 227 JCExpression meth = make.Ident(names._super).setType(jlOCtor.type); 228 TreeInfo.setSymbol(meth, jlOCtor); 229 230 final JCExpressionStatement superCall = make.Exec(make.Apply(null, meth, List.nil()).setType(syms.voidType)); 231 tree.body = make.at(tree.body).Block(0, List.of(superCall)); 232 result = tree; 233 return; 234 } 235 super.visitMethodDef(tree); 236 } finally { 237 currentMethod = previousMethod; 238 } 239 } 240 241 @Override 242 public void visitReturn(JCReturn tree) { 243 if (translatingValueConstructor()) { 244 result = make.Return(make.Ident(currentMethod.factoryProduct)); 245 } else { 246 super.visitReturn(tree); 247 } 248 } 249 250 /* Note: 1. Assignop does not call for any translation, since value instance fields are final and 251 so cannot be AssignedOped. 2. Any redundantly qualified this would have been lowered already. 252 */ 253 @Override 254 public void visitAssign(JCAssign tree) { 255 if (translatingValueConstructor()) { 256 Symbol symbol = null; 257 switch(tree.lhs.getTag()) { 258 case IDENT: 259 symbol = ((JCIdent)tree.lhs).sym; 260 break; 261 case SELECT: 262 JCFieldAccess fieldAccess = (JCFieldAccess) tree.lhs; 263 if (fieldAccess.selected.hasTag(IDENT) && ((JCIdent)fieldAccess.selected).name == names._this) { 264 symbol = fieldAccess.sym; 265 } 266 break; 267 default: 268 break; 269 } 270 if (isInstanceFieldAccess(symbol)) { 271 final JCIdent facHandle = make.Ident(currentMethod.factoryProduct); 272 result = make.Assign(facHandle, make.WithField(make.Select(facHandle, symbol), translate(tree.rhs)).setType(currentClass.type)).setType(currentClass.type); 273 return; 274 } 275 } 276 super.visitAssign(tree); 277 } 278 279 @Override 280 public void visitIdent(JCIdent ident) { 281 if (translatingValueConstructor()) { 282 Symbol symbol = ident.sym; 283 if (isInstanceFieldAccess(symbol)) { 284 final JCIdent facHandle = make.Ident(currentMethod.factoryProduct); 285 result = make.Select(facHandle, symbol); 286 return; 287 } 288 } 289 super.visitIdent(ident); 290 } 291 292 @Override 293 public void visitSelect(JCFieldAccess fieldAccess) { 294 if (translatingValueConstructor()) { // Qualified this would have been lowered already. 295 if (fieldAccess.selected.hasTag(IDENT) && ((JCIdent)fieldAccess.selected).name == names._this) { 296 Symbol symbol = fieldAccess.sym; 297 if (isInstanceFieldAccess(symbol)) { 298 final JCIdent facHandle = make.Ident(currentMethod.factoryProduct); 299 result = make.Select(facHandle, symbol); 300 return; 301 } 302 } 303 } 304 super.visitSelect(fieldAccess); 305 } 306 307 // Translate a reference style instance creation attempt on a value type to a static factory call. 308 @Override 309 public void visitNewClass(JCNewClass tree) { 310 if (tree.creationMode == CreationMode.NEW && types.isValue(tree.clazz.type)) { 311 tree.encl = translate(tree.encl); 312 tree.args = translate(tree.args); 313 Assert.check(tree.def == null); 314 MethodSymbol sFactory = getValueFactory((MethodSymbol) tree.constructor); 315 make.at(tree.pos()); 316 JCExpression meth = tree.encl == null ? make.Ident(sFactory): make.Select(tree.encl, sFactory); // TODO: tree.encl must have been folded already into a synth argument and nullified 317 meth.type = types.erasure(meth.type); 318 final JCMethodInvocation apply = make.Apply(tree.typeargs, meth, tree.args); 319 apply.varargsElement = tree.varargsElement; 320 apply.type = meth.type.getReturnType(); 321 result = apply; 322 return; 323 } 324 super.visitNewClass(tree); 325 } 326 327 // Utility methods ... 328 private boolean translatingValueConstructor() { 329 return currentClass != null && (currentClass.sym.flags() & Flags.VALUE) != 0 && currentMethod != null && currentMethod.sym.isConstructor(); 330 } 331 332 private boolean isInstanceFieldAccess(Symbol symbol) { 333 return symbol != null && symbol.kind == VAR && symbol.owner == currentClass.sym && !symbol.isStatic(); 334 } 335 336 private MethodSymbol getValueFactory(MethodSymbol init) { 337 Assert.check(init.name.equals(names.init)); 338 Assert.check(types.isValue(init.owner.type)); 339 MethodSymbol factory = init2factory.get(init); 340 if (factory != null) 341 return factory; 342 343 final WriteableScope classScope = init.owner.members(); 344 Assert.check(classScope.includes(init, LookupKind.NON_RECURSIVE)); 345 346 MethodType factoryType = new MethodType(init.externalType(types).getParameterTypes(), // init.externalType to account for synthetics. 347 init.owner.type, 348 init.type.getThrownTypes(), 349 init.owner.type.tsym); 350 factory = new MethodSymbol(init.flags_field | Flags.STATIC | Flags.SYNTHETIC, 351 names.makeValue, 352 factoryType, 353 init.owner); 354 factory.setAttributes(init); 355 classScope.enter(factory); 356 init2factory.put(init, factory); 357 return factory; 358 } 359 360 /** Return the *statement* in the constructor that `chains' to another constructor call either 361 * in the same class or its superclass. One MUST exist except for jlO, though may be buried 362 * under synthetic initializations. 363 */ 364 private JCExpressionStatement chainedConstructorCall(JCMethodDecl md) { 365 if (md.name == names.init && md.body != null) { 366 for (JCStatement statement : md.body.stats) { 367 if (statement.hasTag(EXEC)) { 368 JCExpressionStatement exec = (JCExpressionStatement)statement; 369 if (exec.expr.hasTag(APPLY)) { 370 JCMethodInvocation apply = (JCMethodInvocation)exec.expr; 371 Name name = TreeInfo.name(apply.meth); 372 if (name == names._super || name == names._this) 373 return exec; 374 } 375 } 376 } 377 } 378 return null; 379 } 380 381 private MethodSymbol getDefaultConstructor(Symbol klass) { 382 for (Symbol method : klass.members().getSymbolsByName(names.init, s->s.kind == MTH && s.type.getParameterTypes().size() == 0, LookupKind.NON_RECURSIVE)) { 383 return (MethodSymbol) method; 384 } 385 // class defines a non-nullary but no nullary constructor, fabricate a symbol. 386 MethodType dctorType = new MethodType(List.nil(), 387 klass.type, 388 List.nil(), 389 klass.type.tsym); 390 return new MethodSymbol(Flags.PUBLIC, 391 names.init, 392 dctorType, 393 klass); 394 } 395 }