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