All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.javacutil.Resolver Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.48.2
Show newest version
package org.checkerframework.javacutil;

import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacScope;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Kinds.KindSelector;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.DeferredAttr;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.comp.Resolve;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/** A utility class to find symbols corresponding to string references (identifiers). */
// This class reflectively accesses jdk.compiler/com.sun.tools.javac.comp.
// This is why --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED is required when
// running the Checker Framework.  If this class is re-written, then that --add-opens should be
// removed.
public class Resolver {
  private final Resolve resolve;
  private final Names names;
  private final Trees trees;
  private final Log log;

  private static final Method FIND_METHOD;
  private static final Method FIND_VAR;
  private static final Method FIND_IDENT;
  private static final Method FIND_IDENT_IN_TYPE;
  private static final Method FIND_IDENT_IN_PACKAGE;
  private static final Method FIND_TYPE;

  private static final Class ACCESSERROR;
  // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError
  private static final Method ACCESSERROR_ACCESS;

  static {
    try {
      FIND_METHOD =
          Resolve.class.getDeclaredMethod(
              "findMethod",
              Env.class,
              Type.class,
              Name.class,
              List.class,
              List.class,
              boolean.class,
              boolean.class);
      FIND_METHOD.setAccessible(true);

      FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class);
      FIND_VAR.setAccessible(true);

      Method findIdentMethod;
      try {
        findIdentMethod =
            Resolve.class.getDeclaredMethod("findIdent", Env.class, Name.class, KindSelector.class);
      } catch (NoSuchMethodException e) {
        findIdentMethod =
            Resolve.class.getDeclaredMethod(
                "findIdentInternal", Env.class, Name.class, KindSelector.class);
      }
      FIND_IDENT = findIdentMethod;
      FIND_IDENT.setAccessible(true);

      Method findIdentInTypeMethod;
      try {
        findIdentInTypeMethod =
            Resolve.class.getDeclaredMethod(
                "findIdentInType", Env.class, Type.class, Name.class, KindSelector.class);
      } catch (NoSuchMethodException e) {
        findIdentInTypeMethod =
            Resolve.class.getDeclaredMethod(
                "findIdentInTypeInternal", Env.class, Type.class, Name.class, KindSelector.class);
      }
      FIND_IDENT_IN_TYPE = findIdentInTypeMethod;
      FIND_IDENT_IN_TYPE.setAccessible(true);

      Method findIdentInPackageMethod;
      try {
        findIdentInPackageMethod =
            Resolve.class.getDeclaredMethod(
                "findIdentInPackage", Env.class, TypeSymbol.class, Name.class, KindSelector.class);
      } catch (NoSuchMethodException e) {
        findIdentInPackageMethod =
            Resolve.class.getDeclaredMethod(
                "findIdentInPackageInternal",
                Env.class,
                TypeSymbol.class,
                Name.class,
                KindSelector.class);
      }
      FIND_IDENT_IN_PACKAGE = findIdentInPackageMethod;
      FIND_IDENT_IN_PACKAGE.setAccessible(true);

      FIND_TYPE = Resolve.class.getDeclaredMethod("findType", Env.class, Name.class);
      FIND_TYPE.setAccessible(true);
    } catch (Exception e) {
      Error err =
          new AssertionError("Compiler 'Resolve' class doesn't contain required 'find' method");
      err.initCause(e);
      throw err;
    }

