| /* |
| * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.jshell; |
| |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Function; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.VariableElement; |
| import com.sun.source.tree.ReturnTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.ConditionalExpressionTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| import com.sun.source.util.TreePathScanner; |
| import com.sun.source.util.TreeScanner; |
| import com.sun.tools.javac.code.Flags; |
| import com.sun.tools.javac.code.Symbol; |
| import com.sun.tools.javac.code.Symbol.TypeSymbol; |
| import com.sun.tools.javac.code.Symtab; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.code.Types; |
| import com.sun.tools.javac.tree.JCTree.JCClassDecl; |
| import com.sun.tools.javac.tree.TreeInfo; |
| import com.sun.tools.javac.util.List; |
| import com.sun.tools.javac.util.ListBuffer; |
| import java.util.function.BinaryOperator; |
| import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription; |
| import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription.VariableDesc; |
| import jdk.jshell.TaskFactory.AnalyzeTask; |
| import jdk.jshell.TypePrinter.AnonymousTypeKind; |
| |
| /** |
| * Compute information about an expression string, particularly its type name. |
| */ |
| class ExpressionToTypeInfo { |
| |
| private static final String OBJECT_TYPE_NAME = "Object"; |
| |
| final AnalyzeTask at; |
| final CompilationUnitTree cu; |
| final JShell state; |
| final boolean computeEnhancedInfo; |
| final boolean enhancedTypesAccessible; |
| final Symtab syms; |
| final Types types; |
| final Map<TypeSymbol, String> anon2Name = new HashMap<>(); |
| |
| private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state, |
| boolean computeEnhancedInfo, boolean enhancedTypesAccessible) { |
| this.at = at; |
| this.cu = cu; |
| this.state = state; |
| this.computeEnhancedInfo = computeEnhancedInfo; |
| this.enhancedTypesAccessible = enhancedTypesAccessible; |
| this.syms = Symtab.instance(at.context); |
| this.types = Types.instance(at.context); |
| } |
| |
| public static class ExpressionInfo { |
| ExpressionTree tree; |
| boolean isPrimitiveType; |
| String typeName; |
| String accessibleTypeName; |
| /* In result of localVariableTypeForInitializer, the type that should be used |
| * as a declaration type of the field. This does not include intersection types, |
| * but does contain references to anonymous types converted to member types. |
| */ |
| String declareTypeName; |
| /* In result of localVariableTypeForInitializer, the apparent/infered type of |
| * the variable. This includes intersection types, and references to anonymous |
| * types converted to member types. |
| */ |
| String fullTypeName; |
| /* In result of localVariableTypeForInitializer, the human readable type of |
| * the variable. This includes intersection types, and human readable descriptions |
| * of anonymous types. |
| */ |
| String displayTypeName; |
| boolean isNonVoid; |
| /* In result of localVariableTypeForInitializer, description of important anonymous |
| * classes. |
| */ |
| List<AnonymousDescription> anonymousClasses = List.nil(); |
| |
| /* A description of an anonymous class. */ |
| static class AnonymousDescription { |
| /* Parameter types of the invoked super constructor.*/ |
| List<String> parameterTypes; |
| /* Type of the base/enclosing expression, if any.*/ |
| String enclosingInstanceType; |
| /* The denotable name of the supertype.*/ |
| String superTypeName; |
| /* The human-readable name of this class.*/ |
| String declareTypeName; |
| /* If the supertype of this anonymous is a class. */ |
| boolean isClass; |
| /* Variables captured by this anonymous class*/ |
| List<VariableDesc> capturedVariables; |
| |
| static class VariableDesc { |
| String type; |
| String name; |
| |
| public VariableDesc(String type, String name) { |
| this.type = type; |
| this.name = name; |
| } |
| |
| } |
| } |
| } |
| |
| // return mechanism and other general structure from TreePath.getPath() |
| private static class Result extends Error { |
| |
| static final long serialVersionUID = -5942088234594905629L; |
| final TreePath expressionPath; |
| |
| Result(TreePath path) { |
| this.expressionPath = path; |
| } |
| } |
| |
| private static class PathFinder extends TreePathScanner<TreePath, Boolean> { |
| |
| // Optimize out imports etc |
| @Override |
| public TreePath visitCompilationUnit(CompilationUnitTree node, Boolean isTargetContext) { |
| return scan(node.getTypeDecls(), isTargetContext); |
| } |
| |
| // Only care about members |
| @Override |
| public TreePath visitClass(ClassTree node, Boolean isTargetContext) { |
| return scan(node.getMembers(), isTargetContext); |
| } |
| |
| // Only want the doit method where the code is |
| @Override |
| public TreePath visitMethod(MethodTree node, Boolean isTargetContext) { |
| if (Util.isDoIt(node.getName())) { |
| return scan(node.getBody(), true); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public TreePath visitReturn(ReturnTree node, Boolean isTargetContext) { |
| ExpressionTree tree = node.getExpression(); |
| TreePath tp = new TreePath(getCurrentPath(), tree); |
| if (isTargetContext) { |
| throw new Result(tp); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public TreePath visitVariable(VariableTree node, Boolean isTargetContext) { |
| if (isTargetContext) { |
| throw new Result(getCurrentPath()); |
| } else { |
| return null; |
| } |
| } |
| |
| } |
| |
| private Type pathToType(TreePath tp) { |
| return (Type) at.trees().getTypeMirror(tp); |
| } |
| |
| private Type pathToType(TreePath tp, Tree tree) { |
| if (tree instanceof ConditionalExpressionTree) { |
| // Conditionals always wind up as Object -- this corrects |
| ConditionalExpressionTree cet = (ConditionalExpressionTree) tree; |
| Type tmt = pathToType(new TreePath(tp, cet.getTrueExpression())); |
| Type tmf = pathToType(new TreePath(tp, cet.getFalseExpression())); |
| if (!tmt.isPrimitive() && !tmf.isPrimitive()) { |
| Type lub = types.lub(tmt, tmf); |
| // System.err.printf("cond ? %s : %s -- lub = %s\n", |
| // varTypeName(tmt), varTypeName(tmf), varTypeName(lub)); |
| return lub; |
| } |
| } |
| return pathToType(tp); |
| } |
| |
| /** |
| * Entry method: get expression info |
| * @param code the expression as a string |
| * @param state a JShell instance |
| * @return type information |
| */ |
| public static ExpressionInfo expressionInfo(String code, JShell state) { |
| if (code == null || code.isEmpty()) { |
| return null; |
| } |
| OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code)); |
| try { |
| return state.taskFactory.analyze(codeWrap, at -> { |
| CompilationUnitTree cu = at.firstCuTree(); |
| if (at.hasErrors() || cu == null) { |
| return null; |
| } |
| return new ExpressionToTypeInfo(at, cu, state, false, false).typeOfExpression(); |
| }); |
| } catch (Exception ex) { |
| return null; |
| } |
| } |
| |
| /** |
| * Entry method: get expression info corresponding to a local variable declaration if its type |
| * has been inferred automatically from the given initializer. |
| * @param code the initializer as a string |
| * @param state a JShell instance |
| * @return type information |
| */ |
| public static ExpressionInfo localVariableTypeForInitializer(String code, JShell state, boolean onlyAccessible) { |
| if (code == null || code.isEmpty()) { |
| return null; |
| } |
| try { |
| OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodWrap("var $$$ = " + code)); |
| return state.taskFactory.analyze(codeWrap, at -> { |
| CompilationUnitTree cu = at.firstCuTree(); |
| if (at.hasErrors() || cu == null) { |
| return null; |
| } |
| return new ExpressionToTypeInfo(at, cu, state, true, onlyAccessible) |
| .typeOfExpression(); |
| }); |
| } catch (Exception ex) { |
| return null; |
| } |
| } |
| |
| /**List (in a stable order) all NewClassTree instances under {@code from} that should be |
| * converted to member classes |
| * |
| * @param from tree to inspect |
| * @return NewClassTree instances that should be converted to member classes |
| */ |
| public static List<NewClassTree> listAnonymousClassesToConvert(Tree from) { |
| ListBuffer<NewClassTree> classes = new ListBuffer<>(); |
| |
| new TreeScanner<Void, Void>() { |
| @Override |
| public Void visitNewClass(NewClassTree node, Void p) { |
| if (node.getClassBody() != null) { |
| classes.append(node); |
| return null; |
| } |
| return super.visitNewClass(node, p); |
| } |
| }.scan(from, null); |
| |
| return classes.toList(); |
| } |
| |
| private ExpressionInfo typeOfExpression() { |
| return treeToInfo(findExpressionPath()); |
| } |
| |
| private TreePath findExpressionPath() { |
| try { |
| new PathFinder().scan(new TreePath(cu), false); |
| } catch (Result result) { |
| return result.expressionPath; |
| } |
| return null; |
| } |
| |
| /** |
| * A type is accessible if it is public or if it is package-private and is a |
| * type defined in JShell. Additionally, all its type arguments must be |
| * accessible |
| * |
| * @param type the type to check for accessibility |
| * @return true if the type name can be referenced |
| */ |
| private boolean isAccessible(Type type) { |
| Symbol.TypeSymbol tsym = type.asElement(); |
| return ((tsym.flags() & Flags.PUBLIC) != 0 || |
| ((tsym.flags() & Flags.PRIVATE) == 0 && |
| Util.isInJShellClass(tsym.flatName().toString()))) && |
| type.getTypeArguments().stream() |
| .allMatch(this::isAccessible); |
| } |
| |
| /** |
| * Return the superclass. |
| * |
| * @param type the type |
| * @return the superclass, or Object on error |
| */ |
| private Type supertype(Type type) { |
| Type sup = types.supertype(type); |
| if (sup == Type.noType || sup == null) { |
| return syms.objectType; |
| } |
| return sup; |
| } |
| |
| /** |
| * Find an accessible supertype. |
| * |
| * @param type the type |
| * @return the type, if it is accessible, otherwise a superclass or |
| * interface which is |
| */ |
| private List<Type> findAccessibleSupertypes(Type type) { |
| List<Type> accessible = List.nil(); |
| Type accessibleSuper = syms.objectType; |
| // Iterate up the superclasses, see if any are accessible |
| for (Type sup = type; !types.isSameType(sup, syms.objectType); sup = supertype(sup)) { |
| if (isAccessible(sup)) { |
| accessible = accessible.prepend(sup); |
| accessibleSuper = sup; |
| break; |
| } |
| } |
| // then look through superclasses for accessible interfaces |
| for (Type sup = type; !types.isSameType(sup, accessibleSuper); sup = supertype(sup)) { |
| for (Type itf : types.interfaces(sup)) { |
| if (isAccessible(itf)) { |
| accessible = accessible.prepend(itf); |
| } |
| } |
| } |
| if (accessible.isEmpty()) { |
| // Punt, use Object which is the supertype of everything |
| accessible = accessible.prepend(syms.objectType); |
| } |
| |
| return accessible.reverse(); |
| } |
| |
| private ExpressionInfo treeToInfo(TreePath tp) { |
| if (tp != null) { |
| Tree tree = tp.getLeaf(); |
| boolean isExpression = tree instanceof ExpressionTree; |
| if (isExpression || tree.getKind() == Kind.VARIABLE) { |
| ExpressionInfo ei = new ExpressionInfo(); |
| if (isExpression) |
| ei.tree = (ExpressionTree) tree; |
| Type type = pathToType(tp, tree); |
| if (type != null) { |
| switch (type.getKind()) { |
| case VOID: |
| case NONE: |
| case ERROR: |
| case OTHER: |
| break; |
| case NULL: |
| ei.isNonVoid = true; |
| ei.typeName = OBJECT_TYPE_NAME; |
| ei.accessibleTypeName = OBJECT_TYPE_NAME; |
| break; |
| default: { |
| ei.isNonVoid = true; |
| ei.isPrimitiveType = type.isPrimitive(); |
| ei.typeName = varTypeName(type, false, AnonymousTypeKind.SUPER); |
| List<Type> accessibleTypes = findAccessibleSupertypes(type); |
| ei.accessibleTypeName = |
| varTypeName(accessibleTypes.head, false, AnonymousTypeKind.SUPER); |
| if (computeEnhancedInfo) { |
| Type accessibleType = accessibleTypes.size() == 1 ? accessibleTypes.head |
| : types.makeIntersectionType(accessibleTypes); |
| ei.declareTypeName = |
| varTypeName(accessibleType, (full, pkg) -> full, false, AnonymousTypeKind.DECLARE); |
| ei.fullTypeName = |
| varTypeName(enhancedTypesAccessible ? accessibleType : type, (full, pkg) -> full, |
| true, AnonymousTypeKind.DECLARE); |
| ei.displayTypeName = |
| varTypeName(type, true, AnonymousTypeKind.DISPLAY); |
| } |
| break; |
| } |
| } |
| } |
| if (tree.getKind() == Tree.Kind.VARIABLE && computeEnhancedInfo) { |
| Tree init = ((VariableTree) tree).getInitializer(); |
| for (NewClassTree node : listAnonymousClassesToConvert(init)) { |
| Set<VariableElement> captured = capturedVariables(at, |
| tp.getCompilationUnit(), |
| node); |
| JCClassDecl clazz = (JCClassDecl) node.getClassBody(); |
| MethodInvocationTree superCall = |
| clazz.getMembers() |
| .stream() |
| .map(TreeInfo::firstConstructorCall) |
| .findAny() |
| .get(); |
| TreePath superCallPath |
| = at.trees(). |
| getPath(tp.getCompilationUnit(), superCall. |
| getMethodSelect()); |
| Type constrType = pathToType(superCallPath); |
| AnonymousDescription desc = new AnonymousDescription(); |
| desc.parameterTypes = constrType.getParameterTypes(). |
| stream(). |
| map(t -> varTypeName(t, false, AnonymousTypeKind.DECLARE)). |
| collect(List.collector()); |
| if (node.getEnclosingExpression() != null) { |
| TreePath enclPath = new TreePath(tp, |
| node.getEnclosingExpression()); |
| desc.enclosingInstanceType = varTypeName(pathToType(enclPath), |
| false, |
| AnonymousTypeKind.DECLARE); |
| } |
| TreePath currentPath = at.trees() |
| .getPath(tp.getCompilationUnit(), |
| node); |
| Type nodeType = pathToType(currentPath, node); |
| desc.superTypeName = varTypeName(nodeType, |
| false, |
| AnonymousTypeKind.SUPER); |
| desc.declareTypeName = varTypeName(nodeType, |
| true, AnonymousTypeKind.DECLARE); |
| desc.capturedVariables = |
| captured.stream() |
| .map(ve -> new VariableDesc(varTypeName((Type) ve.asType(), |
| false, |
| AnonymousTypeKind.DECLARE), |
| ve.getSimpleName().toString())) |
| .collect(List.collector()); |
| |
| desc.isClass = at.task.getTypes().directSupertypes(nodeType).size() == 1; |
| ei.anonymousClasses = ei.anonymousClasses.prepend(desc); |
| } |
| ei.anonymousClasses = ei.anonymousClasses.reverse(); |
| } |
| return ei; |
| } |
| } |
| return null; |
| } |
| //where: |
| private static Set<VariableElement> capturedVariables(AnalyzeTask at, |
| CompilationUnitTree topLevel, |
| Tree tree) { |
| Set<VariableElement> capturedVars = new HashSet<>(); |
| new TreeScanner<Void, Void>() { |
| Set<VariableElement> declaredLocalVars = new HashSet<>(); |
| @Override |
| public Void visitVariable(VariableTree node, Void p) { |
| TreePath currentPath = at.trees() |
| .getPath(topLevel, node); |
| declaredLocalVars.add((VariableElement) at.trees().getElement(currentPath)); |
| return super.visitVariable(node, p); |
| } |
| |
| @Override |
| public Void visitIdentifier(IdentifierTree node, Void p) { |
| TreePath currentPath = at.trees() |
| .getPath(topLevel, node); |
| Element el = at.trees().getElement(currentPath); |
| if (el != null && |
| LOCAL_VARIABLES.contains(el.getKind()) && |
| !declaredLocalVars.contains(el)) { |
| capturedVars.add((VariableElement) el); |
| } |
| return super.visitIdentifier(node, p); |
| } |
| }.scan(tree, null); |
| |
| return capturedVars; |
| } |
| private static final Set<ElementKind> LOCAL_VARIABLES = |
| EnumSet.of(ElementKind.EXCEPTION_PARAMETER, ElementKind.LOCAL_VARIABLE, |
| ElementKind.PARAMETER, ElementKind.RESOURCE_VARIABLE); |
| |
| private String varTypeName(Type type, boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) { |
| return varTypeName(type, state.maps::fullClassNameAndPackageToClass, printIntersectionTypes, anonymousTypesKind); |
| } |
| |
| private String varTypeName(Type type, BinaryOperator<String> fullClassNameAndPackageToClass, boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) { |
| try { |
| Function<TypeSymbol, String> anonymousClass2DeclareName = |
| cs -> anon2Name.computeIfAbsent(cs, state.eval::computeDeclareName); |
| TypePrinter tp = new TypePrinter(at.messages(), |
| fullClassNameAndPackageToClass, anonymousClass2DeclareName, |
| printIntersectionTypes, anonymousTypesKind); |
| List<Type> captures = types.captures(type); |
| String res = tp.toString(types.upward(type, captures)); |
| |
| if (res == null) |
| res = OBJECT_TYPE_NAME; |
| |
| return res; |
| } catch (Exception ex) { |
| return OBJECT_TYPE_NAME; |
| } |
| } |
| |
| } |