1 /*
  2  * Copyright (c) 2016, 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 jdk.jshell;
 27 
 28 import java.util.EnumSet;
 29 import java.util.HashMap;
 30 import java.util.HashSet;
 31 import java.util.Map;
 32 import java.util.Set;
 33 import java.util.function.Function;
 34 import javax.lang.model.element.Element;
 35 import javax.lang.model.element.ElementKind;
 36 import javax.lang.model.element.VariableElement;
 37 import com.sun.source.tree.ReturnTree;
 38 import com.sun.source.tree.ClassTree;
 39 import com.sun.source.tree.CompilationUnitTree;
 40 import com.sun.source.tree.ConditionalExpressionTree;
 41 import com.sun.source.tree.ExpressionTree;
 42 import com.sun.source.tree.IdentifierTree;
 43 import com.sun.source.tree.MethodInvocationTree;
 44 import com.sun.source.tree.MethodTree;
 45 import com.sun.source.tree.NewClassTree;
 46 import com.sun.source.tree.Tree;
 47 import com.sun.source.tree.Tree.Kind;
 48 import com.sun.source.tree.VariableTree;
 49 import com.sun.source.util.TreePath;
 50 import com.sun.source.util.TreePathScanner;
 51 import com.sun.source.util.TreeScanner;
 52 import com.sun.tools.javac.code.Flags;
 53 import com.sun.tools.javac.code.Symbol;
 54 import com.sun.tools.javac.code.Symbol.TypeSymbol;
 55 import com.sun.tools.javac.code.Symtab;
 56 import com.sun.tools.javac.code.Type;
 57 import com.sun.tools.javac.code.Types;
 58 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
 59 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
 60 import com.sun.tools.javac.tree.TreeInfo;
 61 import com.sun.tools.javac.util.List;
 62 import com.sun.tools.javac.util.ListBuffer;
 63 import java.util.function.BinaryOperator;
 64 import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription;
 65 import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription.VariableDesc;
 66 import jdk.jshell.TaskFactory.AnalyzeTask;
 67 import jdk.jshell.TypePrinter.AnonymousTypeKind;
 68 
 69 /**
 70  * Compute information about an expression string, particularly its type name.
 71  */
 72 class ExpressionToTypeInfo {
 73 
 74     private static final String OBJECT_TYPE_NAME = "Object";
 75 
 76     final AnalyzeTask at;
 77     final CompilationUnitTree cu;
 78     final JShell state;
 79     final boolean computeEnhancedInfo;
 80     final boolean enhancedTypesAccessible;
 81     final Symtab syms;
 82     final Types types;
 83     final Map<TypeSymbol, String> anon2Name = new HashMap<>();
 84 
 85     private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state,
 86                                  boolean computeEnhancedInfo, boolean enhancedTypesAccessible) {
 87         this.at = at;
 88         this.cu = cu;
 89         this.state = state;
 90         this.computeEnhancedInfo = computeEnhancedInfo;
 91         this.enhancedTypesAccessible = enhancedTypesAccessible;
 92         this.syms = Symtab.instance(at.context);
 93         this.types = Types.instance(at.context);
 94     }
 95 
 96     public static class ExpressionInfo {
 97         ExpressionTree tree;
 98         boolean isPrimitiveType;
 99         String typeName;
100         String accessibleTypeName;
101         /* In result of localVariableTypeForInitializer, the type that should be used
102          * as a declaration type of the field. This does not include intersection types,
103          * but does contain references to anonymous types converted to member types.
104          */
105         String declareTypeName;
106         /* In result of localVariableTypeForInitializer, the apparent/infered type of
107          * the variable. This includes intersection types, and references to anonymous
108          * types converted to member types.
109          */
110         String fullTypeName;
111         /* In result of localVariableTypeForInitializer, the human readable type of
112          * the variable. This includes intersection types, and human readable descriptions
113          * of anonymous types.
114          */
115         String displayTypeName;
116         boolean isNonVoid;
117         /* In result of localVariableTypeForInitializer, description of important anonymous
118          * classes.
119          */
120         List<AnonymousDescription> anonymousClasses = List.nil();
121 
122         /* A description of an anonymous class. */
123         static class AnonymousDescription {
124             /* Parameter types of the invoked super constructor.*/
125             List<String> parameterTypes;
126             /* Type of the base/enclosing expression, if any.*/
127             String enclosingInstanceType;
128             /* The denotable name of the supertype.*/
129             String superTypeName;
130             /* The human-readable name of this class.*/
131             String declareTypeName;
132             /* If the supertype of this anonymous is a class. */
133             boolean isClass;
134             /* Variables captured by this anonymous class*/
135             List<VariableDesc> capturedVariables;
136 
137             static class VariableDesc {
138                 String type;
139                 String name;
140 
141                 public VariableDesc(String type, String name) {
142                     this.type = type;
143                     this.name = name;
144                 }
145 
146             }
147         }
148     }
149 
150     // return mechanism and other general structure from TreePath.getPath()
151     private static class Result extends Error {
152 
153         static final long serialVersionUID = -5942088234594905629L;
154         @SuppressWarnings("serial") // Not statically typed as Serializable
155         final TreePath expressionPath;
156 
157         Result(TreePath path) {
158             this.expressionPath = path;
159         }
160     }
161 
162     private static class PathFinder extends TreePathScanner<TreePath, Boolean> {
163 
164         // Optimize out imports etc
165         @Override
166         public TreePath visitCompilationUnit(CompilationUnitTree node, Boolean isTargetContext) {
167             return scan(node.getTypeDecls(), isTargetContext);
168         }
169 
170         // Only care about members
171         @Override
172         public TreePath visitClass(ClassTree node, Boolean isTargetContext) {
173             return scan(node.getMembers(), isTargetContext);
174         }
175 
176         // Only want the doit method where the code is
177         @Override
178         public TreePath visitMethod(MethodTree node, Boolean isTargetContext) {
179             if (Util.isDoIt(node.getName())) {
180                 return scan(node.getBody(), true);
181             } else {
182                 return null;
183             }
184         }
185 
186         @Override
187         public TreePath visitReturn(ReturnTree node, Boolean isTargetContext) {
188             ExpressionTree tree = node.getExpression();
189             TreePath tp = new TreePath(getCurrentPath(), tree);
190             if (isTargetContext) {
191                 throw new Result(tp);
192             } else {
193                 return null;
194             }
195         }
196 
197         @Override
198         public TreePath visitVariable(VariableTree node, Boolean isTargetContext) {
199             if (isTargetContext) {
200                 throw new Result(getCurrentPath());
201             } else {
202                 return null;
203             }
204         }
205 
206     }
207 
208     private Type pathToType(TreePath tp) {
209         return (Type) at.trees().getTypeMirror(tp);
210     }
211 
212     private Type pathToType(TreePath tp, Tree tree) {
213         if (tree instanceof ConditionalExpressionTree) {
214             // Conditionals always wind up as Object -- this corrects
215             ConditionalExpressionTree cet = (ConditionalExpressionTree) tree;
216             Type tmt = pathToType(new TreePath(tp, cet.getTrueExpression()));
217             Type tmf = pathToType(new TreePath(tp, cet.getFalseExpression()));
218             if (!tmt.isPrimitive() && !tmf.isPrimitive()) {
219                 Type lub = types.lub(tmt, tmf);
220                 // System.err.printf("cond ? %s : %s  --  lub = %s\n",
221                 //             varTypeName(tmt), varTypeName(tmf), varTypeName(lub));
222                 return lub;
223             }
224         }
225         return pathToType(tp);
226     }
227 
228     /**
229      * Entry method: get expression info
230      * @param code the expression as a string
231      * @param state a JShell instance
232      * @return type information
233      */
234     public static ExpressionInfo expressionInfo(String code, JShell state) {
235         if (code == null || code.isEmpty()) {
236             return null;
237         }
238         OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code));
239         try {
240             return state.taskFactory.analyze(codeWrap, at -> {
241                 CompilationUnitTree cu = at.firstCuTree();
242                 if (at.hasErrors() || cu == null) {
243                     return null;
244                 }
245                 return new ExpressionToTypeInfo(at, cu, state, false, false).typeOfExpression();
246             });
247         } catch (Exception ex) {
248             return null;
249         }
250     }
251 
252     /**
253      * Entry method: get expression info corresponding to a local variable declaration if its type
254      * has been inferred automatically from the given initializer.
255      * @param code the initializer as a string
256      * @param state a JShell instance
257      * @return type information
258      */
259     public static ExpressionInfo localVariableTypeForInitializer(String code, JShell state, boolean onlyAccessible) {
260         if (code == null || code.isEmpty()) {
261             return null;
262         }
263         try {
264             OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodWrap("var $$$ = " + code));
265             return state.taskFactory.analyze(codeWrap, at -> {
266                 CompilationUnitTree cu = at.firstCuTree();
267                 if (at.hasErrors() || cu == null) {
268                     return null;
269                 }
270                 return new ExpressionToTypeInfo(at, cu, state, true, onlyAccessible)
271                         .typeOfExpression();
272             });
273         } catch (Exception ex) {
274             return null;
275         }
276     }
277 
278     /**List (in a stable order) all NewClassTree instances under {@code from} that should be
279      * converted to member classes
280      *
281      * @param from tree to inspect
282      * @return NewClassTree instances that should be converted to member classes
283      */
284     public static List<NewClassTree> listAnonymousClassesToConvert(Tree from) {
285         ListBuffer<NewClassTree> classes = new ListBuffer<>();
286 
287         new TreeScanner<Void, Void>() {
288             @Override
289             public Void visitNewClass(NewClassTree node, Void p) {
290                 if (node.getClassBody() != null) {
291                     classes.append(node);
292                     return null;
293                 }
294                 return super.visitNewClass(node, p);
295             }
296         }.scan(from, null);
297 
298         return classes.toList();
299     }
300 
301     private ExpressionInfo typeOfExpression() {
302         return treeToInfo(findExpressionPath());
303     }
304 
305     private TreePath findExpressionPath() {
306         try {
307             new PathFinder().scan(new TreePath(cu), false);
308         } catch (Result result) {
309             return result.expressionPath;
310         }
311         return null;
312     }
313 
314     /**
315      * A type is accessible if it is public or if it is package-private and is a
316      * type defined in JShell.  Additionally, all its type arguments must be
317      * accessible
318      *
319      * @param type the type to check for accessibility
320      * @return true if the type name can be referenced
321      */
322     private boolean isAccessible(Type type) {
323         Symbol.TypeSymbol tsym = type.asElement();
324         return ((tsym.flags() & Flags.PUBLIC) != 0 ||
325                 ((tsym.flags() & Flags.PRIVATE) == 0 &&
326                 Util.isInJShellClass(tsym.flatName().toString()))) &&
327                  type.getTypeArguments().stream()
328                         .allMatch(this::isAccessible);
329     }
330 
331     /**
332      * Return the superclass.
333      *
334      * @param type the type
335      * @return the superclass, or Object on error
336      */
337     private Type supertype(Type type) {
338         Type sup = types.supertype(type);
339         if (sup == Type.noType || sup == null) {
340             return syms.objectType;
341         }
342         return sup;
343     }
344 
345     /**
346      * Find an accessible supertype.
347      *
348      * @param type the type
349      * @return the type, if it is accessible, otherwise a superclass or
350      * interface which is
351      */
352     private List<Type> findAccessibleSupertypes(Type type) {
353         List<Type> accessible = List.nil();
354         Type accessibleSuper = syms.objectType;
355         // Iterate up the superclasses, see if any are accessible
356         for (Type sup = type; !types.isSameType(sup, syms.objectType); sup = supertype(sup)) {
357             if (isAccessible(sup)) {
358                 accessible = accessible.prepend(sup);
359                 accessibleSuper = sup;
360                 break;
361             }
362         }
363         // then look through superclasses for accessible interfaces
364         for (Type sup = type; !types.isSameType(sup, accessibleSuper); sup = supertype(sup)) {
365             for (Type itf : types.interfaces(sup)) {
366                 if (isAccessible(itf)) {
367                     accessible = accessible.prepend(itf);
368                 }
369             }
370         }
371         if (accessible.isEmpty()) {
372             // Punt, use Object which is the supertype of everything
373             accessible = accessible.prepend(syms.objectType);
374         }
375 
376         return accessible.reverse();
377     }
378 
379     private ExpressionInfo treeToInfo(TreePath tp) {
380         if (tp != null) {
381             Tree tree = tp.getLeaf();
382             boolean isExpression = tree instanceof ExpressionTree;
383             if (isExpression || tree.getKind() == Kind.VARIABLE) {
384                 ExpressionInfo ei = new ExpressionInfo();
385                 if (isExpression)
386                     ei.tree = (ExpressionTree) tree;
387                 Type type = pathToType(tp, tree);
388                 if (type != null) {
389                     switch (type.getKind()) {
390                         case VOID:
391                         case NONE:
392                         case ERROR:
393                         case OTHER:
394                             break;
395                         case NULL:
396                             ei.isNonVoid = true;
397                             ei.typeName = OBJECT_TYPE_NAME;
398                             ei.accessibleTypeName = OBJECT_TYPE_NAME;
399                             break;
400                         default: {
401                             ei.isNonVoid = true;
402                             ei.isPrimitiveType = type.isPrimitive();
403                             ei.typeName = varTypeName(type, false, AnonymousTypeKind.SUPER);
404                             List<Type> accessibleTypes = findAccessibleSupertypes(type);
405                             ei.accessibleTypeName =
406                                     varTypeName(accessibleTypes.head, false, AnonymousTypeKind.SUPER);
407                             if (computeEnhancedInfo) {
408                                 Type accessibleType = accessibleTypes.size() == 1 ? accessibleTypes.head
409                                             : types.makeIntersectionType(accessibleTypes);
410                                 ei.declareTypeName =
411                                         varTypeName(accessibleType, (full, pkg) -> full, false, AnonymousTypeKind.DECLARE);
412                                 ei.fullTypeName =
413                                         varTypeName(enhancedTypesAccessible ? accessibleType : type, (full, pkg) -> full,
414                                                     true, AnonymousTypeKind.DECLARE);
415                                 ei.displayTypeName =
416                                         varTypeName(type, true, AnonymousTypeKind.DISPLAY);
417                             }
418                             break;
419                         }
420                     }
421                 }
422                 if (tree.getKind() == Tree.Kind.VARIABLE && computeEnhancedInfo) {
423                     Tree init = ((VariableTree) tree).getInitializer();
424                     for (NewClassTree node : listAnonymousClassesToConvert(init)) {
425                         Set<VariableElement> captured = capturedVariables(at,
426                                                                           tp.getCompilationUnit(),
427                                                                           node);
428                         JCClassDecl clazz = (JCClassDecl) node.getClassBody();
429                         MethodInvocationTree superCall =
430                                 clazz.getMembers()
431                                      .stream()
432                                      .filter(JCMethodDecl.class::isInstance)
433                                      .map(JCMethodDecl.class::cast)
434                                      .map(TreeInfo::findConstructorCall)
435                                      .findAny()
436                                      .get();
437                         TreePath superCallPath
438                                 = at.trees().
439                                         getPath(tp.getCompilationUnit(), superCall.
440                                                 getMethodSelect());
441                         Type constrType = pathToType(superCallPath);
442                         AnonymousDescription desc = new AnonymousDescription();
443                         desc.parameterTypes = constrType.getParameterTypes().
444                                 stream().
445                                 map(t -> varTypeName(t, false, AnonymousTypeKind.DECLARE)).
446                                 collect(List.collector());
447                         if (node.getEnclosingExpression() != null) {
448                             TreePath enclPath = new TreePath(tp,
449                                                              node.getEnclosingExpression());
450                             desc.enclosingInstanceType = varTypeName(pathToType(enclPath),
451                                                                      false,
452                                                                      AnonymousTypeKind.DECLARE);
453                         }
454                         TreePath currentPath = at.trees()
455                                                  .getPath(tp.getCompilationUnit(),
456                                                           node);
457                         Type nodeType = pathToType(currentPath, node);
458                         desc.superTypeName = varTypeName(nodeType,
459                                                          false,
460                                                          AnonymousTypeKind.SUPER);
461                         desc.declareTypeName = varTypeName(nodeType,
462                                                            true, AnonymousTypeKind.DECLARE);
463                         desc.capturedVariables =
464                                 captured.stream()
465                                         .map(ve -> new VariableDesc(varTypeName((Type) ve.asType(),
466                                                                                 false,
467                                                                                 AnonymousTypeKind.DECLARE),
468                                                                     ve.getSimpleName().toString()))
469                                         .collect(List.collector());
470 
471                         desc.isClass = at.task.getTypes().directSupertypes(nodeType).size() == 1;
472                         ei.anonymousClasses = ei.anonymousClasses.prepend(desc);
473                     }
474                     ei.anonymousClasses = ei.anonymousClasses.reverse();
475                 }
476                 return ei;
477             }
478         }
479         return null;
480     }
481     //where:
482         private static Set<VariableElement> capturedVariables(AnalyzeTask at,
483                                                               CompilationUnitTree topLevel,
484                                                               Tree tree) {
485             Set<VariableElement> capturedVars = new HashSet<>();
486             new TreeScanner<Void, Void>() {
487                 Set<VariableElement> declaredLocalVars = new HashSet<>();
488                 @Override
489                 public Void visitVariable(VariableTree node, Void p) {
490                     TreePath currentPath = at.trees()
491                                              .getPath(topLevel, node);
492                     declaredLocalVars.add((VariableElement) at.trees().getElement(currentPath));
493                     return super.visitVariable(node, p);
494                 }
495 
496                 @Override
497                 public Void visitIdentifier(IdentifierTree node, Void p) {
498                     TreePath currentPath = at.trees()
499                                              .getPath(topLevel, node);
500                     Element el = at.trees().getElement(currentPath);
501                     if (el != null &&
502                         LOCAL_VARIABLES.contains(el.getKind()) &&
503                         !declaredLocalVars.contains(el)) {
504                         capturedVars.add((VariableElement) el);
505                     }
506                     return super.visitIdentifier(node, p);
507                 }
508             }.scan(tree, null);
509 
510             return capturedVars;
511         }
512         private static final Set<ElementKind> LOCAL_VARIABLES =
513                 EnumSet.of(ElementKind.EXCEPTION_PARAMETER, ElementKind.LOCAL_VARIABLE,
514                            ElementKind.PARAMETER, ElementKind.RESOURCE_VARIABLE);
515 
516     private String varTypeName(Type type, boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
517         return varTypeName(type, state.maps::fullClassNameAndPackageToClass, printIntersectionTypes, anonymousTypesKind);
518     }
519 
520     private String varTypeName(Type type, BinaryOperator<String> fullClassNameAndPackageToClass, boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
521         try {
522             Function<TypeSymbol, String> anonymousClass2DeclareName =
523                     cs -> anon2Name.computeIfAbsent(cs, state.eval::computeDeclareName);
524             TypePrinter tp = new TypePrinter(at.messages(), at.types(),
525                     fullClassNameAndPackageToClass, anonymousClass2DeclareName,
526                     printIntersectionTypes, anonymousTypesKind);
527             List<Type> captures = types.captures(type);
528             String res = tp.toString(types.upward(type, captures));
529 
530             if (res == null)
531                 res = OBJECT_TYPE_NAME;
532 
533             return res;
534         } catch (Exception ex) {
535             return OBJECT_TYPE_NAME;
536         }
537     }
538 
539 }