Skip to content

Commit

Permalink
fix uber#667 Contract annotations support boolean constraints
Browse files Browse the repository at this point in the history
For example, the following method will behave equivalently to
guava `Preconditions.checkState` with regards to null checks:
```java
@contract("false -> fail")
static void check(boolean pass) {
  if (!pass) throw new RuntimeException();
}
```

This implementation works in a couple of layers:
Firstly, contracts with a single boolean resultin in failure
are handled by inserting a conditional throw node inline following
the annotated method. This provides support for complex boolean
logic input validateion.
Secondly, simple null equal-to and not-equal-to checks are handled
by the existing ContractHandler. These don't support such complex
inputs, however they are able to work correctly with boolean
outputs rather than exclusively `fail` cases.

Note that this PR also fixes an assumption in the `ContractHandler`
which assumed that `null -> true` implied `!null -> false` which
isn't guaranteed unless otherwise specified -- fixing this allows
for several new test cases to work as expected.
I've also updated the ContractHandler to use more precise language
around antecedent nullness, previously null antecedents were
described as nullable. Please let me know if I've misunderstood
the logic, but reframing the values helped me to understand the
logic.
  • Loading branch information
carterkozak committed Oct 10, 2022
1 parent 8b7c174 commit 1b19ebe
Show file tree
Hide file tree
Showing 5 changed files with 673 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,25 +127,44 @@ public TypeMirror classToErrorType(Class<?> klass) {
}

/**
* Extend the CFG to throw an exception if the passed expression node evaluates to false.
* Extend the CFG to throw an exception if the passed expression node evaluates to {@code
* false}.
*
* @param booleanExpressionNode a CFG Node representing a boolean expression.
* @param errorType the type of the exception to throw if booleanExpressionNode evaluates to
* false.
* {@code false}.
*/
public void insertThrowOnFalse(Node booleanExpressionNode, TypeMirror errorType) {
insertThrowOn(false, booleanExpressionNode, errorType);
}

/**
* Extend the CFG to throw an exception if the passed expression node evaluates to {@code true}.
*
* @param booleanExpressionNode a CFG Node representing a boolean expression.
* @param errorType the type of the exception to throw if booleanExpressionNode evaluates to
* {@code true}.
*/
public void insertThrowOnTrue(Node booleanExpressionNode, TypeMirror errorType) {
insertThrowOn(true, booleanExpressionNode, errorType);
}

private void insertThrowOn(boolean throwOn, Node booleanExpressionNode, TypeMirror errorType) {
Tree tree = booleanExpressionNode.getTree();
Preconditions.checkArgument(
tree instanceof ExpressionTree,
"Argument booleanExpressionNode must represent a boolean expression");
ExpressionTree booleanExpressionTree = (ExpressionTree) booleanExpressionNode.getTree();
Preconditions.checkNotNull(booleanExpressionTree);
Label falsePreconditionEntry = new Label();
Label preconditionEntry = new Label();
Label endPrecondition = new Label();
this.scan(booleanExpressionTree, (Void) null);
ConditionalJump cjump = new ConditionalJump(endPrecondition, falsePreconditionEntry);
ConditionalJump cjump =
new ConditionalJump(
throwOn ? preconditionEntry : endPrecondition,
throwOn ? endPrecondition : preconditionEntry);
this.extendWithExtendedNode(cjump);
this.addLabelForNextNode(falsePreconditionEntry);
this.addLabelForNextNode(preconditionEntry);
ExtendedNode exNode =
this.extendWithNodeWithException(
new ThrowNode(
Expand Down
Loading

0 comments on commit 1b19ebe

Please sign in to comment.