From 27ac5da6d52ffad7039ff70b35087ffed084072e Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Fri, 9 Jun 2017 16:25:21 -0700 Subject: [PATCH] Add indexer null-check for preallocated tag helper attributes --- .../PreallocatedAttributeTargetExtension.cs | 34 +++++++++++++++++++ .../CodeGeneration/RuntimeTagHelperWriter.cs | 2 +- ...reallocatedAttributeTargetExtensionTest.cs | 9 +++-- ...ixedAttributeTagHelpers_Runtime.codegen.cs | 32 ++++++++++++++--- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs index 43c01077a5..e299fd9e1b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.Language.Legacy; @@ -18,6 +19,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration public string ExecutionContextAddTagHelperAttributeMethodName { get; set; } = "AddTagHelperAttribute"; + public string FormatInvalidIndexerAssignmentMethodName { get; set; } = "InvalidTagHelperIndexerAssignment"; + public void WriteDeclarePreallocatedTagHelperHtmlAttribute(CSharpRenderingContext context, DeclarePreallocatedTagHelperHtmlAttributeIRNode node) { context.Writer @@ -74,8 +77,39 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration public void WriteSetPreallocatedTagHelperProperty(CSharpRenderingContext context, SetPreallocatedTagHelperPropertyIRNode node) { var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); + var propertyName = node.Descriptor.GetPropertyName(); var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, tagHelperVariableName, node.AttributeName, node.Descriptor); var attributeValueAccessor = $"{node.VariableName}.Value" /* ORIGINAL: TagHelperAttributeValuePropertyName */; + + // Ensure that the property we're trying to set has initialized its dictionary bound properties. + if (node.IsIndexerNameMatch && + context.TagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelperTypeName}.{propertyName}")) + { + // Throw a reasonable Exception at runtime if the dictionary property is null. + context.Writer + .Write("if (") + .Write(tagHelperVariableName) + .Write(".") + .Write(propertyName) + .WriteLine(" == null)"); + using (context.Writer.BuildScope()) + { + // System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName + // of InvalidOperationException type. + context.Writer + .Write("throw ") + .WriteStartNewObject(nameof(InvalidOperationException)) + .WriteStartMethodInvocation(FormatInvalidIndexerAssignmentMethodName) + .WriteStringLiteral(node.AttributeName) + .WriteParameterSeparator() + .WriteStringLiteral(node.TagHelperTypeName) + .WriteParameterSeparator() + .WriteStringLiteral(propertyName) + .WriteEndMethodInvocation(endLine: false) // End of method call + .WriteEndMethodInvocation(); // End of new expression / throw statement + } + } + context.Writer .WriteStartAssignment(propertyValueAccessor) .Write("(string)") diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs index 7bdca9f791..616b858734 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs @@ -324,7 +324,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration // Ensure that the property we're trying to set has initialized its dictionary bound properties. if (node.IsIndexerNameMatch && - tagHelperRenderingContext.VerifiedPropertyDictionaries.Add(propertyName)) + tagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelperTypeName}.{propertyName}")) { // Throw a reasonable Exception at runtime if the dictionary property is null. context.Writer diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/PreallocatedAttributeTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/PreallocatedAttributeTargetExtensionTest.cs index be69c43458..8c73658001 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/PreallocatedAttributeTargetExtensionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/PreallocatedAttributeTargetExtensionTest.cs @@ -171,7 +171,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1); var extension = new PreallocatedAttributeTargetExtension(); var context = new CSharpRenderingContext() { - Writer = new CSharpCodeWriter() + Writer = new CSharpCodeWriter(), + TagHelperRenderingContext = new TagHelperRenderingContext() }; var descriptor = BoundAttributeDescriptorBuilder @@ -197,7 +198,11 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1); // Assert var csharp = context.Writer.Builder.ToString(); Assert.Equal( -@"__FooTagHelper.FooProp[""Foo""] = (string)_tagHelper1.Value; +@"if (__FooTagHelper.FooProp == null) +{ + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment(""pre-Foo"", ""FooTagHelper"", ""FooProp"")); +} +__FooTagHelper.FooProp[""Foo""] = (string)_tagHelper1.Value; __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1); ", csharp, diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers_Runtime.codegen.cs index 68b51a0844..5b6c53978f 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers_Runtime.codegen.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers_Runtime.codegen.cs @@ -105,6 +105,10 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["garlic"] = 37; #line default #line hidden __tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-garlic", __TestNamespace_InputTagHelper1.IntDictionaryProperty["garlic"], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes); + if (__TestNamespace_InputTagHelper2.IntDictionaryProperty == null) + { + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("int-prefix-garlic", "TestNamespace.InputTagHelper2", "IntDictionaryProperty")); + } __TestNamespace_InputTagHelper2.IntDictionaryProperty["garlic"] = __TestNamespace_InputTagHelper1.IntDictionaryProperty["garlic"]; #line 17 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers.cshtml" __TestNamespace_InputTagHelper1.IntProperty = 42; @@ -140,6 +144,10 @@ __TestNamespace_InputTagHelper1.IntProperty = 42; throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("int-prefix-grabber", "TestNamespace.InputTagHelper2", "IntDictionaryProperty")); } __TestNamespace_InputTagHelper2.IntDictionaryProperty["grabber"] = __TestNamespace_InputTagHelper1.IntProperty; + if (__TestNamespace_InputTagHelper1.IntDictionaryProperty == null) + { + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("int-prefix-salt", "TestNamespace.InputTagHelper1", "IntDictionaryProperty")); + } #line 19 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers.cshtml" __TestNamespace_InputTagHelper1.IntDictionaryProperty["salt"] = 37; @@ -157,16 +165,20 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["pepper"] = 98; __tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_3); __TestNamespace_InputTagHelper1.StringProperty = (string)__tagHelperAttribute_4.Value; __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_4); + if (__TestNamespace_InputTagHelper2.StringDictionaryProperty == null) + { + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-grabber", "TestNamespace.InputTagHelper2", "StringDictionaryProperty")); + } __TestNamespace_InputTagHelper2.StringDictionaryProperty["grabber"] = (string)__tagHelperAttribute_4.Value; __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_4); + if (__TestNamespace_InputTagHelper1.StringDictionaryProperty == null) + { + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-paprika", "TestNamespace.InputTagHelper1", "StringDictionaryProperty")); + } __TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"] = (string)__tagHelperAttribute_5.Value; __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5); __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = (string)__tagHelperAttribute_5.Value; __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5); - if (__TestNamespace_InputTagHelper1.StringDictionaryProperty == null) - { - throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-cumin", "TestNamespace.InputTagHelper1", "StringDictionaryProperty")); - } BeginWriteTagHelperAttribute(); WriteLiteral("literate "); #line 21 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers.cshtml" @@ -204,9 +216,21 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["value"] = 37; #line default #line hidden __tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-value", __TestNamespace_InputTagHelper1.IntDictionaryProperty["value"], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes); + if (__TestNamespace_InputTagHelper2.IntDictionaryProperty == null) + { + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("int-prefix-value", "TestNamespace.InputTagHelper2", "IntDictionaryProperty")); + } __TestNamespace_InputTagHelper2.IntDictionaryProperty["value"] = __TestNamespace_InputTagHelper1.IntDictionaryProperty["value"]; + if (__TestNamespace_InputTagHelper1.StringDictionaryProperty == null) + { + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-thyme", "TestNamespace.InputTagHelper1", "StringDictionaryProperty")); + } __TestNamespace_InputTagHelper1.StringDictionaryProperty["thyme"] = (string)__tagHelperAttribute_6.Value; __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_6); + if (__TestNamespace_InputTagHelper2.StringDictionaryProperty == null) + { + throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-thyme", "TestNamespace.InputTagHelper2", "StringDictionaryProperty")); + } __TestNamespace_InputTagHelper2.StringDictionaryProperty["thyme"] = (string)__tagHelperAttribute_6.Value; __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_6); await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);