    try {
      ACCESSERROR = Class.forName("com.sun.tools.javac.comp.Resolve$AccessError");
      ACCESSERROR_ACCESS = ACCESSERROR.getMethod("access", Name.class, TypeSymbol.class);
      ACCESSERROR_ACCESS.setAccessible(true);
    } catch (ClassNotFoundException e) {
      throw new BugInCF("Compiler 'Resolve$AccessError' class could not be retrieved.", e);
    } catch (NoSuchMethodException e) {
      throw new BugInCF(
          "Compiler 'Resolve$AccessError' class doesn't contain required 'access' method", e);
    }
  }

  public Resolver(ProcessingEnvironment env) {
    Context context = ((JavacProcessingEnvironment) env).getContext();
    this.resolve = Resolve.instance(context);
    this.names = Names.instance(context);
    this.trees = Trees.instance(env);
    this.log = Log.instance(context);
  }

  /**
   * Determine the environment for the given path.
   *
   * @param path the tree path to the local scope
   * @return the corresponding attribution environment
   */
  public Env getEnvForPath(TreePath path) {
    TreePath iter = path;
    JavacScope scope = null;
    while (scope == null && iter != null) {
      try {
        scope = (JavacScope) trees.getScope(iter);
      } catch (NullPointerException t) {
        // This statement fixes https://github.com/typetools/checker-framework/issues/1059 .
        // It work around the crash by skipping through the TreePath until something doesn't
        // crash. This probably returns the class scope, so users might not get the
        // variables they expect. But that is better than crashing.
        iter = iter.getParentPath();
      }
    }
    if (scope != null) {
      return scope.getEnv();
    } else {
      throw new BugInCF("Could not determine any possible scope for path: " + path.getLeaf());
    }
  }

  /**
   * Finds the package with name {@code name}.
   *
   * @param name the name of the package
   * @param path the tree path to the local scope
   * @return the {@code PackageSymbol} for the package if it is found, {@code null} otherwise
   */
  public @Nullable PackageSymbol findPackage(String name, TreePath path) {
    Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log);
    try {
      Env env = getEnvForPath(path);
      Element res =
          wrapInvocationOnResolveInstance(
              FIND_IDENT, env, names.fromString(name), Kinds.KindSelector.PCK);
      // findIdent will return a PackageSymbol even for a symbol that is not a package,
      // such as a.b.c.MyClass.myStaticField. "exists()" must be called on it to ensure
      // that it exists.
      if (res.getKind() == ElementKind.PACKAGE) {
        PackageSymbol ps = (PackageSymbol) res;
        return ps.exists() ? ps : null;
      } else {
        return null;
      }
    } finally {
      log.popDiagnosticHandler(discardDiagnosticHandler);
    }
  }

  /**
   * Finds the field with name {@code name} in {@code type} or a superclass or superinterface of
   * {@code type}.
   *
   * 

The method adheres to all the rules of Java's scoping (while also considering the imports) * for name resolution. * * @param name the name of the field * @param type the type of the receiver (i.e., the type in which to look for the field) * @param path the tree path to the local scope * @return the element for the field, {@code null} otherwise */ public @Nullable VariableElement findField(String name, TypeMirror type, TreePath path) { Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); try { Env env = getEnvForPath(path); Element res = wrapInvocationOnResolveInstance( FIND_IDENT_IN_TYPE, env, type, names.fromString(name), Kinds.KindSelector.VAR); if (res.getKind().isField()) { return (VariableElement) res; } else if (res.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(res)) { // Return the inaccessible field that was found return (VariableElement) wrapInvocation(res, ACCESSERROR_ACCESS, null, null); } else { // Most likely didn't find the field and the Element is a SymbolNotFoundError return null; } } finally { log.popDiagnosticHandler(discardDiagnosticHandler); } } /** * Finds the local variable (including formal parameters) with name {@code name} in the given * scope. * * @param name the name of the local variable * @param path the tree path to the local scope * @return the element for the local variable, {@code null} otherwise */ public @Nullable VariableElement findLocalVariableOrParameter(String name, TreePath path) { Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); try { Env env = getEnvForPath(path); // Either a VariableElement or a SymbolNotFoundError. Element res = wrapInvocationOnResolveInstance(FIND_VAR, env, names.fromString(name)); // Every kind in the documentation of Element.getKind() is explicitly tested, possibly // in the "default:" case. switch (res.getKind()) { case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: return (VariableElement) res; case ENUM_CONSTANT: case FIELD: return null; default: if (ElementUtils.isBindingVariable(res)) { return (VariableElement) res; } if (res instanceof VariableElement) { throw new BugInCF("unhandled variable ElementKind " + res.getKind()); } // The Element might be a SymbolNotFoundError. return null; } } finally { log.popDiagnosticHandler(discardDiagnosticHandler); } } /** * Finds the class literal with name {@code name}. * *

The method adheres to all the rules of Java's scoping (while also considering the imports) * for name resolution. * * @param name the name of the class * @param path the tree path to the local scope * @return the element for the class */ public Element findClass(String name, TreePath path) { Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); try { Env env = getEnvForPath(path); return wrapInvocationOnResolveInstance(FIND_TYPE, env, names.fromString(name)); } finally { log.popDiagnosticHandler(discardDiagnosticHandler); } } /** * Finds the class with name {@code name} in a given package. * * @param name the name of the class * @param pck the PackageSymbol for the package * @param path the tree path to the local scope * @return the {@code ClassSymbol} for the class if it is found, {@code null} otherwise */ public @Nullable ClassSymbol findClassInPackage(String name, PackageSymbol pck, TreePath path) { Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); try { Env env = getEnvForPath(path); Element res = wrapInvocationOnResolveInstance( FIND_IDENT_IN_PACKAGE, env, pck, names.fromString(name), Kinds.KindSelector.TYP); if (ElementUtils.isTypeElement(res)) { return (ClassSymbol) res; } else { return null; } } finally { log.popDiagnosticHandler(discardDiagnosticHandler); } } /** * Finds the method element for a given name and list of expected parameter types. * *

The method adheres to all the rules of Java's scoping (while also considering the imports) * for name resolution. * *

(This method takes into account autoboxing.) * *

This method is a wrapper around {@code com.sun.tools.javac.comp.Resolve.findMethod}. * * @param methodName name of the method to find * @param receiverType type of the receiver of the method * @param path tree path * @param argumentTypes types of arguments passed to the method call * @return the method element (if found) */ public @Nullable ExecutableElement findMethod( String methodName, TypeMirror receiverType, TreePath path, java.util.List argumentTypes) { Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); try { Env env = getEnvForPath(path); Type site = (Type) receiverType; Name name = names.fromString(methodName); List argtypes = List.nil(); for (TypeMirror a : argumentTypes) { argtypes = argtypes.append((Type) a); } List typeargtypes = List.nil(); boolean allowBoxing = true; boolean useVarargs = false; try { // For some reason we have to set our own method context, which is rather ugly. // TODO: find a nicer way to do this. Object methodContext = buildMethodContext(); Object oldContext = getField(resolve, "currentResolutionContext"); setField(resolve, "currentResolutionContext", methodContext); Element resolveResult = wrapInvocationOnResolveInstance( FIND_METHOD, env, site, name, argtypes, typeargtypes, allowBoxing, useVarargs); setField(resolve, "currentResolutionContext", oldContext); ExecutableElement methodResult; if (resolveResult.getKind() == ElementKind.METHOD || resolveResult.getKind() == ElementKind.CONSTRUCTOR) { methodResult = (ExecutableElement) resolveResult; } else if (resolveResult.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(resolveResult)) { // Return the inaccessible method that was found. methodResult = (ExecutableElement) wrapInvocation(resolveResult, ACCESSERROR_ACCESS, null, null); } else { methodResult = null; } return methodResult; } catch (Throwable t) { Error err = new AssertionError( String.format( "Unexpected reflection error in findMethod(%s, %s, ..., %s)", methodName, receiverType, // path argumentTypes)); err.initCause(t); throw err; } } finally { log.popDiagnosticHandler(discardDiagnosticHandler); } } /** * Build an instance of {@code Resolve$MethodResolutionContext}. * * @return a MethodResolutionContext * @throws ClassNotFoundException if there is trouble constructing the instance * @throws InstantiationException if there is trouble constructing the instance * @throws IllegalAccessException if there is trouble constructing the instance * @throws InvocationTargetException if there is trouble constructing the instance * @throws NoSuchFieldException if there is trouble constructing the instance */ protected Object buildMethodContext() throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException { // Class is not accessible, instantiate reflectively. Class methCtxClss = Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext"); Constructor constructor = methCtxClss.getDeclaredConstructors()[0]; constructor.setAccessible(true); Object methodContext = constructor.newInstance(resolve); // we need to also initialize the fields attrMode and step setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK); @SuppressWarnings("rawtypes") List phases = (List) getField(resolve, "methodResolutionSteps"); assert phases != null : "@AssumeAssertion(nullness): assumption"; setField(methodContext, "step", phases.get(1)); return methodContext; } /** * Reflectively set a field. * * @param receiver the receiver in which to set the field * @param fieldName name of field to set * @param value new value for field * @throws NoSuchFieldException if the field does not exist in the receiver * @throws IllegalAccessException if the field is not accessible */ @SuppressWarnings({ "nullness:argument", "interning:argument" }) // assume that the fields all accept null and uninterned values private void setField(Object receiver, String fieldName, @Nullable Object value) throws NoSuchFieldException, IllegalAccessException { Field f = receiver.getClass().getDeclaredField(fieldName); f.setAccessible(true); f.set(receiver, value); } /** Reflectively get the value of a field. */ private @Nullable Object getField(Object receiver, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field f = receiver.getClass().getDeclaredField(fieldName); f.setAccessible(true); return f.get(receiver); } /** * Wrap a method invocation on the {@code resolve} object. * * @param method the method to called * @param args the arguments to the call * @return the result of invoking the method on {@code resolve} (as the receiver) and the * arguments */ private Symbol wrapInvocationOnResolveInstance(Method method, @Nullable Object... args) { return wrapInvocation(resolve, method, args); } /** * Invoke a method reflectively. This is like {@code Method.invoke()}, but it throws no checked * exceptions. * * @param receiver the receiver * @param method the method to called * @param args the arguments to the call * @return the result of invoking the method on the receiver and arguments */ private Symbol wrapInvocation(Object receiver, Method method, @Nullable Object... args) { try { @SuppressWarnings("nullness") // assume arguments are OK @NonNull Symbol res = (Symbol) method.invoke(receiver, args); return res; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new BugInCF( e, "Unexpected reflection error in wrapInvocation(%s, %s, %s)", receiver, method, Arrays.toString(args)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy