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 }