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 }