From 20e757cd0fc00e92b5a24fa84cd6203c4ca7e8b8 Mon Sep 17 00:00:00 2001 From: Engin Polat <118744+polatengin@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:52:37 +0000 Subject: [PATCH 1/7] Add test for base object spread not evaluating unreferenced external input --- .../ParametersTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Bicep.Core.IntegrationTests/ParametersTests.cs b/src/Bicep.Core.IntegrationTests/ParametersTests.cs index 5689be3fb9a..403c64f86d7 100644 --- a/src/Bicep.Core.IntegrationTests/ParametersTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParametersTests.cs @@ -673,6 +673,44 @@ param values array }")); } + [TestMethod] + public void Base_object_spread_should_not_evaluate_unreferenced_external_input() + { + var result = CompilationHelper.CompileParams( + ("parameters.bicepparam", @" + using 'main.bicep' + extends 'shared.bicepparam' + param B = { + ...base.B + something: 'something' + } + "), + ("shared.bicepparam", @" + using none + param external = externalInput('foo', 'bar') + param B = { + parent: 'parent' + } + "), + ("main.bicep", @" + param external object + param B object + ")); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + result.Parameters.Should().HaveJsonAtPath("parameters.B.value", @"{ + ""parent"": ""parent"", + ""something"": ""something"" + }"); + result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]"); + result.Parameters.Should().HaveJsonAtPath("externalInputDefinitions", @"{ + ""foo_0"": { + ""kind"": ""foo"", + ""config"": ""bar"" + } + }"); + } + [TestMethod] public void Decorators_on_using_param_and_extends_statements_should_raise_errors() { From b6aadcbb356f38e5806f66430996379deb2cd49d Mon Sep 17 00:00:00 2001 From: Engin Polat <118744+polatengin@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:52:48 +0000 Subject: [PATCH 2/7] Add test to ensure base array spread does not evaluate unreferenced external input --- .../ParametersTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Bicep.Core.IntegrationTests/ParametersTests.cs b/src/Bicep.Core.IntegrationTests/ParametersTests.cs index 403c64f86d7..608cb1f6c98 100644 --- a/src/Bicep.Core.IntegrationTests/ParametersTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParametersTests.cs @@ -711,6 +711,44 @@ param B object }"); } + [TestMethod] + public void Base_array_spread_should_not_evaluate_unreferenced_external_input() + { + var result = CompilationHelper.CompileParams( + ("parameters.bicepparam", @" + using 'main.bicep' + extends 'shared.bicepparam' + param values = [ + ...base.values + 'child' + ] + "), + ("shared.bicepparam", @" + using none + param enabled = bool(externalInput('feature', 'enabled')) + param values = [ + 'parent' + ] + "), + ("main.bicep", @" + param enabled bool + param values array + ")); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + result.Parameters.Should().HaveJsonAtPath("parameters.values.value", @"[ + ""parent"", + ""child"" + ]"); + result.Parameters.Should().HaveValueAtPath("parameters.enabled.expression", "[bool(externalInputs('feature_0'))]"); + result.Parameters.Should().HaveJsonAtPath("externalInputDefinitions", @"{ + ""feature_0"": { + ""kind"": ""feature"", + ""config"": ""enabled"" + } + }"); + } + [TestMethod] public void Decorators_on_using_param_and_extends_statements_should_raise_errors() { From 79aaf4acebc78e1a36dff2a43b9fc2d68c5e52ea Mon Sep 17 00:00:00 2001 From: Engin Polat <118744+polatengin@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:53:04 +0000 Subject: [PATCH 3/7] Add test to ensure base property accesses do not evaluate unreferenced external input --- .../ParametersTests.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Bicep.Core.IntegrationTests/ParametersTests.cs b/src/Bicep.Core.IntegrationTests/ParametersTests.cs index 608cb1f6c98..25722a2f9cf 100644 --- a/src/Bicep.Core.IntegrationTests/ParametersTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParametersTests.cs @@ -749,6 +749,36 @@ param values array }"); } + [TestMethod] + public void Base_property_accesses_should_not_evaluate_unreferenced_external_input() + { + var result = CompilationHelper.CompileParams( + ("parameters.bicepparam", @" + using 'main.bicep' + extends 'shared.bicepparam' + param dotAccess = base.selected.value + param bracketAccess = base['selected'].value + "), + ("shared.bicepparam", @" + using none + param external = externalInput('foo') + param selected = { + value: 'parent' + } + "), + ("main.bicep", @" + param external object + param selected object + param dotAccess string + param bracketAccess string + ")); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + result.Parameters.Should().HaveValueAtPath("parameters.dotAccess.value", "parent"); + result.Parameters.Should().HaveValueAtPath("parameters.bracketAccess.value", "parent"); + result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]"); + } + [TestMethod] public void Decorators_on_using_param_and_extends_statements_should_raise_errors() { From 7b1c9fe66f2525172805755ba889b51ea2cc6d86 Mon Sep 17 00:00:00 2001 From: Engin Polat <118744+polatengin@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:53:18 +0000 Subject: [PATCH 4/7] Add test to ensure nested base spreads do not evaluate unreferenced external input --- .../ParametersTests.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/Bicep.Core.IntegrationTests/ParametersTests.cs b/src/Bicep.Core.IntegrationTests/ParametersTests.cs index 25722a2f9cf..8b0c4fcf6cd 100644 --- a/src/Bicep.Core.IntegrationTests/ParametersTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParametersTests.cs @@ -779,6 +779,47 @@ param bracketAccess string result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]"); } + [TestMethod] + public void Nested_base_spreads_should_not_evaluate_unreferenced_external_input() + { + var result = CompilationHelper.CompileParams( + ("parameters.bicepparam", @" + using 'main.bicep' + extends 'middle.bicepparam' + param tags = { + ...base.tags + child: 'child' + } + "), + ("middle.bicepparam", @" + using none + extends 'base.bicepparam' + param tags = { + ...base.tags + middle: 'middle' + } + "), + ("base.bicepparam", @" + using none + param external = externalInput('foo') + param tags = { + parent: 'parent' + } + "), + ("main.bicep", @" + param external object + param tags object + ")); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + result.Parameters.Should().HaveJsonAtPath("parameters.tags.value", @"{ + ""parent"": ""parent"", + ""middle"": ""middle"", + ""child"": ""child"" + }"); + result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]"); + } + [TestMethod] public void Decorators_on_using_param_and_extends_statements_should_raise_errors() { From 726ef9e9362cf8e5f061e19859fe8b743ef4e17b Mon Sep 17 00:00:00 2001 From: Engin Polat <118744+polatengin@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:53:29 +0000 Subject: [PATCH 5/7] Add test to ensure base spread preserves function bindings in selected parent parameter --- .../ParametersTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Bicep.Core.IntegrationTests/ParametersTests.cs b/src/Bicep.Core.IntegrationTests/ParametersTests.cs index 8b0c4fcf6cd..1f1ce6f97d3 100644 --- a/src/Bicep.Core.IntegrationTests/ParametersTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParametersTests.cs @@ -820,6 +820,38 @@ param tags object result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]"); } + [TestMethod] + public void Base_spread_should_preserve_function_bindings_in_selected_parent_parameter() + { + var result = CompilationHelper.CompileParams( + ("parameters.bicepparam", @" + using 'main.bicep' + extends 'shared.bicepparam' + param tags = { + ...base.tags + child: 'child' + } + "), + ("shared.bicepparam", @" + using none + param external = externalInput('foo') + param tags = { + parent: toLower('PARENT') + } + "), + ("main.bicep", @" + param external object + param tags object + ")); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + result.Parameters.Should().HaveJsonAtPath("parameters.tags.value", @"{ + ""parent"": ""parent"", + ""child"": ""child"" + }"); + result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]"); + } + [TestMethod] public void Decorators_on_using_param_and_extends_statements_should_raise_errors() { From 8bbc307cac2eaad5ba937a691b48fce03fbe229f Mon Sep 17 00:00:00 2001 From: Engin Polat <118744+polatengin@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:54:05 +0000 Subject: [PATCH 6/7] Add support for base parameter assignments in expression conversion --- src/Bicep.Core/Intermediate/ExpressionBuilder.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs index d8851710da3..f8c61848b03 100644 --- a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs +++ b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs @@ -1030,6 +1030,11 @@ private Expression ConvertPropertyAccess(PropertyAccessSyntax propertyAccess) { var flags = GetAccessExpressionFlags(propertyAccess); + if (TryGetBaseParameterAssignment(propertyAccess.BaseExpression, propertyAccess.PropertyName.IdentifierName) is { } parentAssignment) + { + return ConvertWithoutLowering(parentAssignment.DeclaringParameterAssignment.Value); + } + // Looking for: myResource.someProp (where myResource is a resource declared in-file) if (Context.SemanticModel.ResourceMetadata.TryLookup(propertyAccess.BaseExpression) is DeclaredResourceMetadata resource) { @@ -1098,6 +1103,11 @@ moduleCollectionOutputs.BaseExpression is ArrayAccessSyntax moduleArrayAccess && flags); } + private ParameterAssignmentSymbol? TryGetBaseParameterAssignment(SyntaxBase baseExpression, string propertyName) + => Context.SemanticModel.GetSymbolInfo(baseExpression) is BaseParametersSymbol baseParameters + ? baseParameters.ParentAssignments.FirstOrDefault(assignment => LanguageConstants.IdentifierComparer.Equals(assignment.Name, propertyName)) + : null; + private Expression ConvertResourceAccess(ResourceAccessSyntax resourceAccessSyntax) { if (Context.SemanticModel.ResourceMetadata.TryLookup(resourceAccessSyntax) is { } resource) From 2c01ec3a8be50e3c8dc03111df7b8c3fde078513 Mon Sep 17 00:00:00 2001 From: Engin Polat <118744+polatengin@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:54:14 +0000 Subject: [PATCH 7/7] Add support for base parameter assignments in array access conversion --- src/Bicep.Core/Intermediate/ExpressionBuilder.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs index f8c61848b03..82ac1b91121 100644 --- a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs +++ b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs @@ -979,6 +979,13 @@ private Expression ConvertArrayAccess(ArrayAccessSyntax arrayAccess) } } + if (arrayAccess.IndexExpression is StringSyntax indexString && + indexString.TryGetLiteralValue() is { } propertyName && + TryGetBaseParameterAssignment(arrayAccess.BaseExpression, propertyName) is { } parentAssignment) + { + return ConvertWithoutLowering(parentAssignment.DeclaringParameterAssignment.Value); + } + var convertedBase = ConvertWithoutLowering(arrayAccess.BaseExpression); var convertedIndex = ConvertWithoutLowering(arrayAccess.IndexExpression);