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