Skip to content

Commit

Permalink
VB compiled expression lookup: normalize spanish quotes before queryi…
Browse files Browse the repository at this point in the history
…ng as the key is also normalized in the compiled map

the key is normalized in the map because it's really hard to write the original unaltered expression in a .vb file when it has the spanish quotes.
  • Loading branch information
mciureanu authored Feb 28, 2025
1 parent ec73c9c commit 2165682
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 3 deletions.
109 changes: 109 additions & 0 deletions src/Test/TestCases.Workflows/SpecialCharactersVBTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

using System.Reflection;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
using Xunit;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.VisualBasic;
using System.CodeDom.Compiler;
using System.CodeDom;
using System;
using System.IO;

namespace TestCases.Workflows
{
public class SpecialCharactersVBTests
{
//We check that Roslyn compilation from an expression tree changes special characters in strings. For example “ is replaced with ",
//since “ cannot be used in VB code in a string
//This happens when compiling workflow expressions in VB.NET for example
[Fact]
public void VbCompilation_ShouldReplaceSpecialCharacters()
{
// Step 1: Create CodeDOM Structure
CodeCompileUnit compileUnit = new CodeCompileUnit();
CodeNamespace codeNamespace = new CodeNamespace("DynamicNamespace");
// Optionally, add imports
codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
compileUnit.Namespaces.Add(codeNamespace);

// Define the TestClass
CodeTypeDeclaration testClass = new CodeTypeDeclaration("TestClass")
{
IsClass = true,
TypeAttributes = System.Reflection.TypeAttributes.Public
};
codeNamespace.Types.Add(testClass);

// Define the GetMessage method
CodeMemberMethod getMessageMethod = new CodeMemberMethod
{
Name = "GetMessage",
Attributes = MemberAttributes.Public | MemberAttributes.Static,
ReturnType = new CodeTypeReference(typeof(string))
};
// Add the return statement: return "“Hello, Roslyn!“";
getMessageMethod.Statements.Add(
new CodeMethodReturnStatement(
new CodePrimitiveExpression("“This works“")
)
);
testClass.Members.Add(getMessageMethod);

string vbCode;
using (VBCodeProvider vbProvider = new VBCodeProvider())
{
// Generate VB.NET Code
vbCode = GenerateCode(vbProvider, compileUnit);

}

//Compile vbCode with Roslyn
var syntaxTree = VisualBasicSyntaxTree.ParseText(vbCode);

var compilation = VisualBasicCompilation.Create(
assemblyName: "DynamicAssembly",
syntaxTrees: new[] { syntaxTree },
references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) },
options: new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

using var ms = new MemoryStream();
var result = compilation.Emit(ms);

// Ensure compilation was successful
Assert.True(result.Success, "Compilation failed: " + string.Join("\n", result.Diagnostics));

// Load the compiled assembly
ms.Seek(0, SeekOrigin.Begin);
var assembly = Assembly.Load(ms.ToArray());

// Get the compiled type and method
var type = assembly.GetType("DynamicNamespace.TestClass");
Assert.NotNull(type);
var method = type.GetMethod("GetMessage", BindingFlags.Static | BindingFlags.Public);
Assert.NotNull(method);

// Invoke the method and check the result
string resultMessage = (string)method.Invoke(null, null);

//We check that the special character “ is replaced with "
Assert.Equal("\"This works\"", resultMessage);

}


static string GenerateCode(CodeDomProvider provider, CodeCompileUnit compileUnit)
{
using (StringWriter sw = new StringWriter())
{
CodeGeneratorOptions options = new CodeGeneratorOptions
{
BracingStyle = "C",
IndentString = " "
};
provider.GenerateCodeFromCompileUnit(compileUnit, sw, options);
return sw.ToString();
}
}
}
}
119 changes: 119 additions & 0 deletions src/Test/TestCases.Workflows/TestXamls/SpecialCharacters.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<Activity mc:Ignorable="sap sap2010" x:Class="Main" VisualBasic.Settings="{x:Null}" sap2010:WorkflowViewState.IdRef="ActivityBuilder_1" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:sap2010="http://schemas.microsoft.com/netfx/2010/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System.Private.CoreLib" xmlns:sco="clr-namespace:System.Collections.ObjectModel;assembly=System.Private.CoreLib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextExpression.NamespacesForImplementation>
<sco:Collection x:TypeArguments="x:String">
<x:String>System.Activities</x:String>
<x:String>System.Activities.Statements</x:String>
<x:String>System.Activities.Expressions</x:String>
<x:String>System.Activities.Validation</x:String>
<x:String>System.Activities.XamlIntegration</x:String>
<x:String>Microsoft.VisualBasic</x:String>
<x:String>Microsoft.VisualBasic.Activities</x:String>
<x:String>System</x:String>
<x:String>System.Collections</x:String>
<x:String>System.Collections.Generic</x:String>
<x:String>System.Collections.ObjectModel</x:String>
<x:String>System.Data</x:String>
<x:String>System.Diagnostics</x:String>
<x:String>System.Drawing</x:String>
<x:String>System.IO</x:String>
<x:String>System.Linq</x:String>
<x:String>System.Net.Mail</x:String>
<x:String>System.Xml</x:String>
<x:String>System.Xml.Linq</x:String>
<x:String>UiPath.Core</x:String>
<x:String>UiPath.Core.Activities</x:String>
<x:String>System.Windows.Markup</x:String>
<x:String>GlobalVariablesNamespace</x:String>
<x:String>GlobalConstantsNamespace</x:String>
<x:String>System.Linq.Expressions</x:String>
<x:String>System.Runtime.Serialization</x:String>
<x:String>System.Reflection</x:String>
</sco:Collection>
</TextExpression.NamespacesForImplementation>
<TextExpression.ReferencesForImplementation>
<sco:Collection x:TypeArguments="AssemblyReference">
<AssemblyReference>Microsoft.VisualBasic</AssemblyReference>
<AssemblyReference>mscorlib</AssemblyReference>
<AssemblyReference>PresentationCore</AssemblyReference>
<AssemblyReference>PresentationFramework</AssemblyReference>
<AssemblyReference>System</AssemblyReference>
<AssemblyReference>System.Activities</AssemblyReference>
<AssemblyReference>System.Collections</AssemblyReference>
<AssemblyReference>System.Collections.NonGeneric</AssemblyReference>
<AssemblyReference>System.ComponentModel</AssemblyReference>
<AssemblyReference>System.ComponentModel.TypeConverter</AssemblyReference>
<AssemblyReference>System.Configuration.ConfigurationManager</AssemblyReference>
<AssemblyReference>System.Console</AssemblyReference>
<AssemblyReference>System.Core</AssemblyReference>
<AssemblyReference>System.Data</AssemblyReference>
<AssemblyReference>System.Data.Common</AssemblyReference>
<AssemblyReference>System.Data.DataSetExtensions</AssemblyReference>
<AssemblyReference>System.Drawing</AssemblyReference>
<AssemblyReference>System.Drawing.Common</AssemblyReference>
<AssemblyReference>System.Drawing.Primitives</AssemblyReference>
<AssemblyReference>System.Linq</AssemblyReference>
<AssemblyReference>System.Linq.Async</AssemblyReference>
<AssemblyReference>System.Linq.Expressions</AssemblyReference>
<AssemblyReference>System.Memory</AssemblyReference>
<AssemblyReference>System.Net.Mail</AssemblyReference>
<AssemblyReference>System.ObjectModel</AssemblyReference>
<AssemblyReference>System.Private.CoreLib</AssemblyReference>
<AssemblyReference>System.Private.DataContractSerialization</AssemblyReference>
<AssemblyReference>System.Private.ServiceModel</AssemblyReference>
<AssemblyReference>System.Private.Uri</AssemblyReference>
<AssemblyReference>System.Reflection.DispatchProxy</AssemblyReference>
<AssemblyReference>System.Reflection.Metadata</AssemblyReference>
<AssemblyReference>System.Reflection.TypeExtensions</AssemblyReference>
<AssemblyReference>System.Runtime.Serialization.Formatters</AssemblyReference>
<AssemblyReference>System.Runtime.Serialization.Primitives</AssemblyReference>
<AssemblyReference>System.Security.Permissions</AssemblyReference>
<AssemblyReference>System.Xaml</AssemblyReference>
<AssemblyReference>System.Xml</AssemblyReference>
<AssemblyReference>System.Xml.Linq</AssemblyReference>
<AssemblyReference>UiPath.Studio.Constants</AssemblyReference>
<AssemblyReference>UiPath.System.Activities</AssemblyReference>
<AssemblyReference>UiPath.UiAutomation.Activities</AssemblyReference>
<AssemblyReference>UiPath.Workflow</AssemblyReference>
<AssemblyReference>WindowsBase</AssemblyReference>
</sco:Collection>
</TextExpression.ReferencesForImplementation>
<Sequence DisplayName="Main Sequence" sap:VirtualizedContainerService.HintSize="874.4000000000001,606.84" sap2010:WorkflowViewState.IdRef="Sequence_1">
<Sequence.Variables>

