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

com.google.errorprone.bugpatterns.threadsafety.GuardedByBinder Maven / Gradle / Ivy

There is a newer version: 2.27.1
Show newest version
/*
 * Copyright 2014 The Error Prone Authors.
 *
 * 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.google.errorprone.bugpatterns.threadsafety;

import static com.google.errorprone.bugpatterns.threadsafety.IllegalGuardedBy.checkGuardedBy;

import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.threadsafety.GuardedByExpression.Kind;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.Names;
import java.util.Optional;
import javax.lang.model.element.Name;

/**
 * A binder from {@code @GuardedBy} annotations to {@link GuardedByExpression}s.
 *
 * @author [email protected] (Liam Miller-Cushon)
 */
public final class GuardedByBinder {

  /**
   * Creates a {@link GuardedByExpression} from a bound AST node, or returns {@code
   * Optional.empty()} if the AST node doesn't correspond to a 'simple' lock expression.
   */
  public static Optional bindExpression(
      JCTree.JCExpression exp, VisitorState visitorState, GuardedByFlags flags) {
    try {
      return Optional.of(
          bind(
              exp,
              BinderContext.of(
                  ALREADY_BOUND_RESOLVER,
                  ASTHelpers.getSymbol(visitorState.findEnclosing(ClassTree.class)),
                  visitorState.getTypes(),
                  visitorState.getNames(),
                  flags)));
    } catch (IllegalGuardedBy expected) {
      return Optional.empty();
    }
  }

  /** Creates a {@link GuardedByExpression} from a string, given the resolution context. */
  public static Optional bindString(
      String string, GuardedBySymbolResolver resolver, GuardedByFlags flags) {
    try {
      return Optional.of(
          bind(
              GuardedByUtils.parseString(string, resolver.context()),
              BinderContext.of(
                  resolver,
                  resolver.enclosingClass(),
                  resolver.visitorState().getTypes(),
                  resolver.visitorState().getNames(),
                  flags)));
    } catch (IllegalGuardedBy expected) {
      return Optional.empty();
    }
  }

  private static class BinderContext {
    final Resolver resolver;
    final ClassSymbol thisClass;
    final Types types;
    final Names names;
    final GuardedByFlags flags;

    public BinderContext(
        Resolver resolver, ClassSymbol thisClass, Types types, Names names, GuardedByFlags flags) {
      this.resolver = resolver;
      this.thisClass = thisClass;
      this.types = types;
      this.names = names;
      this.flags = flags;
    }

    public static BinderContext of(
        Resolver resolver, ClassSymbol thisClass, Types types, Names names, GuardedByFlags flags) {
      return new BinderContext(resolver, thisClass, types, names, flags);
    }
  }

  private static GuardedByExpression bind(JCTree.JCExpression exp, BinderContext context) {
    GuardedByExpression expr = BINDER.visit(exp, context);
    checkGuardedBy(expr != null, String.valueOf(exp));
    checkGuardedBy(expr.kind() != Kind.TYPE_LITERAL, "Raw type literal: %s", exp);
    return expr;
  }

