Repository: opentofu/opentofu
File: internal/lang/evalchecks/eval_for_each.go (Go)

Code before:
			EvalContext: hclCtx,
		})
		return cty.NullVal(ty), diags
	}

	return forEachVal, diags
}

// performSetTypeChecks checks Set values to ensure all elements are statically defined and not null.
func performSetValueChecks(expr hcl.Expression, hclCtx *hcl.EvalContext, forEachVal cty.Value) (cty.Value, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	ty := forEachVal.Type()

	// Since we're using a multi-error approach, we try to add as much of information as possible. The ElementIterator code below can't iterate on null or unknown values, that's why we test if these conditions are present and return earlier.
	if !forEachVal.IsWhollyKnown() {
		// since we can't use a set values that are unknown, we treat the entire set as unknown
		return cty.UnknownVal(ty), diags
	}

	if forEachVal.IsNull() {
		return forEachVal, diags
	}

	// A set of strings may contain null, which makes it impossible to
	// convert to a map, so we must return an error
	forEachVal, _ = forEachVal.UnmarkDeep()
	it := forEachVal.ElementIterator()
	for it.Next() {
		item, _ := it.Element()
		if item.IsNull() {
			diags = diags.Append(&hcl.Diagnostic{
				Severity:    hcl.DiagError,
				Summary:     "Invalid for_each argument",
				Detail:      `The given "for_each" argument value is unsuitable: "for_each" sets must not contain null values.`,
				Subject:     expr.Range().Ptr(),
				Expression:  expr,
				EvalContext: hclCtx,
			})
			return cty.NullVal(ty), diags
		}
	}
	return forEachVal, diags
}

// markSafeLengthInt allows calling LengthInt on marked values safely
func markSafeLengthInt(val cty.Value) int {
	v, _ := val.UnmarkDeep()
	return v.LengthInt()
}

// Returns some English-language text describing a workaround using the -exclude

Review comment (bug):
I don't think it's correct to return the unmarked version of this value, because the caller of this function then passes the returned value on to another function:

https://github.com/opentofu/opentofu/blob/131b1456cae9c3359148c6b93b909b880e1bd739/internal/lang/evalchecks/eval_for_each.go#L105-L109

...and then that function wants to reject attempts to use a sensitive value:

https://github.com/opentofu/opentofu/blob/131b1456cae9c3359148c6b93b909b880e1bd739/internal/lang/evalchecks/eval_for_each.go#L172-L185

To make sure this change can't affect anything beyond fixing the bug as reported, I'd suggest that we instead change the following line to `it := forEachVal.UnmarkDeep().ElementIterator()`, so that the mark will still be present in all of the other places it previously was.


Code after:
			EvalContext: hclCtx,
		})
		return cty.NullVal(ty), diags
	}

	return forEachVal, diags
}

// performSetTypeChecks checks Set values to ensure all elements are statically defined and not null.
func performSetValueChecks(expr hcl.Expression, hclCtx *hcl.EvalContext, forEachVal cty.Value) (cty.Value, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	ty := forEachVal.Type()

	// Since we're using a multi-error approach, we try to add as much of information as possible. The ElementIterator code below can't iterate on null or unknown values, that's why we test if these conditions are present and return earlier.
	if !forEachVal.IsWhollyKnown() {
		// since we can't use a set values that are unknown, we treat the entire set as unknown
		return cty.UnknownVal(ty), diags
	}

	if forEachVal.IsNull() {
		return forEachVal, diags
	}

	// A set of strings may contain null, which makes it impossible to
	// convert to a map, so we must return an error
	forEachValUnmarked, _ := forEachVal.UnmarkDeep()
	it := forEachValUnmarked.ElementIterator()
	for it.Next() {
		item, _ := it.Element()
		if item.IsNull() {
			diags = diags.Append(&hcl.Diagnostic{
				Severity:    hcl.DiagError,
				Summary:     "Invalid for_each argument",
				Detail:      `The given "for_each" argument value is unsuitable: "for_each" sets must not contain null values.`,
				Subject:     expr.Range().Ptr(),
				Expression:  expr,
				EvalContext: hclCtx,
			})
			return cty.NullVal(ty), diags
		}
	}
	return forEachVal, diags
}

// markSafeLengthInt allows calling LengthInt on marked values safely
func markSafeLengthInt(val cty.Value) int {
	v, _ := val.UnmarkDeep()
	return v.LengthInt()
}

// Returns some English-language text describing a workaround using the -exclude