<!-- Different styles of variable declaration -->
<Variable x:TypeArguments="x:String" Name="variable1">
<Variable.Default>
<VisualBasicValue x:TypeArguments="x:String" ExpressionText="&quot;SampleText1&lt;&quot;.ToLower()" />
</Variable.Default>
</Variable>
<Variable x:TypeArguments="x:String" Name="variable2">
<Variable.Default>
<VisualBasicValue x:TypeArguments="x:String" ExpressionText="“SampleText2“.ToLower()" />
</Variable.Default>
</Variable>
<Variable x:TypeArguments="x:String" Name="variable3">
<Variable.Default>
<VisualBasicValue x:TypeArguments="x:String" ExpressionText="“SampleText3“" />
</Variable.Default>
</Variable>
<Variable x:TypeArguments="x:String" Name="variable4">
<Variable.Default>
<Literal x:TypeArguments="x:String">SampleText4</Literal>
</Variable.Default>
</Variable>
<Variable x:TypeArguments="x:String" Default="[“SampleText5“]" Name="variable5" />

<Variable x:TypeArguments="x:String" Name="variable6">
<Variable.Default>
<Literal x:TypeArguments="x:String">SampleText6““Special</Literal>
</Variable.Default>
</Variable>

</Sequence.Variables>
<sap:WorkflowViewStateService.ViewState>
<scg:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg:Dictionary>
</sap:WorkflowViewStateService.ViewState>
</Sequence>
</Activity>
3 changes: 2 additions & 1 deletion src/Test/TestCases.Workflows/TestXamls/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public enum TestXamls
SimpleWorkflowWithArgsAndVar,
IfThenElseBranchWithVars,
NestedSequencesWithVars,
WorkflowWithReadonlyValueTypeVar
WorkflowWithReadonlyValueTypeVar,
SpecialCharacters
}
}
38 changes: 38 additions & 0 deletions src/Test/TestCases.Workflows/WF4Samples/Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xaml;
using TestCases.Workflows.TestUtils;
Expand Down Expand Up @@ -106,6 +107,43 @@ public void CompileCSharpCalculation()
var activity = Compile(TestXamls.CSharpCalculation);
TestHelper.InvokeWorkflow(activity, CSharpCalculationInputs).ShouldBe(CSharpCalculationResult);
}

