Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion checker/tests/lock/LockExpressionIsFinal.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ void testGuardedByExpressionIsFinal() {

// Test expressions that are not supported by LockVisitor.ensureExpressionIsEffectivelyFinal
@GuardedBy("java.lang.String.class") Object guarded4;
// :: error: (expression.unparsable)
// :: error: (lock.expression.not.final)
@GuardedBy("c1.getFieldPure(b ? c1 : o1, c1)") Object guarded5;

@GuardedBy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.CharLiteralExpr;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.ConditionalExpr;
import com.github.javaparser.ast.expr.DoubleLiteralExpr;
import com.github.javaparser.ast.expr.EnclosedExpr;
import com.github.javaparser.ast.expr.Expression;
Expand Down Expand Up @@ -68,12 +69,15 @@
import org.checkerframework.dataflow.expression.SuperReference;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.dataflow.expression.UnaryOperation;
import org.checkerframework.dataflow.expression.Unknown;
import org.checkerframework.dataflow.expression.ValueLiteral;
import org.checkerframework.framework.source.DiagMessage;
import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
import org.checkerframework.framework.util.typeinference8.types.InferenceFactory;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Resolver;
import org.checkerframework.javacutil.TypeKindUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.trees.TreeBuilder;
import org.plumelib.util.CollectionsPlume;
Expand Down Expand Up @@ -230,7 +234,28 @@ private static class ExpressionToJavaExpressionVisitor
private final TypeMirror stringTypeMirror;

/** The primitive boolean type. */
private final TypeMirror booleanTypeMirror;
private final TypeMirror primitiveBooleanTypeMirror;

/** The primitive boolean type. */
private final TypeMirror primitiveByteTypeMirror;

/** The primitive boolean type. */
private final TypeMirror primitiveCharTypeMirror;

/** The primitive boolean type. */
private final TypeMirror primitiveDoubleTypeMirror;

/** The primitive boolean type. */
private final TypeMirror primitiveFloatTypeMirror;

/** The primitive boolean type. */
private final TypeMirror primitiveIntTypeMirror;

/** The primitive boolean type. */
private final TypeMirror primitiveLongTypeMirror;

/** The primitive boolean type. */
private final TypeMirror primitiveShortTypeMirror;

/** The enclosing type. Used to look up unqualified method, field, and class names. */
private final TypeMirror enclosingType;
Expand Down Expand Up @@ -273,7 +298,14 @@ private ExpressionToJavaExpressionVisitor(
this.env = env;
this.types = env.getTypeUtils();
this.stringTypeMirror = ElementUtils.getTypeElement(env, String.class).asType();
this.booleanTypeMirror = types.getPrimitiveType(TypeKind.BOOLEAN);
this.primitiveBooleanTypeMirror = types.getPrimitiveType(TypeKind.BOOLEAN);
this.primitiveByteTypeMirror = types.getPrimitiveType(TypeKind.BYTE);
this.primitiveCharTypeMirror = types.getPrimitiveType(TypeKind.CHAR);
this.primitiveDoubleTypeMirror = types.getPrimitiveType(TypeKind.DOUBLE);
this.primitiveFloatTypeMirror = types.getPrimitiveType(TypeKind.FLOAT);
this.primitiveIntTypeMirror = types.getPrimitiveType(TypeKind.INT);
this.primitiveLongTypeMirror = types.getPrimitiveType(TypeKind.LONG);
this.primitiveShortTypeMirror = types.getPrimitiveType(TypeKind.SHORT);
this.enclosingType = enclosingType;
this.thisReference = thisReference;
this.parameters = parameters;
Expand Down Expand Up @@ -977,7 +1009,7 @@ public JavaExpression visit(BinaryExpr expr, Void aVoid) {
type = stringTypeMirror;
} else if (COMPARISON_OPERATORS.contains(op)) {
if (types.isSubtype(leftType, rightType) || types.isSubtype(rightType, leftType)) {
type = booleanTypeMirror;
type = primitiveBooleanTypeMirror;
} else {
// Don't fall through, issue an error immediately instead.
throw new ParseRuntimeException(
Expand All @@ -999,6 +1031,100 @@ public JavaExpression visit(BinaryExpr expr, Void aVoid) {
type, javaParserBinaryOperatorToTreeKind(expr.getOperator()), leftJe, rightJe);
}

@Override
public JavaExpression visit(ConditionalExpr expr, Void aVoid) {
// Determine the type from JLS sections 15.25.1, 15.25.2, and 15.25.3.
JavaExpression thenJe = expr.getThenExpr().accept(this, null);
JavaExpression elseJe = expr.getElseExpr().accept(this, null);
TypeMirror thenType = thenJe.getType();
TypeMirror elseType = elseJe.getType();
TypeMirror type;
if (types.isSameType(thenType, elseType)) {
type = thenType;
} else {
// This block handles section 15.25.1.
TypeKind thenPrimitiveTypeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(thenType);
TypeKind elsePrimitiveTypeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(elseType);
if (thenPrimitiveTypeKind == TypeKind.BOOLEAN
&& elsePrimitiveTypeKind == TypeKind.BOOLEAN) {
// The types are not equal, so one is `boolean` and the other is `Boolean`.
type = primitiveBooleanTypeMirror;
} else if (TypeKindUtils.isNumeric(thenPrimitiveTypeKind)
&& TypeKindUtils.isNumeric(elsePrimitiveTypeKind)) {
// This block handles section 15.25.2.
if (thenPrimitiveTypeKind == elsePrimitiveTypeKind) {
// The types are not equal, so one is a primitive and the other is a boxed primitive.
// Use the primitive.
type = TypesUtils.isPrimitive(thenType) ? thenType : elseType;
} else if ((thenPrimitiveTypeKind == TypeKind.BYTE
&& elsePrimitiveTypeKind == TypeKind.SHORT)
|| (thenPrimitiveTypeKind == TypeKind.SHORT
&& elsePrimitiveTypeKind == TypeKind.BYTE)) {
type = primitiveShortTypeMirror;
}
// At this point, the JLS says to look at the values of constants. I'm skipping that.
else {
TypeKind promoted =
TypeKindUtils.numericPromotion(thenPrimitiveTypeKind, elsePrimitiveTypeKind, true);
type = numericTypeKindToTypeMirror(promoted);
}
} else {
// This block handles section 15.25.3.
TypeKind thenTypeKind = thenType.getKind();
TypeKind elseTypeKind = thenType.getKind();
if (thenTypeKind == TypeKind.NULL) {
type = elseType;
} else if (elseTypeKind == TypeKind.NULL) {
type = thenType;
} else {
// If either type is numeric, box it.
if (TypesUtils.isPrimitive(thenType)) {
thenType =
types.getDeclaredType(
types.boxedClass((javax.lang.model.type.PrimitiveType) thenType));
}
if (TypesUtils.isPrimitive(elseType)) {
elseType =
types.getDeclaredType(
types.boxedClass((javax.lang.model.type.PrimitiveType) elseType));
}
// Now, "the type ... is the result of applying capture conversion to lub(T1, T2)."
type = types.capture(InferenceFactory.lub(env, thenType, elseType));
}
}
}
// Now `type` is set.
return new Unknown(type, expr.toString());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return a new JavaExpression for conditional expressions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does such an expression exist? I was trying to avoid implementing it if possible, though I could do so.

}

/**
* Given a numeric primitive TypeMirror, return the corresponding TypeMirror.
*
* @param tk a type kind
* @return the corresponding TypeMirror
*/
private TypeMirror numericTypeKindToTypeMirror(TypeKind tk) {
switch (tk) {
case BYTE:
return primitiveByteTypeMirror;
case CHAR:
return primitiveCharTypeMirror;
case DOUBLE:
return primitiveDoubleTypeMirror;
case FLOAT:
return primitiveFloatTypeMirror;
case INT:
return primitiveIntTypeMirror;
case LONG:
return primitiveLongTypeMirror;
case SHORT:
return primitiveShortTypeMirror;

default:
throw new BugInCF("Not numeric: " + tk);
}
}

/**
* Convert a JavaParser binary operator to a TreeKind.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ public static boolean isFloatingPoint(TypeKind typeKind) {
* @param typeKind a type kind
* @return true if the argument is a primitive numeric type kind
*/
public static boolean isNumeric(TypeKind typeKind) {
public static boolean isNumeric(@Nullable TypeKind typeKind) {
if (typeKind == null) {
return false;
}
switch (typeKind) {
case BYTE:
case CHAR:
Expand Down Expand Up @@ -311,4 +314,45 @@ public static long maxValue(TypeKind tk) {
throw new BugInCF(tk + " does not have a maximum value");
}
}

/**
* Performs numeric promotion, as defined by JLS section 5.6.
*
* @param tk1 the first type kind, which must be numeric primitive
* @param tk2 the second type kind, which must be numeric primitive
* @param choiceContext true if the expressions are the second or third arguments of a ternary
* conditional expression, or if from a standalone switch expression all of whose values are
* numeric
* @return the numeric promotion of the two arguments
*/
public static TypeKind numericPromotion(TypeKind tk1, TypeKind tk2, boolean choiceContext) {
assert isNumeric(tk1);
assert isNumeric(tk2);

if (tk1 == TypeKind.DOUBLE || tk2 == TypeKind.DOUBLE) {
return TypeKind.DOUBLE;
} else if (tk1 == TypeKind.FLOAT || tk2 == TypeKind.FLOAT) {
return TypeKind.FLOAT;
} else if (tk1 == TypeKind.LONG || tk2 == TypeKind.LONG) {
return TypeKind.LONG;
} else if (!choiceContext) {
return TypeKind.INT;
}
// The below should also look at the values of constant expressions. (But the signature of this
// method does not provide such information.)
else if (tk1 == TypeKind.INT || tk2 == TypeKind.INT) {
return TypeKind.INT;
} else if (tk1 == TypeKind.SHORT || tk2 == TypeKind.SHORT) {
return TypeKind.SHORT;
} else if (tk1 == TypeKind.CHAR || tk2 == TypeKind.CHAR) {
return TypeKind.CHAR;
}
// Byte is preferred over CHAR if all values fit in byte; but this method doesn't have access to
// constant values.
else if (tk1 == TypeKind.BYTE || tk2 == TypeKind.BYTE) {
return TypeKind.BYTE;
} else {
return TypeKind.INT;
}
}
}