From 65d54e9a926491d4f67d2d11bfa0b861ab3b6ed0 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Wed, 31 Jul 2024 09:57:04 -0700 Subject: [PATCH] 4.4.1 - Fixing NullReferenceException in IsWebJobsAttribute extension method (#655) * Adding null check to `IsWebJobsAttribute` extension method. * Fix method summary. * Updating E2E test to include a function with nullable parameter type. --- common.props | 2 +- .../AttributeExtensions.cs | 15 +++++++++++---- .../FunctionsV4SdkTests.cs | 12 ++++++++---- .../v4/FunctionAppWithHttpTrigger/HttpFunction.cs | 8 ++++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/common.props b/common.props index 7500396c..2e4d5c01 100644 --- a/common.props +++ b/common.props @@ -4,7 +4,7 @@ 4 4 - 0 + 1 diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs b/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs index c937da11..64425dfa 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs @@ -43,13 +43,20 @@ private static string ToAttributeFriendlyName(string name) }; /// - /// + /// Checks if a custom attribute is a WebJobs attribute. /// - /// - /// + /// The custom attribute to check. + /// True if the attribute is a WebJobs attribute; otherwise, False. public static bool IsWebJobsAttribute(this CustomAttribute attribute) { - return attribute.AttributeType.Resolve().CustomAttributes.Any(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute") + var attributeTypeDefinition = attribute.AttributeType?.Resolve(); + + if (attributeTypeDefinition == null) + { + return false; + } + + return attributeTypeDefinition.CustomAttributes.Any(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute") || _supportedAttributes.Contains(attribute.AttributeType.FullName); } diff --git a/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/FunctionsV4SdkTests.cs b/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/FunctionsV4SdkTests.cs index a4e9a2f2..43e3d730 100644 --- a/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/FunctionsV4SdkTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/FunctionsV4SdkTests.cs @@ -11,11 +11,11 @@ public class FunctionsV4SdkTests private string _testsDirectory; private TestInitialize _testInitializer; private ITestOutputHelper _testOutputHelper; - private const string _testVersion = "v4"; + private const string TestVersion = "v4"; public FunctionsV4SdkTests(ITestOutputHelper testOutputHelper) { - _testInitializer = new TestInitialize(testOutputHelper, _testVersion); + _testInitializer = new TestInitialize(testOutputHelper, TestVersion); _testOutputHelper = testOutputHelper; _functionsSdkPackageSource = _testInitializer.FunctionsSdkPackageSource; _testsDirectory = _testInitializer.TestDirectory; @@ -68,7 +68,7 @@ public void BuildAndPublish_CopiesRuntimesToAdditionalBinFolder() Assert.True(Directory.Exists(additionalBinDir)); var files = Directory.EnumerateFiles(additionalBinDir, "*.dll", SearchOption.AllDirectories); Assert.True(files.Count() > 1); - // Test addional runtimes + // Test additional runtimes files = Directory.EnumerateFiles(Path.Combine(additionalBinDir, "runtimes"), "*.dll", SearchOption.AllDirectories); Assert.True(files.Count() > 1); @@ -99,7 +99,7 @@ public void BuildAndPublish_GeneratesFunctions() string dotnetArgs = $"build {projectFileToTest}.csproj --configuration {TestInitialize.Configuration}"; int? exitCode = new ProcessWrapper().RunProcess(TestInitialize.DotNetExecutable, dotnetArgs, projectFileDirectory, out int? _, createDirectoryIfNotExists: false, testOutputHelper: _testOutputHelper); Assert.True(exitCode.HasValue && exitCode.Value == 0); - // Test addional bin + // Test additional bin string binDir = Path.Combine(projectFileDirectory, "bin", TestInitialize.Configuration, TestInitialize.NetCoreFramework); string additionalBinDir = Path.Combine(binDir, "bin"); Assert.True(Directory.Exists(additionalBinDir)); @@ -108,6 +108,8 @@ public void BuildAndPublish_GeneratesFunctions() // Test functions generator output string httpTriggerFunctionpath = Path.Combine(binDir, "HttpFunction", "function.json"); Assert.True(File.Exists(httpTriggerFunctionpath)); + string httpTrigger2Functionpath = Path.Combine(binDir, "HttpFunction2", "function.json"); + Assert.True(File.Exists(httpTrigger2Functionpath)); // Publish dotnetArgs = $"publish {projectFileToTest}.csproj --configuration {TestInitialize.Configuration}"; @@ -122,6 +124,8 @@ public void BuildAndPublish_GeneratesFunctions() // Test functions generator output httpTriggerFunctionpath = Path.Combine(publishDir, "HttpFunction", "function.json"); Assert.True(File.Exists(httpTriggerFunctionpath)); + httpTrigger2Functionpath = Path.Combine(publishDir, "HttpFunction2", "function.json"); + Assert.True(File.Exists(httpTrigger2Functionpath)); } private void UpdatePackageReference(string projectFileToTest, string projectFileDirectory) diff --git a/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/Resources/v4/FunctionAppWithHttpTrigger/HttpFunction.cs b/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/Resources/v4/FunctionAppWithHttpTrigger/HttpFunction.cs index ae2ea34c..6eb56d60 100644 --- a/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/Resources/v4/FunctionAppWithHttpTrigger/HttpFunction.cs +++ b/test/Microsoft.NET.Sdk.Functions.EndToEnd.Tests/Resources/v4/FunctionAppWithHttpTrigger/HttpFunction.cs @@ -31,5 +31,13 @@ public static async Task Run( return new OkObjectResult(responseMessage); } + + // A function which uses nullable type for parameter. + [FunctionName("HttpFunction2")] + public static async Task Run2( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "hello/{id}")] HttpRequest req, ILogger log, string? id) + { + return new OkObjectResult($"Hello {id}"); + } } }