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