-
Notifications
You must be signed in to change notification settings - Fork 299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider supporting simple null equality checks in boolean-based Contract annotations #667
Comments
This is an interesting idea! And it seems related to #666, which asks for support of Guava's The only thing is that I'd probably want @lazaroclapp to review a change like this one before we land it (both at a high level and also the code in the PR), and he is on holiday for the next couple of weeks. I am optimistic we would accept a change like this if you want to go ahead and work on the PR, but we will have to wait for his review before it lands. Let me know what you think, and I can give tips / feedback on implementation strategy. |
Thank you for the information, I really appreciate it! The approach used for Guava's Preconditions in #608 is beautifully elegant, however I think there are a few key differences that we may want to consider:
|
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.
Upon investigation, the second case I was worried about seems to work just fine: non-void methods aren't impacted by inserting a conditional throw. I've created PR #669. I'd really appreciate if you (or anyone else) would like to provide feedback, but no rush :-) (disclaimer: I'm not attached to the code, that's just how I prefer to quickly build an understanding of a codebase -- I won't be offended if folks want to throw it out and try something entirely different!) |
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.
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.
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.
Closing since #669 was merged |
Firstly, NullAway is fantastic and I can't thank you enough for building it!
I've adopted
@Contract
annotations in a few places with great success on methods along the lines ofMyPreconditions.checkNotNull(value)
(contractnull -> fail
), however I'd love to see support for boolean parameters when the provided argument is a simple null check e.g.MyPreconditions.checkArgument(obj != null)
(contractfalse -> fail
).In practice, the current limitation means that the following code fails as described inline:
Would you accept a contribution to handle such cases?
Concretely, my proposed solution would be to support boolean values in contracts only when the arg was provided as
arg == null
,null == arg
,arg != null
, ornull != arg
, thus the contract could be understood as a null check. I think this is a reasonable middle-ground without adding boolean-logic-dataflow support for cases where the argument itself is stored elsewhere e.g.Thanks again for your work, any feedback is appreciated :-)
The text was updated successfully, but these errors were encountered: