/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* 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.auto.value.AutoValue;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.Names;
import java.util.Objects;
import javax.lang.model.element.ElementKind;
/**
* The lock expression of an {@code @GuardedBy} annotation.
*
* @author cushon@google.com (Liam Miller-Cushon)
*/
public abstract class GuardedByExpression {
public abstract Kind kind();
public abstract Symbol sym();
public abstract Type type();
static final String ENCLOSING_INSTANCE_NAME = "outer$";
/**
* A 'class' literal: ClassName.class
*/
@AutoValue
public abstract static class ClassLiteral extends GuardedByExpression {
public static ClassLiteral create(Symbol owner) {
return new AutoValue_GuardedByExpression_ClassLiteral(
Kind.CLASS_LITERAL, owner, owner.type);
}
}
/**
* The base expression for a static member select on a class literal (e.g. ClassName.fieldName).
*/
@AutoValue
public abstract static class TypeLiteral extends GuardedByExpression {
public static TypeLiteral create(Symbol owner) {
return new AutoValue_GuardedByExpression_TypeLiteral(
Kind.TYPE_LITERAL, owner, owner.type);
}
}
/**
* A local variable (or parameter), resolved as part of a lock access expression.
*/
@AutoValue
public abstract static class LocalVariable extends GuardedByExpression {
public static LocalVariable create(Symbol owner) {
return new AutoValue_GuardedByExpression_LocalVariable(
Kind.LOCAL_VARIABLE, owner, owner.type);
}
}
/** A guarded by expression that could not be resolved. */
public static class Erroneous extends GuardedByExpression {
private final String guardString;
Erroneous(String guardString) {
this.guardString = guardString;
}
@Override
public Kind kind() {
return Kind.ERROR;
}
@Override
public Symbol sym() {
return null;
}
@Override
public Type type() {
return null;
}
public String guardString() {
return guardString;
}
}
/**
* A simple 'this literal.
*/
// Don't use AutoValue here, since sym and type need to be 'null'. (And since
// it's a singleton we don't need to implement equals() or hashCode()).
public static class ThisLiteral extends GuardedByExpression {
static final ThisLiteral INSTANCE = new ThisLiteral();
@Override
public Kind kind() {
return Kind.THIS;
}
@Override
public Symbol sym() {
return null;
}
@Override
public Type type() {
return null;
}
private ThisLiteral() {}
}
/**
* The member access expression for a field or method.
*/
@AutoValue
public abstract static class Select extends GuardedByExpression {
public abstract GuardedByExpression base();
public static Select create(GuardedByExpression base, Symbol sym, Type type) {
return new AutoValue_GuardedByExpression_Select(Kind.SELECT, sym, type, base);
}
}
/** Makes {@link GuardedByExpression}s. */
public static class Factory {
ThisLiteral thisliteral() {
return ThisLiteral.INSTANCE;
}
/**
* Synthesizes the {@link GuardedByExpression} for an enclosing class access. The
* access is represented as a chain of field accesses from an instance of the current class to
* its enclosing ancestor. At each level, the enclosing class is accessed via a magic 'outer$'
* field.
*
* <p>Example:
*
* <pre>
* <code>
* class Outer {
* final Object lock = new Object();
* class Middle {
* class Inner {
* @GuardedBy("lock") // resolves to 'this.outer$.outer$.lock'
* int x;
* }
* }
* }
* </code>
* </pre>
*
* @param access the inner class where the access occurs.
* @param enclosing the lexically enclosing class.
*/
GuardedByExpression qualifiedThis(Names names, ClassSymbol access, Symbol enclosing) {
GuardedByExpression base = thisliteral();
Symbol curr = access;
do {
curr = curr.owner.enclClass();
if (curr == null) {
break;
}
base = select(base, new EnclosingInstanceSymbol(names, curr));
} while (!curr.equals(enclosing));
checkGuardedBy(curr != null , "Expected an enclosing class.");
return base;
}
private static class EnclosingInstanceSymbol extends VarSymbol {
public EnclosingInstanceSymbol(Names names, Symbol curr) {
super(
Flags.SYNTHETIC,
names.fromString(GuardedByExpression.ENCLOSING_INSTANCE_NAME),
curr.type,
curr);
}
@Override
public int hashCode() {
return Objects.hash(ENCLOSING_INSTANCE_NAME, owner.hashCode());
}
@Override
public boolean equals(Object other) {
if (!(other instanceof VarSymbol)) {
return false;
}
VarSymbol that = (VarSymbol) other;
if (!that.getSimpleName().contentEquals(ENCLOSING_INSTANCE_NAME)) {
return false;
}
return owner.equals(that.owner);
}
}
ClassLiteral classLiteral(Symbol clazz) {
return ClassLiteral.create(clazz);
}
TypeLiteral typeLiteral(Symbol type) {
return TypeLiteral.create(type);
}
Select select(GuardedByExpression base, Symbol member) {
if (member instanceof VarSymbol) {
return select(base, (VarSymbol) member);
}
if (member instanceof MethodSymbol) {
return select(base, (MethodSymbol) member);
}
throw new IllegalStateException(
"Bad select expression: expected symbol " + member.getKind());
}
Select select(GuardedByExpression base, Symbol.VarSymbol member) {
return Select.create(base, member, member.type);
}
Select select(GuardedByExpression base, Symbol.MethodSymbol member) {
return Select.create(base, member, member.getReturnType());
}
GuardedByExpression select(GuardedByExpression base, Select select) {
return Select.create(base, select.sym(), select.type());
}
LocalVariable localVariable(Symbol.VarSymbol varSymbol) {
return LocalVariable.create(varSymbol);
}
Erroneous error(String guardString) {
return new Erroneous(guardString);
}
}
/** {@link GuardedByExpression} kind. */
public static enum Kind {
THIS,
CLASS_LITERAL,
TYPE_LITERAL,
LOCAL_VARIABLE,
SELECT,
ERROR;
}
@Override
public String toString() {
return PrettyPrinter.print(this);
}
public String debugPrint() {
return DebugPrinter.print(this);
}
/**
* Pretty printer for lock expressions.
*/
private static class PrettyPrinter {
public static String print(GuardedByExpression exp) {
StringBuilder sb = new StringBuilder();
pprint(exp, sb);
return sb.toString();
}
private static void pprint(GuardedByExpression exp, StringBuilder sb) {
switch (exp.kind()) {
case CLASS_LITERAL:
sb.append(String.format("%s.class", exp.sym().name));
break;
case THIS:
sb.append("this");
break;
case TYPE_LITERAL:
case LOCAL_VARIABLE:
sb.append(exp.sym().name);
break;
case SELECT:
pprintSelect((Select) exp, sb);
break;
case ERROR:
sb.append(((Erroneous) exp).guardString());
break;
}
}
private static void pprintSelect(Select exp, StringBuilder sb) {
if (exp.sym().name.contentEquals(ENCLOSING_INSTANCE_NAME)) {
GuardedByExpression curr = exp.base();
while (curr.kind() == Kind.SELECT) {
curr = ((Select) curr).base();
if (curr.kind() == Kind.THIS) {
break;
}
}
if (curr.kind() == Kind.THIS) {
sb.append(String.format("%s.this", exp.sym().owner.name));
} else {
pprint(exp.base(), sb);
sb.append(".this$0");
}
} else {
pprint(exp.base(), sb);
sb.append(String.format(".%s", exp.sym().name));
if (exp.sym().getKind() == ElementKind.METHOD) {
sb.append("()");
}
}
}
}
/**
* s-exp pretty printer for lock expressions.
*/
private static class DebugPrinter {
public static String print(GuardedByExpression exp) {
StringBuilder sb = new StringBuilder();
pprint(exp, sb);
return sb.toString();
}
private static void pprint(GuardedByExpression exp, StringBuilder sb) {
switch (exp.kind()) {
case TYPE_LITERAL:
case CLASS_LITERAL:
case LOCAL_VARIABLE:
sb.append(String.format("(%s %s)", exp.kind(), exp.sym()));
break;
case THIS:
sb.append("(THIS)");
break;
case SELECT:
pprintSelect((Select) exp, sb);
break;
case ERROR:
sb.append("(ERROR)");
break;
}
}
private static void pprintSelect(Select exp, StringBuilder sb) {
sb.append(String.format("(%s ", exp.kind()));
pprint(exp.base(), sb);
if (exp.sym().name.contentEquals(ENCLOSING_INSTANCE_NAME)) {
sb.append(String.format(" %s%s)", ENCLOSING_INSTANCE_NAME, exp.sym().owner));
} else {
sb.append(String.format(" %s)", exp.sym()));
}
}
}
}