From 176ae6e95f71894e65f316081910b409dae46fce Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 19 Dec 2018 12:38:18 -0800 Subject: [PATCH] core: Detect and reject self-referencing local values We already catch indirect cycles through the normal cycle detector, but we never create self-edges in the graph so we need to handle a direct self-reference separately here. The prior behavior was simply to produce an incorrect result (since the local value wasn't assigned a new value yet). This fixes #18503. --- terraform/eval_local.go | 28 ++++++++++++++++++++++++++-- terraform/eval_local_test.go | 10 ++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/terraform/eval_local.go b/terraform/eval_local.go index bfed17816..bad9ac5b1 100644 --- a/terraform/eval_local.go +++ b/terraform/eval_local.go @@ -4,8 +4,11 @@ import ( "fmt" "github.com/hashicorp/hcl2/hcl" - "github.com/hashicorp/terraform/addrs" "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/lang" + "github.com/hashicorp/terraform/tfdiags" ) // EvalLocal is an EvalNode implementation that evaluates the @@ -17,11 +20,32 @@ type EvalLocal struct { } func (n *EvalLocal) Eval(ctx EvalContext) (interface{}, error) { - val, diags := ctx.EvaluateExpr(n.Expr, cty.DynamicPseudoType, nil) + var diags tfdiags.Diagnostics + + // We ignore diags here because any problems we might find will be found + // again in EvaluateExpr below. + refs, _ := lang.ReferencesInExpr(n.Expr) + for _, ref := range refs { + if ref.Subject == n.Addr { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Self-referencing local value", + Detail: fmt.Sprintf("Local value %s cannot use its own result as part of its expression.", n.Addr), + Subject: ref.SourceRange.ToHCL().Ptr(), + Context: n.Expr.Range().Ptr(), + }) + } + } if diags.HasErrors() { return nil, diags.Err() } + val, moreDiags := ctx.EvaluateExpr(n.Expr, cty.DynamicPseudoType, nil) + diags = diags.Append(moreDiags) + if moreDiags.HasErrors() { + return nil, diags.Err() + } + state := ctx.State() if state == nil { return nil, fmt.Errorf("cannot write local value to nil state") diff --git a/terraform/eval_local_test.go b/terraform/eval_local_test.go index d1cdfdfaa..f862b5e4d 100644 --- a/terraform/eval_local_test.go +++ b/terraform/eval_local_test.go @@ -34,6 +34,11 @@ func TestEvalLocal(t *testing.T) { "", false, }, + { + "Hello, ${local.foo}", + nil, + true, // self-referencing + }, } for _, test := range tests { @@ -64,8 +69,9 @@ func TestEvalLocal(t *testing.T) { ms := ctx.StateState.Module(addrs.RootModuleInstance) gotLocals := ms.LocalValues - wantLocals := map[string]cty.Value{ - "foo": hcl2shim.HCL2ValueFromConfigValue(test.Want), + wantLocals := map[string]cty.Value{} + if test.Want != nil { + wantLocals["foo"] = hcl2shim.HCL2ValueFromConfigValue(test.Want) } if !reflect.DeepEqual(gotLocals, wantLocals) {