diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LlvmIrGeneratorTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LlvmIrGeneratorTests.cs new file mode 100644 index 00000000000..7013ebc9367 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LlvmIrGeneratorTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Utilities; +using NUnit.Framework; +using Xamarin.Android.Tasks.LLVMIR; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Build.Tests.Tasks +{ + [TestFixture] + public class LlvmIrGeneratorTests + { + /// + /// Regression test for https://github.com/dotnet/android/issues/10679 + /// + /// When a managed method parameter has a whitespace-only name, the LLVM IR + /// generator would produce invalid IR like: + /// define void @test(ptr noundef % ) + /// + /// The fix ensures whitespace-only names are normalized to null before + /// creating LlvmIrFunctionParameter instances, so that LlvmIrFunction + /// assigns them valid numeric names (e.g., %0, %1). + /// + [Test] + [TestCase (null, Description = "Null parameter name")] + [TestCase ("", Description = "Empty parameter name")] + [TestCase (" ", Description = "Space-only parameter name")] + [TestCase (" ", Description = "Multiple spaces parameter name")] + [TestCase ("\t", Description = "Tab-only parameter name")] + public void FunctionParameterWithInvalidName_GetsNumericName (string? paramName) + { + var parameters = new List { + new LlvmIrFunctionParameter (typeof (IntPtr), "env"), + new LlvmIrFunctionParameter (typeof (IntPtr), "klass"), + new LlvmIrFunctionParameter (typeof (IntPtr), paramName), + }; + + var func = new LlvmIrFunction ("test_function", typeof (void), parameters); + + // The third parameter should have been assigned a numeric name + var thirdParam = func.Signature.Parameters[2]; + Assert.IsNotNull (thirdParam.Name, "Parameter name should not be null after LlvmIrFunction construction"); + Assert.IsNotEmpty (thirdParam.Name, "Parameter name should not be empty after LlvmIrFunction construction"); + Assert.That (thirdParam.Name.Trim (), Is.Not.Empty, "Parameter name should not be whitespace-only after LlvmIrFunction construction"); + } + + /// + /// Verifies that the LLVM IR generator produces valid function signatures + /// when parameters have whitespace-only names that would have caused + /// invalid IR like "ptr noundef % )" before the fix. + /// + [Test] + [TestCase (" ", Description = "Space-only parameter name")] + [TestCase ("\t", Description = "Tab-only parameter name")] + public void GeneratedIR_FunctionWithWhitespaceParameterName_ProducesValidOutput (string paramName) + { + var parameters = new List { + new LlvmIrFunctionParameter (typeof (IntPtr), "env"), + new LlvmIrFunctionParameter (typeof (IntPtr), "klass"), + new LlvmIrFunctionParameter (typeof (IntPtr), paramName), + }; + + var func = new LlvmIrFunction ("test_function", typeof (void), parameters); + + var log = new TaskLoggingHelper (new MockBuildEngine (TestContext.Out, [], [], []), "test"); + var module = new LlvmIrModule (new LlvmIrTypeCache (), log); + func.Body.Ret (typeof (void)); + module.Add (func); + module.AfterConstruction (); + + var generator = LlvmIrGenerator.Create (AndroidTargetArch.Arm64, "test.ll"); + using var writer = new StringWriter (); + generator.Generate (writer, module); + + string output = writer.ToString (); + // The output should contain valid parameter declarations - no whitespace-only names after % + Assert.That (output, Does.Not.Contain ("% )"), "Generated LLVM IR should not contain 'ptr noundef % )' pattern"); + Assert.That (output, Does.Not.Contain ("%\t)"), "Generated LLVM IR should not contain 'ptr noundef %\\t)' pattern"); + Assert.That (output, Does.Contain ("@test_function"), "Generated LLVM IR should contain the function name"); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 9722c62a3b9..cdeacd342fb 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -442,7 +442,7 @@ public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttribut continue; } - if (!String.IsNullOrEmpty (parameter.Name)) { + if (!String.IsNullOrWhiteSpace (parameter.Name)) { continue; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 79e91e5be05..2a7e3b1d428 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -567,7 +567,12 @@ void AddParameter (Type type) } // Every parameter which isn't a primitive type becomes a pointer - parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name)); + // If the parameter name is null, empty, or whitespace, pass null to let LlvmIrFunction assign a numeric name + string? paramName = implementedMethod.Parameters[parameters.Count].Name; + if (String.IsNullOrWhiteSpace (paramName)) { + paramName = null; + } + parameters.Add (new LlvmIrFunctionParameter (type, paramName)); } }