package org.checkerframework.checker.signedness;
import com.sun.source.tree.*;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TreePath;
import javax.lang.model.type.TypeKind;
import org.checkerframework.checker.signedness.qual.Signed;
import org.checkerframework.checker.signedness.qual.Unsigned;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.javacutil.TreeUtils;
/**
* The SignednessVisitor enforces the Signedness Checker rules. These rules are described in detail
* in the Checker Framework Manual.
*
* @checker_framework.manual #signedness-checker Signedness Checker
*/
public class SignednessVisitor extends BaseTypeVisitor<SignednessAnnotatedTypeFactory> {
public SignednessVisitor(BaseTypeChecker checker) {
super(checker);
}
/** @return true iff node is a mask operation (& or |) */
private boolean isMask(Tree node) {
Kind kind = node.getKind();
return kind == Kind.AND || kind == Kind.OR;
}
/** @return type of a primitive cast, or null if not primitive */
private PrimitiveTypeTree primitiveTypeCast(Tree node) {
if (node.getKind() != Kind.TYPE_CAST) {
return null;
}
TypeCastTree cast = (TypeCastTree) node;
Tree castType = cast.getType();
// All types should be wrapped in some annotation
if (castType.getKind() != Kind.ANNOTATED_TYPE) {
return null;
}
AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) castType;
ExpressionTree underlyingType = annotatedType.getUnderlyingType();
if (underlyingType.getKind() != Kind.PRIMITIVE_TYPE) {
return null;
}
return (PrimitiveTypeTree) underlyingType;
}
/** @return true iff expr is a literal */
private boolean isLiteral(ExpressionTree expr) {
return expr instanceof LiteralTree;
}
/**
* @param obj either an Integer or a Long
* @return the long value of obj
*/
private long getLong(Object obj) {
if (obj instanceof Integer) {
Integer intObj = (Integer) obj;
return intObj.longValue();
} else {
return (long) obj;
}
}
/**
* Given a masking operation of the form {@code expr & maskLit} or {@code expr | maskLit},
* return true iff the masking operation results in the same output regardless of the value of
* the shiftAmount most significant bits of expr. This is if the shiftAmount most significant
* bits of mask are 0 for AND, and 1 for OR. For example, assuming that shiftAmount is 4, the
* following is true about AND and OR masks:
*
* <p>{@code expr & 0x0... == 0x0... ;}
*
* <p>{@code expr | 0xF... == 0xF... ;}
*
* @param maskKind the kind of mask (AND or OR)
* @param shiftAmountLit the LiteralTree whose value is shiftAmount
* @param maskLit the LiteralTree whose value is mask
* @return true iff the shiftAmount most significant bits of mask are 0 for AND, and 1 for OR
*/
private boolean maskIgnoresMSB(Kind maskKind, LiteralTree shiftAmountLit, LiteralTree maskLit) {
long shiftAmount = getLong(shiftAmountLit.getValue());
long mask = getLong(maskLit.getValue());
// Shift the shiftAmount most significant bits to become the shiftAmount least significant bits, zeroing out the
// rest.
if (maskLit.getKind() != Kind.LONG_LITERAL) {
mask <<= 32;
}
mask >>>= (64 - shiftAmount);
if (maskKind == Kind.AND) {
// Check that the shiftAmount most significant bits of the mask were 0.
return mask == 0;
} else if (maskKind == Kind.OR) {
// Check that the shiftAmount most significant bits of the mask were 1.
return mask == (1 << shiftAmount) - 1;
} else {
// This shouldn't be possible.
throw new RuntimeException("Invalid Masking Operation");
}
}
/**
* Given a casted right shift of the form {@code (type) (baseExpr >> shiftAmount)} or {@code
* (type) (baseExpr >>> shiftAmount)}, return true iff the expression's value is the same
* regardless of the type of right shift (signed or unsigned). This is true if the cast ignores
* the shiftAmount most significant bits of the shift result -- that is, if the cast ignores all
* the new bits that the right shift introduced on the left.
*
* <p>For example, the function returns true for
*
* <pre>{@code (short) (myInt >> 16)}</pre>
*
* and for
*
* <pre>{@code (short) (myInt >>> 16)}</pre>
*
* because these two expressions are guaranteed to have the same result.
*
* @param shiftTypeKind the kind of the type of the shift literal (BYTE, CHAR, SHORT, INT, or
* LONG)
* @param castTypeKind the kind of the cast target type (BYTE, CHAR, SHORT, INT, or LONG)
* @param shiftAmountLit the LiteralTree whose value is shiftAmount
* @return true iff introduced bits are discarded
*/
private boolean castIgnoresMSB(
TypeKind shiftTypeKind, TypeKind castTypeKind, LiteralTree shiftAmountLit) {
// Determine number of bits in the shift type, note shifts upcast to int.
// Also determine the shift amount as it is dependent on the shift type.
long shiftBits;
long shiftAmount;
switch (shiftTypeKind) {
case INT:
shiftBits = 32;
// When the LHS of the shift is an int, the 5 lower order bits of the RHS are used.
shiftAmount = 0x1F & getLong(shiftAmountLit.getValue());
break;
case LONG:
shiftBits = 64;
// When the LHS of the shift is a long, the 6 lower order bits of the RHS are used.
shiftAmount = 0x3F & getLong(shiftAmountLit.getValue());
break;
default:
throw new RuntimeException("Invalid shift type");
}
// Determine number of bits in the cast type
long castBits;
switch (castTypeKind) {
case BYTE:
castBits = 8;
break;
case CHAR:
castBits = 8;
break;
case SHORT:
castBits = 16;
break;
case INT:
castBits = 32;
break;
case LONG:
castBits = 64;
break;
default:
throw new RuntimeException("Invalid cast target");
}
long bitsDiscarded = shiftBits - castBits;
return shiftAmount <= bitsDiscarded || shiftAmount == 0;
}
/**
* Determines if a right shift operation, {@code >>} or {@code >>>}, is masked with a masking
* operation of the form {@code shiftExpr & maskLit} or {@code shiftExpr | maskLit} such that
* the mask renders the shift signedness ({@code >>} vs {@code >>>}) irrelevent by destroying
* the bits duplicated into the shift result. For example, the following pairs of right shifts
* on {@code byte b} both produce the same results under any input, because of their masks:
*
* <p>{@code (b >> 4) & 0x0F == (b >>> 4) & 0x0F;}
*
* <p>{@code (b >> 4) | 0xF0 == (b >>> 4) | 0xF0;}
*
* @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2}
* @return true iff the right shift is masked such that a signed or unsigned right shift has the
* same effect
*/
private boolean isMaskedShift(BinaryTree shiftExpr) {
// enclosing is the operation or statement that immediately contains shiftExpr
Tree enclosing;
// enclosingChild is the top node in the chain of nodes from shiftExpr to parent
Tree enclosingChild;
{
TreePath parentPath = visitorState.getPath().getParentPath();
enclosing = parentPath.getLeaf();
enclosingChild = enclosing;
// Strip away all parentheses from the shift operation
while (enclosing.getKind() == Kind.PARENTHESIZED) {
parentPath = parentPath.getParentPath();
enclosingChild = enclosing;
enclosing = parentPath.getLeaf();
}
}
if (!isMask(enclosing)) {
return false;
}
BinaryTree maskExpr = (BinaryTree) enclosing;
ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand();
// Determine which child of maskExpr leads to shiftExpr. The other one is the mask.
ExpressionTree mask =
maskExpr.getRightOperand() == enclosingChild
? maskExpr.getLeftOperand()
: maskExpr.getRightOperand();
// Strip away the parentheses from the mask if any exist
mask = TreeUtils.skipParens(mask);
if (!isLiteral(shiftAmountExpr) || !isLiteral(mask)) {
return false;
}
LiteralTree shiftLit = (LiteralTree) shiftAmountExpr;
LiteralTree maskLit = (LiteralTree) mask;
return maskIgnoresMSB(maskExpr.getKind(), shiftLit, maskLit);
}
/**
* Determines if a right shift operation, {@code >>} or {@code >>>}, is type casted such that
* the cast renders the shift signedness ({@code >>} vs {@code >>>}) irrelevent by discarding
* the bits duplicated into the shift result. For example, the following pair of right shifts on
* {@code short s} both produce the same results under any input, because of type casting:
*
* <p>{@code (byte)(s >> 8) == (byte)(b >>> 8);}
*
* @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2}
* @return true iff the right shift is type casted such that a signed or unsigned right shift
* has the same effect
*/
private boolean isCastedShift(BinaryTree shiftExpr) {
// enclosing is the operation or statement that immediately contains shiftExpr
Tree enclosing;
{
TreePath parentPath = visitorState.getPath().getParentPath();
enclosing = parentPath.getLeaf();
// Strip away all parentheses from the shift operation
while (enclosing.getKind() == Kind.PARENTHESIZED) {
parentPath = parentPath.getParentPath();
enclosing = parentPath.getLeaf();
}
}
PrimitiveTypeTree castPrimitiveType = primitiveTypeCast(enclosing);
if (castPrimitiveType == null) {
return false;
}
TypeKind castTypeKind = castPrimitiveType.getPrimitiveTypeKind();
// Determine the type of the shift result
TypeKind shiftTypeKind =
atypeFactory.getAnnotatedType(shiftExpr).getUnderlyingType().getKind();
// Determine shift literal
ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand();
if (!isLiteral(shiftAmountExpr)) {
return false;
}
LiteralTree shiftLit = (LiteralTree) shiftAmountExpr;
return castIgnoresMSB(shiftTypeKind, castTypeKind, shiftLit);
}
/**
* Enforces the following rules on binary operations involving Unsigned and Signed types:
*
* <ul>
* <li>Do not allow any Unsigned types in {@literal {/, %}} operations.
* <li>Do not allow signed right shift {@literal {>>}} on an Unsigned type.
* <li>Do not allow unsigned right shift {@literal {>>>}} on a Signed type.
* <li>Allow any left shift {@literal {<<}}.
* <li>Do not allow non-equality comparisons {@literal {<, <=, >, >=}} on Unsigned types.
* <li>Do not allow the mixing of Signed and Unsigned types.
* </ul>
*/
@Override
public Void visitBinary(BinaryTree node, Void p) {
ExpressionTree leftOp = node.getLeftOperand();
ExpressionTree rightOp = node.getRightOperand();
AnnotatedTypeMirror leftOpType = atypeFactory.getAnnotatedType(leftOp);
AnnotatedTypeMirror rightOpType = atypeFactory.getAnnotatedType(rightOp);
Kind kind = node.getKind();
switch (kind) {
case DIVIDE:
case REMAINDER:
if (leftOpType.hasAnnotation(Unsigned.class)) {
checker.report(Result.failure("operation.unsignedlhs", kind), leftOp);
} else if (rightOpType.hasAnnotation(Unsigned.class)) {
checker.report(Result.failure("operation.unsignedrhs", kind), rightOp);
}
break;
case RIGHT_SHIFT:
if (leftOpType.hasAnnotation(Unsigned.class)
&& !isMaskedShift(node)
&& !isCastedShift(node)) {
checker.report(Result.failure("shift.signed", kind), leftOp);
}
break;
case UNSIGNED_RIGHT_SHIFT:
if (leftOpType.hasAnnotation(Signed.class)
&& !isMaskedShift(node)
&& !isCastedShift(node)) {
checker.report(Result.failure("shift.unsigned", kind), leftOp);
}
break;
case LEFT_SHIFT:
break;
case GREATER_THAN:
case GREATER_THAN_EQUAL:
case LESS_THAN:
case LESS_THAN_EQUAL:
if (leftOpType.hasAnnotation(Unsigned.class)) {
checker.report(Result.failure("comparison.unsignedlhs"), leftOp);
} else if (rightOpType.hasAnnotation(Unsigned.class)) {
checker.report(Result.failure("comparison.unsignedrhs"), rightOp);
}
break;
case EQUAL_TO:
case NOT_EQUAL_TO:
if (leftOpType.hasAnnotation(Unsigned.class)
&& rightOpType.hasAnnotation(Signed.class)) {
checker.report(Result.failure("comparison.mixed.unsignedlhs"), node);
} else if (leftOpType.hasAnnotation(Signed.class)
&& rightOpType.hasAnnotation(Unsigned.class)) {
checker.report(Result.failure("comparison.mixed.unsignedrhs"), node);
}
break;
default:
if (leftOpType.hasAnnotation(Unsigned.class)
&& rightOpType.hasAnnotation(Signed.class)) {
checker.report(Result.failure("operation.mixed.unsignedlhs", kind), node);
} else if (leftOpType.hasAnnotation(Signed.class)
&& rightOpType.hasAnnotation(Unsigned.class)) {
checker.report(Result.failure("operation.mixed.unsignedrhs", kind), node);
}
break;
}
return super.visitBinary(node, p);
}
/** @return a string representation of kind, with trailing _ASSIGNMENT stripped off if any */
private String kindWithOutAssignment(Kind kind) {
String result = kind.toString();
if (result.endsWith("_ASSIGNMENT")) {
return result.substring(0, result.length() - "_ASSIGNMENT".length());
} else {
return result;
}
}
/**
* Enforces the following rules on compound assignments involving Unsigned and Signed types:
*
* <ul>
* <li>Do not allow any Unsigned types in {@literal {/=, %=}} assignments.
* <li>Do not allow signed right shift {@literal {>>=}} to assign to an Unsigned type.
* <li>Do not allow unsigned right shift {@literal {>>>=}} to assign to a Signed type.
* <li>Allow any left shift {@literal {<<=}} assignment.
* <li>Do not allow mixing of Signed and Unsigned types.
* </ul>
*/
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
ExpressionTree var = node.getVariable();
ExpressionTree expr = node.getExpression();
AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(var);
AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr);
Kind kind = node.getKind();
switch (kind) {
case DIVIDE_ASSIGNMENT:
case REMAINDER_ASSIGNMENT:
if (varType.hasAnnotation(Unsigned.class)) {
checker.report(
Result.failure(
"compound.assignment.unsigned.variable",
kindWithOutAssignment(kind)),
var);
} else if (exprType.hasAnnotation(Unsigned.class)) {
checker.report(
Result.failure(
"compound.assignment.unsigned.expression",
kindWithOutAssignment(kind)),
expr);
}
break;
case RIGHT_SHIFT_ASSIGNMENT:
if (varType.hasAnnotation(Unsigned.class)) {
checker.report(
Result.failure(
"compound.assignment.shift.signed",
kindWithOutAssignment(kind),
"unsigned"),
var);
}
break;
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
if (varType.hasAnnotation(Signed.class)) {
checker.report(
Result.failure(
"compound.assignment.shift.unsigned",
kindWithOutAssignment(kind),
"signed"),
var);
}
break;
case LEFT_SHIFT_ASSIGNMENT:
break;
default:
if (varType.hasAnnotation(Unsigned.class) && exprType.hasAnnotation(Signed.class)) {
checker.report(
Result.failure(
"compound.assignment.mixed.unsigned.variable",
kindWithOutAssignment(kind)),
expr);
} else if (varType.hasAnnotation(Signed.class)
&& exprType.hasAnnotation(Unsigned.class)) {
checker.report(
Result.failure(
"compound.assignment.mixed.unsigned.expression",
kindWithOutAssignment(kind)),
expr);
}
break;
}
return super.visitCompoundAssignment(node, p);
}
}