sealed class CompileSpecialCharactersHelperCompiler : JustInTimeCompiler
{
private JustInTimeCompiler _inner;

private static ThreadLocal<bool> _threadLocalEnabled = new ThreadLocal<bool>(() => false);

public static bool EnabledOnCurrentThread
{
get => _threadLocalEnabled.Value;
set => _threadLocalEnabled.Value = value;
}


public CompileSpecialCharactersHelperCompiler(JustInTimeCompiler inner) { this._inner = inner; }

public override LambdaExpression CompileExpression(ExpressionToCompile compilerRequest)
{
if (EnabledOnCurrentThread)
throw new InvalidOperationException($"JIT compilation is disabled for non-Legacy projects. {compilerRequest} should have been compiled by the Studio Compiler.");
return _inner.CompileExpression(compilerRequest);
}
}

//https://uipath.atlassian.net/browse/ROBO-4469 Checks that an xaml WF with special characters can be compiled, and doesn't need JIT.
[Fact]
public void CompileSpecialCharacters()
{
var cf = VisualBasicSettings.Default.CompilerFactory;
CompileSpecialCharactersHelperCompiler.EnabledOnCurrentThread = true;
VisualBasicSettings.Default.CompilerFactory = a => new CompileSpecialCharactersHelperCompiler(cf(a));
var activity = Compile(TestXamls.SpecialCharacters);
var result = TestHelper.InvokeWorkflow(activity);
VisualBasicSettings.Default.CompilerFactory = cf;
}


[Fact]
public void CSharpCalculation()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// This file is part of Core WF which is licensed under the MIT license.
// This file is part of Core WF which is licensed under the MIT license.
// See LICENSE file in the project root for full license information.

using System.Activities.XamlIntegration;
Expand Down Expand Up @@ -192,8 +192,21 @@ private bool TryGetCurrentCompiledExpressionRoot(ActivityContext activityContext
private bool CanExecuteExpression(ICompiledExpressionRoot compiledExpressionRoot, out int expressionId)
{
var resultType = _expressionActivity.ResultType;

string normalizedExpressionText = _textExpression.ExpressionText;

if (_textExpression.Language == "VB")
{
//These characters are not allowed in VB code, so when VB compilation is done, they are replaced with normal quotes.
//But in the Expression Tree, we can have them, since they can be parsed directly from xaml code, or from a C# dll,
// so we need to "normalize" them, so that the comparison will work.
normalizedExpressionText = normalizedExpressionText
.Replace('“', '"')
.Replace('”', '"');
}

return compiledExpressionRoot.CanExecuteExpression(_isReference ? resultType.GenericTypeArguments[0] : resultType,
_textExpression.ExpressionText, _isReference, _locationReferences, out expressionId); ;
normalizedExpressionText, _isReference, _locationReferences, out expressionId); ;
}

private void ProcessLocationReferences()
Expand Down

0 comments on commit 2165682

Please sign in to comment.