-
Notifications
You must be signed in to change notification settings - Fork 26
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
Add new rule to check if null checking is being done using short circuit #238
base: main
Are you sure you want to change the base?
Conversation
Nice! This is one of the surprising behaviors of the Terraform language, and it's great to be able to find the possible error statically. Furthermore, it would be nice to be able to auto-fix the issue. I'll review this again later. |
// Try to get variable name from LHS | ||
if scopeTraversalExpr, ok := binaryOpExpr.LHS.(*hclsyntax.ScopeTraversalExpr); ok { | ||
if len(scopeTraversalExpr.Traversal) > 0 { | ||
varName = scopeTraversalExpr.Traversal.RootName() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RootName
will only return var
for addresses like var.foo
. So there are false positives in expressions like:
var.foo != null && var.bar ? 1 : 0
|
||
// referencesNullCheckedVar checks if the right side expression references the same variable that was null checked | ||
func referencesNullCheckedVar(nullCheck, expr hcl.Expression) bool { | ||
// Get the variable name from the null check |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of searching for the variable reference again in referencesNullCheckedVar
, it would be better for isNullCheck
to return it.
// isNullCheck determines if an expression is checking for null | ||
func isNullCheck(expr hcl.Expression) bool { | ||
if binaryOpExpr, ok := expr.(*hclsyntax.BinaryOpExpr); ok { | ||
if binaryOpExpr.Op == hclsyntax.OpEqual || binaryOpExpr.Op == hclsyntax.OpNotEqual { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strictly speaking, the only valid combinations are OpLogicalAnd
with OpNotEqual
and OpLogicalOr
with OpEqual
. However, in practice, an expression like var.obj == null && var.obj.enabled
is rarely useful, so this may be sufficient as it is.
if referencesNullCheckedVar(binaryOpExpr.LHS, binaryOpExpr.RHS) { | ||
if err := runner.EmitIssue( | ||
r, | ||
"Short-circuit evaluation is not supported in Terraform. Use a conditional expression (condition ? true : false) instead.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's debatable whether it's appropriate to always suggest a conditional expression as a fix. According to hashicorp/terraform#24128 there are a variety of different solutions.
As you already mentioned in the documentation, another option is to use the try
function.
@@ -22,6 +22,7 @@ var PresetRules = map[string][]tflint.Rule{ | |||
NewTerraformUnusedDeclarationsRule(), | |||
NewTerraformUnusedRequiredProvidersRule(), | |||
NewTerraformWorkspaceRemoteRule(), | |||
NewTerraformNoShortCircuitEvaluationRule(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this rule should be added to the recommended preset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an upcoming version of Terraform eliminates this footgun that's a great reason for this not to be enabled by default. Users of Terraform who are sufficiently up to date should be free to use the more familiar/concise syntax. Authors of reusable modules need to be more concerned about backward compatibility.
@@ -0,0 +1,60 @@ | |||
# terraform_no_short_circuit_evaluation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
terraform_logical_operator_short_circuit
would be a better name. Most of the other rules are anti-patterns but none uses a no
prefix.
@@ -0,0 +1,60 @@ | |||
# terraform_no_short_circuit_evaluation | |||
|
|||
Disallow using logical operators (`&&`, `||`) with null checks that could lead to errors due to lack of short-circuit evaluation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disallow using logical operators (`&&`, `||`) with null checks that could lead to errors due to lack of short-circuit evaluation. | |
Detects usage of logical operators (`&&`, `||`) that could lead to errors because of their lack of support for short-circuit evaluation. |
Doesn't seem like null is an essential part of the description, you might be looking at length or any other intended pre-condition.
|
||
Unlike many programming languages, Terraform's logical operators (`&&` and `||`) do not short-circuit. This means that in an expression like `var.obj != null && var.obj.enabled`, both sides will be evaluated even if `var.obj` is null, which will result in an error. | ||
|
||
This is a common source of confusion for users coming from other programming languages where short-circuit evaluation is standard behavior. The issue is particularly problematic when checking for null before accessing object attributes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
particularly problematic
Compared to what?
|
||
Use nested conditional expressions instead of logical operators when you need short-circuit behavior. For example: | ||
|
||
```hcl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split this into multiple code blocks for readability
try(var.obj.enabled, false) | ||
``` | ||
|
||
For more information, see [hashicorp/terraform#24128](https://github.com/hashicorp/terraform/issues/24128). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is too open-ended for users, too much history to sort through. Instead link to the official docs on this limitation:
https://developer.hashicorp.com/terraform/language/expressions/operators#logical-operators
And then perhaps summarize any version-specific context, e.g. if it only affects Terraform versions earlier than x.
This adds a new rule
terraform_no_short_circuit_evaluation
that checks for attempts to use logical operators (&&
,||
) with null checks that could lead to errors due to lack of short-circuit evaluation.This is one of the most common mistakes I usually see in Terraform for beginners. While this issue was recently fixed in Terraform core (hashicorp/terraform#24128), older Terraform versions don't support short-circuit evaluation. Therefore, it would be helpful to have TFLint check this optionally by enabling this rule.
The rule will warn about code patterns like:
The rule could be enabled by default and included in the "recommended" preset since this is a common source of errors for Terraform users.