/* * Copyright 2017-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.jvm.java.abi.source; import com.facebook.buck.util.liteinfersupport.Preconditions; import com.sun.source.tree.BlockTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.ImportTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; /** * Examines the non-private interfaces of types defined in one or more {@link CompilationUnitTree}s * and finds references to types or compile-time constants. This is intended for use during full * compilation, to expose references that may be problematic when generating ABIs without * dependencies. */ class InterfaceTypeAndConstantReferenceFinder { public interface Listener { void onTypeImported(TypeElement type); void onTypeReferenceFound(TypeElement type, TreePath path, Element enclosingElement); void onConstantReferenceFound( VariableElement constant, TreePath path, Element enclosingElement); } private final Listener listener; private final Trees trees; public InterfaceTypeAndConstantReferenceFinder(Trees trees, Listener listener) { this.trees = trees; this.listener = listener; } public void findReferences(Iterable<? extends CompilationUnitTree> files) { files.forEach(file -> findReferencesInSingleFile(file)); } private void findReferencesInSingleFile(CompilationUnitTree file) { // Scan the non-private interface portions of the tree, and report any references to types // or constants that are found. new TreeContextScanner<Void, Void>(trees) { /** Reports types that are imported via single-type imports */ @Override public Void visitImport(ImportTree node, Void aVoid) { if (node.isStatic()) { // Static import; we care only about type imports return null; } MemberSelectTree typeNameTree = (MemberSelectTree) node.getQualifiedIdentifier(); if (typeNameTree.getIdentifier().contentEquals("*")) { // Star import; caller doesn't care return null; } // Single-type import; report to listener TreePath importedTypePath = new TreePath(getCurrentPath(), typeNameTree); TypeElement importedType = (TypeElement) Preconditions.checkNotNull(trees.getElement(importedTypePath)); listener.onTypeImported(importedType); return null; } /** Restricts the search to non-private classes. */ @Override public Void visitClass(ClassTree node, Void aVoid) { Element element = getEnclosingElement(); // Skip private since they're not part of the interface if (isPrivate(element)) { return null; } return super.visitClass(node, aVoid); } /** Restricts the search to non-private methods */ @Override public Void visitMethod(MethodTree node, Void aVoid) { Element element = getEnclosingElement(); // Skip private since they're not part of the interface if (isPrivate(element)) { return null; } return super.visitMethod(node, aVoid); } /** * Restricts the search to non-private variables, and only searches the initializer if the * variable is a compile-time constant field. */ @Override public Void visitVariable(VariableTree node, Void aVoid) { VariableElement element = (VariableElement) getEnclosingElement(); // Skip private since they're not part of the interface if (isPrivate(element)) { return null; } scan(node.getModifiers(), aVoid); scan(node.getType(), aVoid); // Skip the initializers of variables that aren't static constants since they're not part // of the interface if (element.getConstantValue() == null || !element.getModifiers().contains(Modifier.STATIC)) { return null; } scan(node.getInitializer(), aVoid); return null; } /** Prevents the search from descending into method bodies. */ @Override public Void visitBlock(BlockTree node, Void aVoid) { // We care only about the interface, so we don't need to recurse into code blocks return null; } /** * Reports references to types or constants via fully or partially-qualified names, wherever * they might appear in the interface. */ @Override public Void visitMemberSelect(MemberSelectTree node, Void aVoid) { Element currentElement = getCurrentElement(); if (currentElement == null) { // This can happen with the package name tree at compilation unit level return null; } // The other visit methods have ensured that we're only seeing MemberSelectTrees that are // part of the non-private interface, so let's figure out if this one represents a type // reference or constant reference that we need to report ElementKind kind = currentElement.getKind(); if (kind.isClass() || kind.isInterface()) { TypeElement typeElement = (TypeElement) currentElement; if (typeElement.getEnclosingElement().getKind() == ElementKind.PACKAGE) { // This is a fully-qualified name reportType(); return null; // Stop; we don't need to report the package reference } // If it's not a package member, keep going; we want to report the outermost type // that is actually named in the source, since that's the thing that might be imported } else { // If it's not a class reference, it could be a reference to a constant field, either // as part of initializing a constant field or as a parameter to an annotation VariableElement variableElement = (VariableElement) currentElement; if (variableElement.getConstantValue() != null) { reportConstant(); // Keep going; there can also be a type reference that needs reporting } } // Look at the root of this member select; it might be a top-level type return super.visitMemberSelect(node, aVoid); } /** * Reports references to types or constants via simple name, wherever it might appear in the * interface */ @Override public Void visitIdentifier(IdentifierTree node, Void aVoid) { Element currentElement = getCurrentElement(); if (currentElement == null) { // This can happen with the package name tree at compilation unit level return null; } ElementKind kind = currentElement.getKind(); if (kind.isClass() || kind.isInterface()) { // Identifier represents a type (either imported, from the same package, or a member of // the current class or a supertype/interface); report it reportType(); } else if (kind == ElementKind.FIELD) { // Identifier is a field name. Check if it's a constant and report if so. VariableElement variableElement = (VariableElement) currentElement; if (variableElement.getConstantValue() != null) { reportConstant(); } } // Ignore other types of identifiers return null; } private void reportType() { TypeMirror currentType = Preconditions.checkNotNull(getCurrentType()); if (currentType.getKind() != TypeKind.DECLARED) { return; } listener.onTypeReferenceFound( (TypeElement) Preconditions.checkNotNull(getCurrentElement()), getCurrentPath(), getEnclosingElement()); } private void reportConstant() { VariableElement variable = (VariableElement) Preconditions.checkNotNull(getCurrentElement()); listener.onConstantReferenceFound(variable, getCurrentPath(), getEnclosingElement()); } }.scan(file, null); } private static boolean isPrivate(Element element) { return element.getModifiers().contains(Modifier.PRIVATE); } }