  /**
   * A context containing the information necessary to resolve a {@link
   * com.sun.tools.javac.code.Symbol} from an AST node.
   *
   * 

Guard expressions can be bound from the string value of an {@code @GuardedBy} annotation, or * from an actual java expression. In the first case, the string is parsed into an AST which will * not have any semantic information attached. */ public interface Resolver { Symbol resolveIdentifier(IdentifierTree node); Symbol.MethodSymbol resolveMethod(MethodInvocationTree node, Name name); Symbol.MethodSymbol resolveMethod( MethodInvocationTree node, GuardedByExpression base, Name identifier); Symbol resolveSelect(GuardedByExpression base, MemberSelectTree node); Symbol resolveTypeLiteral(ExpressionTree expression); Symbol resolveEnclosingClass(ExpressionTree expression); } /** A resolver for AST nodes that have already been bound by javac. */ static final Resolver ALREADY_BOUND_RESOLVER = new Resolver() { @Override public Symbol resolveIdentifier(IdentifierTree node) { return ASTHelpers.getSymbol(node); } @Override public Symbol.MethodSymbol resolveMethod(MethodInvocationTree node, Name name) { return ASTHelpers.getSymbol(node); } @Override public Symbol.MethodSymbol resolveMethod( MethodInvocationTree node, GuardedByExpression base, Name identifier) { return ASTHelpers.getSymbol(node); } @Override public Symbol resolveSelect(GuardedByExpression base, MemberSelectTree node) { return ASTHelpers.getSymbol(node); } @Override public Symbol resolveTypeLiteral(ExpressionTree expression) { return ASTHelpers.getSymbol(expression); } @Override public Symbol resolveEnclosingClass(ExpressionTree expression) { return ASTHelpers.getSymbol(expression); } }; private static final GuardedByExpression.Factory F = new GuardedByExpression.Factory(); private static final SimpleTreeVisitor BINDER = new SimpleTreeVisitor() { @Override public GuardedByExpression visitMethodInvocation( MethodInvocationTree node, BinderContext context) { checkGuardedBy( node.getArguments().isEmpty() && node.getTypeArguments().isEmpty(), "Only nullary methods are allowed."); ExpressionTree methodSelect = node.getMethodSelect(); switch (methodSelect.getKind()) { case IDENTIFIER: { IdentifierTree identifier = (IdentifierTree) methodSelect; Symbol.MethodSymbol method = context.resolver.resolveMethod(node, identifier.getName()); checkGuardedBy(method != null, identifier.toString()); return bindSelect(computeBase(context, method), method); } case MEMBER_SELECT: { MemberSelectTree select = (MemberSelectTree) methodSelect; GuardedByExpression base = visit(select.getExpression(), context); checkGuardedBy(base != null, select.getExpression().toString()); Symbol.MethodSymbol method = context.resolver.resolveMethod(node, base, select.getIdentifier()); checkGuardedBy(method != null, select.toString()); return bindSelect(normalizeBase(context, method, base), method); } default: throw new IllegalGuardedBy(methodSelect.getKind().toString()); } } @Override public GuardedByExpression visitMemberSelect(MemberSelectTree node, BinderContext context) { String name = node.getIdentifier().toString(); if (name.equals("this")) { Symbol base = context.resolver.resolveEnclosingClass(node.getExpression()); if (context.thisClass == base) { return F.thisliteral(); } return F.qualifiedThis(context.names, context.thisClass, base); } if (name.equals("class")) { Symbol base = context.resolver.resolveTypeLiteral(node.getExpression()); return F.classLiteral(base); } GuardedByExpression base = visit(node.getExpression(), context); checkGuardedBy(base != null, "Bad expression: %s", node.getExpression()); Symbol sym = context.resolver.resolveSelect(base, node); checkGuardedBy(sym != null, "Could not resolve: %s", node); boolean condition = sym instanceof Symbol.VarSymbol || sym instanceof Symbol.MethodSymbol; checkGuardedBy(condition, "Bad member symbol: %s", sym.getClass()); return bindSelect(normalizeBase(context, sym, base), sym); } private GuardedByExpression bindSelect(GuardedByExpression base, Symbol sym) { if (base.kind().equals(Kind.TYPE_LITERAL) && !sym.isStatic()) { throw new IllegalGuardedBy("Instance access on static: " + base + ", " + sym); } // TODO(cushon) - forbid static access on instance? return F.select(base, sym); } @Override public GuardedByExpression visitIdentifier(IdentifierTree node, BinderContext context) { Symbol symbol = context.resolver.resolveIdentifier(node); checkGuardedBy(symbol != null, "Could not resolve %s", node); if (symbol instanceof Symbol.VarSymbol) { Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbol; switch (varSymbol.getKind()) { case LOCAL_VARIABLE: case PARAMETER: return F.localVariable(varSymbol); case FIELD: { if (symbol.name.contentEquals("this")) { return F.thisliteral(); } return F.select(computeBase(context, varSymbol), varSymbol); } default: throw new IllegalGuardedBy(varSymbol.getKind().toString()); } } else if (symbol instanceof Symbol.MethodSymbol) { Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) symbol; return F.select(computeBase(context, symbol), methodSymbol); } else if (symbol instanceof Symbol.ClassSymbol) { if (node.getName().contentEquals("this")) { return F.thisliteral(); } else { return F.typeLiteral(symbol); } } throw new IllegalGuardedBy(symbol.getClass().toString()); } @Override public GuardedByExpression visitParenthesized( ParenthesizedTree node, BinderContext context) { return node.getExpression().accept(this, context); } /** * Determines the implicit receiver of a select expression that accesses the given symbol by * simple name in the given resolution context. */ private GuardedByExpression computeBase(BinderContext context, Symbol symbol) { return normalizeBase(context, symbol, null); } /** * Normalizes the receiver of a select expression so that accesses on 'this' are divided * into type names (for static accesses), qualified this accesses (for members of a * lexically enclosing scope), or simple this accesses for members of the current class. */ private GuardedByExpression normalizeBase( BinderContext context, Symbol symbol, GuardedByExpression base) { if (symbol.isStatic()) { return F.typeLiteral(symbol.owner.enclClass()); } if (base != null && base.kind() != GuardedByExpression.Kind.THIS) { return base; } if (symbol.isMemberOf(context.thisClass.type.tsym, context.types)) { return F.thisliteral(); } Symbol lexicalOwner = isEnclosedIn(context.thisClass, symbol, context.types); if (lexicalOwner != null) { return F.qualifiedThis(context.names, context.thisClass, lexicalOwner); } throw new IllegalGuardedBy("Could not find the implicit receiver."); } /** * Returns the owner if the given member is declared in a lexically enclosing scope, and * {@code null} otherwise. */ private ClassSymbol isEnclosedIn(ClassSymbol startingClass, Symbol member, Types types) { for (ClassSymbol scope = startingClass.owner.enclClass(); scope != null; scope = scope.owner.enclClass()) { if (member.isMemberOf(scope.type.tsym, types)) { return scope; } } return null; } }; private GuardedByBinder() {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy