From 59a6e31b0a7ebfee8d42976d9aa4db085da39c9b Mon Sep 17 00:00:00 2001 From: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:58:25 +0200 Subject: [PATCH] Add Expander fast paths for expressions used in .NET SDK 9 (#10409) --- .../Evaluation/Expander_Tests.cs | 35 +++++++++++ src/Build/Evaluation/Expander.cs | 59 ++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index 25187464656..f137cf89960 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -5064,6 +5064,41 @@ public void GetTypeMethod_ShouldBeAllowed_EnabledByEnvVariable(string methodName } } + [Theory] + [InlineData("$([System.Version]::Parse('17.12.11.10').ToString(2))")] + [InlineData("$([System.Text.RegularExpressions.Regex]::Replace('abc123def', 'abc', ''))")] + [InlineData("$([System.String]::new('Hi').Equals('Hello'))")] + [InlineData("$([System.IO.Path]::GetFileNameWithoutExtension('C:\\folder\\file.txt'))")] + [InlineData("$([System.Int32]::new(123).ToString('mm')")] + [InlineData("$([Microsoft.Build.Evaluation.IntrinsicFunctions]::NormalizeDirectory('C:/folder1/./folder2/'))")] + [InlineData("$([Microsoft.Build.Evaluation.IntrinsicFunctions]::IsOSPlatform('Windows'))")] + public void FastPathValidationTest(string methodInvocationMetadata) + { + using (var env = TestEnvironment.Create()) + { + // Setting this env variable allows to track if expander was using reflection for a function invocation. + env.SetEnvironmentVariable("MSBuildLogPropertyFunctionsRequiringReflection", "1"); + + var logger = new MockLogger(); + ILoggingService loggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); + loggingService.RegisterLogger(logger); + var loggingContext = new MockLoggingContext( + loggingService, + new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0, 0)); + + _ = new Expander( + new PropertyDictionary(), + FileSystems.Default, + loggingContext) + .ExpandIntoStringLeaveEscaped(methodInvocationMetadata, ExpanderOptions.ExpandProperties, MockElementLocation.Instance); + + string reflectionInfoPath = Path.Combine(Directory.GetCurrentDirectory(), "PropertyFunctionsRequiringReflection"); + + // the fast path was successfully resolved without reflection. + File.Exists(reflectionInfoPath).ShouldBeFalse(); + } + } + /// /// Determines if ICU mode is enabled. /// Copied from: https://learn.microsoft.com/en-us/dotnet/core/extensions/globalization-icu#determine-if-your-app-is-using-icu diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index cc4562be381..75f0216028f 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -3864,6 +3864,14 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst return true; } } + else if (string.Equals(_methodMethodName, nameof(string.Equals), StringComparison.OrdinalIgnoreCase)) + { + if (TryGetArg(args, out string arg0)) + { + returnVal = text.Equals(arg0); + return true; + } + } } else if (objectInstance is string[] stringArray) { @@ -4310,6 +4318,22 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst return true; } } + else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.NormalizeDirectory), StringComparison.OrdinalIgnoreCase)) + { + if (TryGetArg(args, out string arg0)) + { + returnVal = IntrinsicFunctions.NormalizeDirectory(arg0); + return true; + } + } + else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.IsOSPlatform), StringComparison.OrdinalIgnoreCase)) + { + if (TryGetArg(args, out string arg0)) + { + returnVal = IntrinsicFunctions.IsOSPlatform(arg0); + return true; + } + } } else if (_receiverType == typeof(Path)) { @@ -4407,6 +4431,14 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst return true; } } + else if (string.Equals(_methodMethodName, nameof(Path.GetFileNameWithoutExtension), StringComparison.OrdinalIgnoreCase)) + { + if (TryGetArg(args, out string arg0)) + { + returnVal = Path.GetFileNameWithoutExtension(arg0); + return true; + } + } } else if (_receiverType == typeof(Version)) { @@ -4419,7 +4451,7 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst } } } - else if (_receiverType == typeof(System.Guid)) + else if (_receiverType == typeof(Guid)) { if (string.Equals(_methodMethodName, nameof(Guid.NewGuid), StringComparison.OrdinalIgnoreCase)) { @@ -4430,8 +4462,31 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst } } } + else if (string.Equals(_methodMethodName, nameof(Regex.Replace), StringComparison.OrdinalIgnoreCase) && args.Length == 3) + { + if (TryGetArg([args[0]], out string arg1) && TryGetArg([args[1]], out string arg2) && TryGetArg([args[2]], out string arg3)) + { + returnVal = Regex.Replace(arg1, arg2, arg3); + return true; + } + } + } + else if (string.Equals(_methodMethodName, nameof(Version.ToString), StringComparison.OrdinalIgnoreCase) && objectInstance is Version v) + { + if (TryGetArg(args, out int arg0)) + { + returnVal = v.ToString(arg0); + return true; + } + } + else if (string.Equals(_methodMethodName, nameof(Int32.ToString), StringComparison.OrdinalIgnoreCase) && objectInstance is int i) + { + if (TryGetArg(args, out string arg0)) + { + returnVal = i.ToString(arg0); + return true; + } } - if (Traits.Instance.LogPropertyFunctionsRequiringReflection) { LogFunctionCall("PropertyFunctionsRequiringReflection", objectInstance, args);