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));
}
}