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