From 0f1701abb4b8d2441bafcc32a2f9d03010247296 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Fri, 18 Aug 2023 11:47:47 -0400 Subject: [PATCH 1/8] fix extra ldarg_0 instruction emmited on member access through conditional expressions (#208) Member access through conditional expressions were being considered to be equivalent to an unqualified access which requires 'this' to be implicitly loaded leading to extra 'ldlarg_0' to be emmited. --- .../Tests/Unit/OperatorsTests.cs | 19 ++++++++++++++ Cecilifier.Core/AST/ExpressionVisitor.cs | 25 +------------------ Cecilifier.Core/AST/SyntaxWalkerBase.cs | 3 +-- .../Extensions/SyntaxNodeExtensions.cs | 18 +++++++++++++ 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs b/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs index cc2432a2..bdbaa0bf 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs @@ -107,6 +107,25 @@ public void TestNullConditionalOperatorOnStorageTargets(string target, Code expe @"il_M_3.Append\(lbl_conditionEnd_7\);")); } + [TestCase("f", "Ldfld, fld_f_6", TestName = "Field")] + [TestCase("P", "Call, m_get_2", TestName = "Property")] + [TestCase("M()", "Call, m_M_4", TestName = "Method")] + public void TestNullConditionalOperator_OnQualifiedMemberAccess_DoesNoLoadThis(string member, string expected) + { + var result = RunCecilifier($$"""class Foo { private int P => 0; private int M() => 0; private int f; object Bar(Foo p) => p?.{{member}}; }"""); + Assert.That( + result.GeneratedCode.ReadToEnd(), + Does.Match( + $""" + (il_bar_\d+\.Emit\(OpCodes\.)Ldarg_1\); + (\s+\1){expected}\); + \2Newobj, .+ImportReference\(.+ResolveMethod\(typeof\(System\.Nullable\), ".ctor",.+, "System.Int32"\)\)\); + \s+il_bar_\d+.Append\(lbl_conditionEnd_\d+\); + \2Box,.+ImportReference\(typeof\(System.Nullable<>\)\)\.MakeGenericInstanceType\(assembly.MainModule.TypeSystem.Int32\)\); + \2Ret\); + """)); + } + [TestCase( "int i = 42; i -= 10;", @"(il_topLevelMain_\d\.Emit\(OpCodes\.)Ldc_I4, 42\);\s+\1Stloc, (l_i_\d+)\);\s+\1Ldloc, \2\);\s+\1Ldc_I4, 10\);\s+\1Sub\);\s+\1Stloc, \2\);\s+m_topLevelStatements_1\.Body\.Instructions\.Add\(.+OpCodes\.Ret\)\);", diff --git a/Cecilifier.Core/AST/ExpressionVisitor.cs b/Cecilifier.Core/AST/ExpressionVisitor.cs index 48d261bf..53576efe 100644 --- a/Cecilifier.Core/AST/ExpressionVisitor.cs +++ b/Cecilifier.Core/AST/ExpressionVisitor.cs @@ -1175,7 +1175,7 @@ private void ProcessProperty(SimpleNameSyntax node, IPropertySymbol propertySymb propertySymbol.EnsurePropertyExists(Context, node); var isAccessOnThisOrObjectCreation = node.Parent.IsAccessOnThisOrObjectCreation(); - if (IsUnqualifiedAccess(node, propertySymbol)) + if (!propertySymbol.IsStatic && !node.IsQualifiedAccessToTypeOrMember()) { // if this is an *unqualified* access we need to load *this* Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0); @@ -1212,29 +1212,6 @@ private void HandlePropertyGetAccess(SimpleNameSyntax node, IPropertySymbol prop } } - /// - /// Checks whether the given represents an unqualified reference (i.e, only a type name) or - /// a qualified one. For instance in `Foo f = new NS.Foo();`, - /// 1. Foo => Unqualified - /// 2. NS.Foo => Qualified - /// - /// - /// Node with the name of the property being accessed. - /// Property symbol representing the property being accessed. - /// true if represents an unqualified access to , false otherwise - /// - /// A NameColon syntax represents the `Length: 42` in an expression like `o as string { Length: 42 }` - /// A MemberBindExpression represents the null coalescing operator - /// - private static bool IsUnqualifiedAccess(SimpleNameSyntax node, IPropertySymbol propertySymbol) - { - var parentMae = node.Parent as MemberAccessExpressionSyntax; - return (parentMae == null || parentMae.Expression == node) - && !node.Parent.IsKind(SyntaxKind.NameColon) - && !node.Parent.IsKind(SyntaxKind.MemberBindingExpression) - && !propertySymbol.IsStatic; - } - private void ProcessMethodReference(SimpleNameSyntax node, IMethodSymbol method) { var invocationParent = node.Ancestors().OfType() diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index 321fd058..434b8cb8 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -499,8 +499,7 @@ protected void ProcessField(string ilVar, SimpleNameSyntax node, IFieldSymbol fi var fieldDeclarationVariable = fieldSymbol.EnsureFieldExists(Context, node); - var isTargetOfQualifiedAccess = (node.Parent is MemberAccessExpressionSyntax mae) && mae.Name == node; - if (!fieldSymbol.IsStatic && !isTargetOfQualifiedAccess) + if (!fieldSymbol.IsStatic && !node.IsQualifiedAccessToTypeOrMember()) Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0); if (HandleLoadAddressOnStorage(ilVar, fieldSymbol.Type, node, fieldSymbol.IsStatic ? OpCodes.Ldsflda : OpCodes.Ldflda, fieldSymbol.Name, VariableMemberKind.Field, fieldSymbol.ContainingType.ToDisplayString())) diff --git a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs index 4ea5415c..abb21577 100644 --- a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs +++ b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs @@ -13,6 +13,24 @@ namespace Cecilifier.Core.Extensions { public static class SyntaxNodeExtensions { + /// + /// Checks whether the given is the name in a qualified reference. + /// + /// Node with the name of the type/member to be tested. + /// true if is the name in a qualified access, false otherwise + /// + /// Examples of qualified / unqualified access: + /// 1. in, `Foo f = new NS.Foo();`, `Foo` : Foo f => Unqualified, NS.Foo => Qualified + /// 2. `o.field ?? otherField`, otherField => Unqualified, `field` in `o.field` => Qualified + /// + public static bool IsQualifiedAccessToTypeOrMember(this SimpleNameSyntax node) => node.Parent switch + { + MemberAccessExpressionSyntax mae => mae.Name == node, + MemberBindingExpressionSyntax mbe => mbe.Name == node, // A MemberBindExpression represents `?.` in the null conditional operator, for instance, `o?.member` + NameColonSyntax => true, // A NameColon syntax represents the `Length: 42` in an expression like `o as string { Length: 42 }`. In this case, `Length` is equivalent to `o.Length` + _ => false + }; + /// /// Returns a human readable summary of the containing nodes/tokens until (including) first one with a new line trivia. /// From ac00a0e3bb022fd0646f2cb0cc443b9a65db1d69 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Fri, 18 Aug 2023 11:57:15 -0400 Subject: [PATCH 2/8] bump version to 2.6.0 --- Cecilifier.Common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cecilifier.Common.props b/Cecilifier.Common.props index 979256e0..c4f6783f 100644 --- a/Cecilifier.Common.props +++ b/Cecilifier.Common.props @@ -2,7 +2,7 @@ net7.0 11 - 2.5.0 + 2.6.0 true \ No newline at end of file From ad90bbe54190edb915e0de0466abf95d8e00fd28 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Fri, 18 Aug 2023 16:04:46 -0400 Subject: [PATCH 3/8] switches to sending messages to discord asynchronously also: 1. Stopped instantiating HttpClient multiple times, instantiate it once in a static ctor 2. Ensure HttpRequestMessages are disposed correctly --- Cecilifier.Web/Startup.cs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Cecilifier.Web/Startup.cs b/Cecilifier.Web/Startup.cs index dd607834..cb54bee9 100644 --- a/Cecilifier.Web/Startup.cs +++ b/Cecilifier.Web/Startup.cs @@ -50,6 +50,8 @@ public class Startup "; + private static HttpClient discordConnection = new(); + public Startup(IConfiguration configuration) { Configuration = configuration; @@ -180,7 +182,7 @@ private async Task ProcessWebSocketAsync(WebSocket webSocket, byte[] buffer) Naming = new DefaultNameStrategy(toBeCecilified.Settings.NamingOptions, toBeCecilified.Settings.ElementKindPrefixes.ToDictionary(entry => entry.ElementKind, entry => entry.Prefix)) }); - SendTextMessageToChat($"One more happy user {(deployKind == 'Z' ? "(project)" : "")}", $"Total so far: {CecilifierApplication.Count}\n\n***********\n\n```{toBeCecilified.Code}```", "4437377"); + await SendTextMessageToChatAsync($"One more happy user {(deployKind == 'Z' ? "(project)" : "")}", $"Total so far: {CecilifierApplication.Count}\n\n***********\n\n```{toBeCecilified.Code}```", "4437377"); if (deployKind == 'Z') { @@ -211,14 +213,14 @@ private async Task ProcessWebSocketAsync(WebSocket webSocket, byte[] buffer) //TODO: Log errors! var source = includeSourceInErrorReports ? codeSnippet : string.Empty; - SendMessageWithCodeToChat("Syntax Error", ex.Message, "15746887", source); + await SendMessageWithCodeToChatAsync("Syntax Error", ex.Message, "15746887", source); var dataToReturn = Encoding.UTF8.GetBytes($"{{ \"status\" : 1, \"error\": \"Code contains syntax errors\", \"syntaxError\": \"{HttpUtility.JavaScriptStringEncode(ex.Message)}\" }}").AsMemory(); await webSocket.SendAsync(dataToReturn, WebSocketMessageType.Text, true, CancellationToken.None); } catch (Exception ex) { - SendExceptionToChat(ex, buffer, received.Count); + await SendExceptionToChatAsync(ex, buffer, received.Count); var dataToReturn = Encoding.UTF8.GetBytes($"{{ \"status\" : 2, \"error\": \"{HttpUtility.JavaScriptStringEncode(ex.ToString())}\" }}").AsMemory(); await webSocket.SendAsync(dataToReturn, WebSocketMessageType.Text, true, CancellationToken.None); @@ -287,7 +289,7 @@ private static byte[] JsonSerializedBytes(string cecilifiedCode, char kind, Ceci return JsonSerializer.SerializeToUtf8Bytes(cecilifiedWebResult); } - private void SendExceptionToChat(Exception exception, byte[] code, int length) + private Task SendExceptionToChatAsync(Exception exception, byte[] code, int length) { var stasktrace = JsonEncodedText.Encode(exception.StackTrace); @@ -307,8 +309,9 @@ private void SendExceptionToChat(Exception exception, byte[] code, int length) ] }}"; - SendJsonMessageToChat(toSend); + return SendJsonMessageToChatAsync(toSend); } + private string CodeInBytesToString(byte[] code, int length) { var stream = new MemoryStream(code, 2, length - 2); // skip byte with info whether user wants zipped project or not & publishing source (discord) or not. @@ -316,12 +319,12 @@ private string CodeInBytesToString(byte[] code, int length) return reader.ReadToEnd(); } - private void SendMessageWithCodeToChat(string title, string msg, string color, string code) + private Task SendMessageWithCodeToChatAsync(string title, string msg, string color, string code) { - SendTextMessageToChat(title, $"{msg}\n\n***********\n\n```{code}```", color); + return SendTextMessageToChatAsync(title, $"{msg}\n\n***********\n\n```{code}```", color); } - private void SendJsonMessageToChat(string jsonMessage) + private async Task SendJsonMessageToChatAsync(string jsonMessage) { var discordChannelUrl = Configuration["DiscordChannelUrl"]; if (string.IsNullOrWhiteSpace(discordChannelUrl)) @@ -330,12 +333,11 @@ private void SendJsonMessageToChat(string jsonMessage) return; } - var discordPostRequest = new HttpRequestMessage(HttpMethod.Post, discordChannelUrl); + using var discordPostRequest = new HttpRequestMessage(HttpMethod.Post, discordChannelUrl); discordPostRequest.Content = new StringContent(jsonMessage, Encoding.UTF8, "application/json"); try { - var discordConnection = new HttpClient(); - var response = discordConnection.Send(discordPostRequest); + using var response = await discordConnection.SendAsync(discordPostRequest); if (response.StatusCode != HttpStatusCode.NoContent) { Console.WriteLine($"Discord returned status: {response.StatusCode}"); @@ -347,7 +349,7 @@ private void SendJsonMessageToChat(string jsonMessage) } } - private void SendTextMessageToChat(string title, string msg, string color) + private Task SendTextMessageToChatAsync(string title, string msg, string color) { var toSend = $@"{{ ""embeds"": [ @@ -359,7 +361,7 @@ private void SendTextMessageToChat(string title, string msg, string color) ] }}"; - SendJsonMessageToChat(toSend); + return SendJsonMessageToChatAsync(toSend); } From 384faf54be9747af912da1a6478daef8d407ca42 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 26 Aug 2023 17:47:53 -0400 Subject: [PATCH 4/8] Fixes StructLayout being mishandled (#205) --- .../Tests/Unit/AttributesTest.cs | 34 ++++++++++++++++++ .../AST/SyntaxWalkerBase.Private.cs | 26 ++++++++++++++ Cecilifier.Core/AST/SyntaxWalkerBase.cs | 36 +++++++++++++++---- .../Extensions/ISymbolExtensions.cs | 3 -- 4 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 Cecilifier.Core/AST/SyntaxWalkerBase.Private.cs diff --git a/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs b/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs index ca1b3263..142c711c 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using NUnit.Framework; namespace Cecilifier.Core.Tests.Tests.Unit; @@ -69,6 +70,39 @@ class Foo {{ }}"); @"cls_foo_\d+\.CustomAttributes\.Add\(\1\);")); } + [TestCase("LayoutKind.Auto, Pack=1, Size=12", 1, 12)] + [TestCase("LayoutKind.Auto, Pack=1", 1, 0)] + [TestCase("LayoutKind.Auto, Size=42", 0, 42)] + [TestCase("LayoutKind.Sequential")] + public void StructLayout_ItNotEmitted(string initializationData, int expectedPack = -1, int expectedSize = -1) + { + // StructLayout attribute should not be emitted to metadata as an attribute; + // instead, the respective properties in the type definition should be set. + + var result = RunCecilifier($$""" + using System.Runtime.InteropServices; + [StructLayout({{initializationData}})] + struct Foo { } + """); + + var cecilifiedCode = result.GeneratedCode.ReadToEnd(); + + Assert.That(cecilifiedCode, Does.Not.Match(@"st_foo_\d+.CustomAttributes.Add\(attr_structLayout_\d+\);")); + + if (expectedSize == -1 && expectedPack == -1) + { + Assert.That(cecilifiedCode, Does.Not.Match(@"st_foo_\d+.ClassSize = \d+;")); + Assert.That(cecilifiedCode, Does.Not.Match(@"st_foo_\d+.PackingSize = \d+;")); + } + else + { + Assert.That(cecilifiedCode, Does.Match(@$"st_foo_\d+.ClassSize = {expectedSize};")); + Assert.That(cecilifiedCode, Does.Match(@$"st_foo_\d+.PackingSize = {expectedPack};")); + } + + Assert.That(cecilifiedCode, Does.Match($@"\|\s+TypeAttributes.{Regex.Match(initializationData, @"LayoutKind\.([^,$]+)").Groups[1].Value}Layout")); + } + private const string AttributeDefinition = "class MyAttribute : System.Attribute { public MyAttribute(string message) {} } "; private const string GenericAttributeDefinition = @" [System.AttributeUsage(System.AttributeTargets.All, AllowMultiple =true)] diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.Private.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.Private.cs new file mode 100644 index 00000000..3339088f --- /dev/null +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.Private.cs @@ -0,0 +1,26 @@ +using Microsoft.CodeAnalysis; + +namespace Cecilifier.Core.AST; + +// Helper types / members useful only to SyntaxWalkerBase. +// Some of these may be refactored and made public/internal if needed in the future. +internal partial class SyntaxWalkerBase +{ + internal enum AttributeKind + { + DllImport, + StructLayout, + Ordinary + } +} + +public static class PrivateExtensions +{ + internal static SyntaxWalkerBase.AttributeKind AttributeKind(this ITypeSymbol self) => (self.ContainingNamespace.ToString(), self.Name) switch + { + ("System.Runtime.InteropServices", "DllImportAttribute") => SyntaxWalkerBase.AttributeKind.DllImport, + ("System.Runtime.InteropServices", "StructLayoutAttribute") => SyntaxWalkerBase.AttributeKind.StructLayout, + _ => SyntaxWalkerBase.AttributeKind.Ordinary, + }; +} + diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index 434b8cb8..d5554d66 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -17,7 +18,7 @@ namespace Cecilifier.Core.AST { - internal class SyntaxWalkerBase : CSharpSyntaxWalker + internal partial class SyntaxWalkerBase : CSharpSyntaxWalker { internal SyntaxWalkerBase(IVisitorContext ctx) { @@ -365,7 +366,7 @@ private static void AppendStructLayoutTo(ITypeSymbol typeSymbol, StringBuilder t _ => throw new ArgumentException($"Invalid StructLayout value for {typeSymbol.Name}") }; - typeAttributes.AppendModifier($"TypeAttributes.{specifiedLayout}"); + typeAttributes.AppendModifier(specifiedLayout); } } @@ -798,10 +799,13 @@ private static void HandleAttributesInMemberDeclaration(IVisitorContext context, //TODO: Pass the correct list of type parameters when C# supports generic attributes. TypeDeclarationVisitor.EnsureForwardedTypeDefinition(context, type, Array.Empty()); - var attrsExp = context.SemanticModel.GetSymbolInfo(attribute.Name).Symbol.IsDllImportCtor() - ? ProcessDllImportAttribute(context, attribute, targetDeclarationVar) - : ProcessNormalMemberAttribute(context, attribute, targetDeclarationVar); - + var attrsExp = type.AttributeKind() switch + { + AttributeKind.DllImport => ProcessDllImportAttribute(context, attribute, targetDeclarationVar), + AttributeKind.StructLayout => ProcessStructLayoutAttribute(attribute, targetDeclarationVar), + _ => ProcessNormalMemberAttribute(context, attribute, targetDeclarationVar) + }; + AddCecilExpressions(context, attrsExp); } } @@ -912,6 +916,26 @@ string AttributePropertyOrDefaultValue(AttributeSyntax attr, string propertyName } } + private static IEnumerable ProcessStructLayoutAttribute(AttributeSyntax attribute, string typeVar) + { + Debug.Assert(attribute.ArgumentList != null); + if (attribute.ArgumentList.Arguments.Count == 0 || attribute.ArgumentList.Arguments.All(a => a.NameEquals == null)) + return Array.Empty(); + + return new[] + { + $"{typeVar}.ClassSize = { AssignedValue(attribute, "Size") };", + $"{typeVar}.PackingSize = { AssignedValue(attribute, "Pack") };", + }; + + static int AssignedValue(AttributeSyntax attribute, string parameterName) + { + // whenever Size/Pack are omitted the corresponding property should be set to 0. See Ecma-335 II 22.8. + var parameterAssignmentExpression = attribute.ArgumentList?.Arguments.FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == parameterName)?.Expression; + return parameterAssignmentExpression != null ? Int32.Parse(((LiteralExpressionSyntax) parameterAssignmentExpression).Token.Text) : 0; + } + } + private static string CallingConventionToCecil(CallingConvention callingConvention) { var pinvokeAttribute = callingConvention switch diff --git a/Cecilifier.Core/Extensions/ISymbolExtensions.cs b/Cecilifier.Core/Extensions/ISymbolExtensions.cs index 866fba4f..ec8be85f 100644 --- a/Cecilifier.Core/Extensions/ISymbolExtensions.cs +++ b/Cecilifier.Core/Extensions/ISymbolExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Runtime.CompilerServices; using Cecilifier.Core.AST; using Cecilifier.Core.Misc; using Cecilifier.Core.Naming; @@ -89,8 +88,6 @@ public static T EnsureNotNull([NotNullIfNotNull("symbol")] this T symbol) whe return symbol; } - public static bool IsDllImportCtor(this ISymbol self) => self != null && self.ContainingType.Name == "DllImportAttribute"; - public static string AsParameterAttribute(this IParameterSymbol symbol) { var refRelatedAttr = symbol.RefKind.AsParameterAttribute(); From 78bf43a838a9325f3754b7389f440a4e239186a8 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 26 Aug 2023 18:21:57 -0400 Subject: [PATCH 5/8] Code clean up / refactor --- Cecilifier.Core/AST/IVisitorContext.cs | 2 +- Cecilifier.Core/AST/StatementVisitor.cs | 2 +- Cecilifier.Core/AST/SyntaxWalkerBase.cs | 74 +------------------ .../Extensions/ISymbolExtensions.cs | 48 ++++++++++++ 4 files changed, 52 insertions(+), 74 deletions(-) diff --git a/Cecilifier.Core/AST/IVisitorContext.cs b/Cecilifier.Core/AST/IVisitorContext.cs index ed736174..831f8902 100644 --- a/Cecilifier.Core/AST/IVisitorContext.cs +++ b/Cecilifier.Core/AST/IVisitorContext.cs @@ -28,7 +28,7 @@ internal interface IVisitorContext void EmitCilInstruction(string ilVar, OpCode opCode); void EmitCilInstruction(string ilVar, OpCode opCode, T operand, string comment = null); - void WriteCecilExpression(string msg); + void WriteCecilExpression(string expression); void WriteComment(string comment); void WriteNewLine(); diff --git a/Cecilifier.Core/AST/StatementVisitor.cs b/Cecilifier.Core/AST/StatementVisitor.cs index df936fd1..d416d3eb 100644 --- a/Cecilifier.Core/AST/StatementVisitor.cs +++ b/Cecilifier.Core/AST/StatementVisitor.cs @@ -324,7 +324,7 @@ private void ProcessVariableInitialization(VariableDeclaratorSyntax localVar, IT var valueBeingAssignedIsByRef = Context.SemanticModel.GetSymbolInfo(localVar.Initializer.Value).Symbol.IsByRef(); if (!variableType.IsByRef() && valueBeingAssignedIsByRef) { - OpCode opCode = LoadIndirectOpCodeFor(variableType.SpecialType); + OpCode opCode = variableType.LoadIndirectOpCodeFor(); Context.EmitCilInstruction(_ilVar, opCode); } diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index d5554d66..24921058 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -225,54 +224,12 @@ private bool LoadDefaultValueForTypeParameter(string ilVar, ITypeSymbol type, Sy private void LoadLiteralToStackHandlingCallOnValueTypeLiterals(string ilVar, ITypeSymbol literalType, object literalValue, bool isTargetOfCall) { - var opCode = LoadOpCodeFor(literalType); + var opCode = literalType.LoadOpCodeFor(); Context.EmitCilInstruction(ilVar, opCode, literalValue); if (isTargetOfCall) StoreTopOfStackInLocalVariableAndLoadItsAddress(ilVar, literalType); } - private OpCode LoadOpCodeFor(ITypeSymbol type) - { - switch (type.SpecialType) - { - case SpecialType.System_IntPtr: - case SpecialType.System_UIntPtr: - return OpCodes.Ldc_I4; - - case SpecialType.System_Single: - return OpCodes.Ldc_R4; - - case SpecialType.System_Double: - return OpCodes.Ldc_R8; - - case SpecialType.System_Byte: - case SpecialType.System_SByte: - case SpecialType.System_Int16: - case SpecialType.System_Int32: - case SpecialType.System_UInt16: - case SpecialType.System_UInt32: - return OpCodes.Ldc_I4; - - case SpecialType.System_UInt64: - case SpecialType.System_Int64: - return OpCodes.Ldc_I8; - - case SpecialType.System_Char: - return OpCodes.Ldc_I4; - - case SpecialType.System_Boolean: - return OpCodes.Ldc_I4; - - case SpecialType.System_String: - return OpCodes.Ldstr; - - case SpecialType.None: - return OpCodes.Ldnull; - } - - throw new ArgumentException($"Literal type {type} not supported.", nameof(type)); - } - private void StoreTopOfStackInLocalVariableAndLoadItsAddress(string ilVar, ITypeSymbol type, string variableName = "tmp") { var tempLocalName = StoreTopOfStackInLocalVariable(Context, ilVar, variableName, type).VariableName; @@ -665,8 +622,7 @@ protected void HandlePotentialRefLoad(string ilVar, SyntaxNode expression, IType if (needsLoadIndirect) { - OpCode opCode = LoadIndirectOpCodeFor(type.SpecialType); - Context.EmitCilInstruction(ilVar, opCode); + Context.EmitCilInstruction(ilVar, type.LoadIndirectOpCodeFor()); } } @@ -708,29 +664,6 @@ private IParameterSymbol ParameterSymbolFromArgumentSyntax(ArgumentSyntax argume return null; } - protected OpCode LoadIndirectOpCodeFor(SpecialType type) - { - return type switch - { - SpecialType.System_Single => OpCodes.Ldind_R4, - SpecialType.System_Double => OpCodes.Ldind_R8, - SpecialType.System_SByte => OpCodes.Ldind_I1, - SpecialType.System_Byte => OpCodes.Ldind_U1, - SpecialType.System_Int16 => OpCodes.Ldind_I2, - SpecialType.System_UInt16 => OpCodes.Ldind_U2, - SpecialType.System_Int32 => OpCodes.Ldind_I4, - SpecialType.System_UInt32 => OpCodes.Ldind_U4, - SpecialType.System_Int64 => OpCodes.Ldind_I8, - SpecialType.System_UInt64 => OpCodes.Ldind_I8, - SpecialType.System_Char => OpCodes.Ldind_U2, - SpecialType.System_Boolean => OpCodes.Ldind_U1, - SpecialType.System_Object => OpCodes.Ldind_Ref, - SpecialType.None => OpCodes.Ldind_Ref, - - _ => throw new ArgumentException($"Literal type {type} not supported.", nameof(type)) - }; - } - private bool IsSystemIndexUsedAsIndex(ITypeSymbol symbol, SyntaxNode node) { if (symbol.MetadataToken != Context.RoslynTypeSystem.SystemIndex.MetadataToken) @@ -789,9 +722,6 @@ protected static void HandleAttributesInTypeParameter(IVisitorContext context, I private static void HandleAttributesInMemberDeclaration(IVisitorContext context, IEnumerable attributeLists, string targetDeclarationVar) { - if (!attributeLists.Any()) - return; - foreach (var attribute in attributeLists.SelectMany(al => al.Attributes)) { var type = context.SemanticModel.GetSymbolInfo(attribute).Symbol.EnsureNotNull().ContainingType; diff --git a/Cecilifier.Core/Extensions/ISymbolExtensions.cs b/Cecilifier.Core/Extensions/ISymbolExtensions.cs index ec8be85f..07faf422 100644 --- a/Cecilifier.Core/Extensions/ISymbolExtensions.cs +++ b/Cecilifier.Core/Extensions/ISymbolExtensions.cs @@ -155,6 +155,54 @@ public static void EnsurePropertyExists(this IPropertySymbol propertySymbol, IVi _ => throw new ArgumentException($"Invalid symbol type for {symbol} ({symbol.Kind})") }; + public static OpCode LoadOpCodeFor(this ITypeSymbol type) + { + return type.SpecialType switch + { + SpecialType.System_IntPtr => OpCodes.Ldc_I4, + SpecialType.System_UIntPtr => OpCodes.Ldc_I4, + SpecialType.System_Single => OpCodes.Ldc_R4, + SpecialType.System_Double => OpCodes.Ldc_R8, + SpecialType.System_Byte => OpCodes.Ldc_I4, + SpecialType.System_SByte => OpCodes.Ldc_I4, + SpecialType.System_Int16 => OpCodes.Ldc_I4, + SpecialType.System_Int32 => OpCodes.Ldc_I4, + SpecialType.System_UInt16 => OpCodes.Ldc_I4, + SpecialType.System_UInt32 => OpCodes.Ldc_I4, + SpecialType.System_UInt64 => OpCodes.Ldc_I8, + SpecialType.System_Int64 => OpCodes.Ldc_I8, + SpecialType.System_Char => OpCodes.Ldc_I4, + SpecialType.System_Boolean => OpCodes.Ldc_I4, + SpecialType.System_String => OpCodes.Ldstr, + SpecialType.None => OpCodes.Ldnull, + + _ => throw new ArgumentException($"Literal type {type} not supported.", nameof(type)) + }; + } + + public static OpCode LoadIndirectOpCodeFor(this ITypeSymbol type) + { + return type.SpecialType switch + { + SpecialType.System_Single => OpCodes.Ldind_R4, + SpecialType.System_Double => OpCodes.Ldind_R8, + SpecialType.System_SByte => OpCodes.Ldind_I1, + SpecialType.System_Byte => OpCodes.Ldind_U1, + SpecialType.System_Int16 => OpCodes.Ldind_I2, + SpecialType.System_UInt16 => OpCodes.Ldind_U2, + SpecialType.System_Int32 => OpCodes.Ldind_I4, + SpecialType.System_UInt32 => OpCodes.Ldind_U4, + SpecialType.System_Int64 => OpCodes.Ldind_I8, + SpecialType.System_UInt64 => OpCodes.Ldind_I8, + SpecialType.System_Char => OpCodes.Ldind_U2, + SpecialType.System_Boolean => OpCodes.Ldind_U1, + SpecialType.System_Object => OpCodes.Ldind_Ref, + SpecialType.None => OpCodes.Ldind_Ref, + + _ => throw new ArgumentException($"Literal type {type} not supported.", nameof(type)) + }; + } + public static string ValueForDefaultLiteral(this ITypeSymbol literalType) => literalType switch { { SpecialType: SpecialType.System_Char } => "\0", From 67bce73930736eecca5e2d2d26c5aa4dd8da8050 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Fri, 1 Sep 2023 10:48:51 -0400 Subject: [PATCH 6/8] adds poor man statistics 1. Number of total calls to cecilifier 2. Number of unique ips 3. Max execution number from a single ip --- Cecilifier.Web/Pages/Index.cshtml | 8 ++---- Cecilifier.Web/Pages/Index.cshtml.cs | 2 ++ Cecilifier.Web/Pages/Shared/_Layout.cshtml | 5 ++-- Cecilifier.Web/Startup.cs | 29 +++++++++++++++++----- Cecilifier.Web/wwwroot/js/cecilifier.js | 20 ++++++++++++--- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/Cecilifier.Web/Pages/Index.cshtml b/Cecilifier.Web/Pages/Index.cshtml index bb89ab32..46cd580b 100644 --- a/Cecilifier.Web/Pages/Index.cshtml +++ b/Cecilifier.Web/Pages/Index.cshtml @@ -72,12 +72,8 @@ - - - - - - + +
diff --git a/Cecilifier.Web/Pages/Index.cshtml.cs b/Cecilifier.Web/Pages/Index.cshtml.cs index 75d87841..1324621c 100644 --- a/Cecilifier.Web/Pages/Index.cshtml.cs +++ b/Cecilifier.Web/Pages/Index.cshtml.cs @@ -12,6 +12,8 @@ namespace Cecilifier.Web.Pages public class CecilifierApplication : PageModel { public static int Count; + public static uint UniqueClients; + public static uint MaximumUnique; public static int SupportedCSharpVersion => Core.Cecilifier.SupportedCSharpVersion; public string FromGist { get; private set; } = string.Empty; diff --git a/Cecilifier.Web/Pages/Shared/_Layout.cshtml b/Cecilifier.Web/Pages/Shared/_Layout.cshtml index d659d4c8..41392bdc 100644 --- a/Cecilifier.Web/Pages/Shared/_Layout.cshtml +++ b/Cecilifier.Web/Pages/Shared/_Layout.cshtml @@ -121,8 +121,9 @@ - | © 2023 - Adriano Carlos Verona (@CecilifierApplication.Count) -
+ + | © 2023 - Adriano Carlos Verona + diff --git a/Cecilifier.Web/Startup.cs b/Cecilifier.Web/Startup.cs index cb54bee9..122f3721 100644 --- a/Cecilifier.Web/Startup.cs +++ b/Cecilifier.Web/Startup.cs @@ -33,6 +33,8 @@ public class CecilifiedWebResult [JsonPropertyName("status")] public int Status { get; set; } [JsonPropertyName("cecilifiedCode")] public string CecilifiedCode { get; set; } [JsonPropertyName("counter")] public int Counter { get; set; } + [JsonPropertyName("clientsCounter")] public uint Clients { get; set; } + [JsonPropertyName("maximumUnique")] public uint MaximumUnique { get; set; } [JsonPropertyName("kind")] public char Kind { get; set; } [JsonPropertyName("mappings")] public IList Mappings { get; set; } [JsonPropertyName("mainTypeName")] public string MainTypeName { get; set; } @@ -51,6 +53,7 @@ public class Startup "; private static HttpClient discordConnection = new(); + private static IDictionary seemClientIPHashCodes = new Dictionary(); public Startup(IConfiguration configuration) { @@ -112,7 +115,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (context.WebSockets.IsWebSocketRequest) { var webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await CecilifyCodeAsync(webSocket); + await CecilifyCodeAsync(webSocket, context.Connection.RemoteIpAddress); } else { @@ -125,12 +128,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } }); - async Task CecilifyCodeAsync(WebSocket webSocket) + async Task CecilifyCodeAsync(WebSocket webSocket, IPAddress remoteIpAddress) { var buffer = ArrayPool.Shared.Rent(1024 * 128); try { - await ProcessWebSocketAsync(webSocket, buffer); + await ProcessWebSocketAsync(webSocket, remoteIpAddress, buffer); } finally { @@ -140,13 +143,14 @@ async Task CecilifyCodeAsync(WebSocket webSocket) } } - private async Task ProcessWebSocketAsync(WebSocket webSocket, byte[] buffer) + private async Task ProcessWebSocketAsync(WebSocket webSocket, IPAddress remoteIpAddress, byte[] buffer) { var memory = new Memory(buffer); var received = await webSocket.ReceiveAsync(memory, CancellationToken.None); while (received.MessageType != WebSocketMessageType.Close) { - CecilifierApplication.Count++; + UpdateStatistics(remoteIpAddress); + var codeSnippet = string.Empty; bool includeSourceInErrorReports = false; @@ -229,6 +233,17 @@ private async Task ProcessWebSocketAsync(WebSocket webSocket, byte[] buffer) received = await webSocket.ReceiveAsync(memory, CancellationToken.None); } + static void UpdateStatistics(IPAddress remoteIpAddress) + { + Interlocked.Increment(ref CecilifierApplication.Count); + if (!seemClientIPHashCodes.TryGetValue(remoteIpAddress.GetHashCode(), out var visits)) + { + Interlocked.Increment(ref CecilifierApplication.UniqueClients); + } + seemClientIPHashCodes[remoteIpAddress.GetHashCode()] = visits + 1; + Interlocked.Exchange(ref CecilifierApplication.MaximumUnique, seemClientIPHashCodes.Values.Max()); + } + IEnumerable GetTrustedAssembliesPath() { return ((string) AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator).ToList(); @@ -273,7 +288,7 @@ Memory ZipProject(params (string fileName, string contents)[] sources) return File.ReadAllBytes(outputZipPath); } } - + private static byte[] JsonSerializedBytes(string cecilifiedCode, char kind, CecilifierResult cecilifierResult) { var cecilifiedWebResult = new CecilifiedWebResult @@ -281,6 +296,8 @@ private static byte[] JsonSerializedBytes(string cecilifiedCode, char kind, Ceci Status = 0, CecilifiedCode = cecilifiedCode, Counter = CecilifierApplication.Count, + MaximumUnique = CecilifierApplication.MaximumUnique, + Clients = CecilifierApplication.UniqueClients, Kind = kind, MainTypeName = cecilifierResult.MainTypeName, Mappings = cecilifierResult.Mappings.OrderBy(x => x.Cecilified.Length).ToArray(), diff --git a/Cecilifier.Web/wwwroot/js/cecilifier.js b/Cecilifier.Web/wwwroot/js/cecilifier.js index 8e2e1725..a1cb2087 100644 --- a/Cecilifier.Web/wwwroot/js/cecilifier.js +++ b/Cecilifier.Web/wwwroot/js/cecilifier.js @@ -374,7 +374,7 @@ function setTooltips(version) { theme: 'cecilifier-tooltip', delay: defaultDelay }); - + tippy('#csharpcode-container', { content: "Type any valid C# code to generate the equivalent Cecil api calls.", placement: 'bottom', @@ -708,8 +708,22 @@ function initializeWebSocket() { // this is where we get the cecilified code back... let response = JSON.parse(event.data); if (response.status === 0) { - const cecilifiedCounter = document.getElementById('cecilified_counter'); - cecilifiedCounter.innerText = response.counter; + if (document.getElementById("cecilifier-stats")._tippy === undefined) { + tippy('#cecilifier-stats', { + content: "N/A", + placement: 'top', + interactive: true, + allowHTML: true, + theme: 'cecilifier-tooltip', + delay: [500, null] + }); + } + + document.getElementById("cecilifier-stats")._tippy.setContent(` + Total: ${response.counter}
+ Clients: ${response.clientsCounter}
+ Maximum: ${response.maximumUnique}
+ `); if (response.kind === 'Z') { setTimeout(function() { From d652a94923fd05c704ec670c97d7da4c736489df Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sun, 10 Sep 2023 18:06:46 -0400 Subject: [PATCH 7/8] Fixes Assembly.EntryPoint not being set for top level based programs (#198) --- .../Tests/Unit/Miscellaneous.Statements.cs | 7 +++++++ Cecilifier.Core/AST/CompilationUnitVisitor.cs | 8 +++----- Cecilifier.Core/AST/GlobalStatementHandler.cs | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs index b6d211e6..bad94218 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs @@ -6,6 +6,13 @@ namespace Cecilifier.Core.Tests.Tests.Unit; [TestFixture] public partial class MiscellaneousStatements : CecilifierUnitTestBase { + [Test] + public void EntryPointInTopLevelStatements() + { + var result = RunCecilifier("System.Console.WriteLine(42);"); + Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(@"assembly\.EntryPoint = m_topLevelStatements_\d+")); + } + [Test] public void BreakInForBody() { diff --git a/Cecilifier.Core/AST/CompilationUnitVisitor.cs b/Cecilifier.Core/AST/CompilationUnitVisitor.cs index 4934861f..c88ddfe0 100644 --- a/Cecilifier.Core/AST/CompilationUnitVisitor.cs +++ b/Cecilifier.Core/AST/CompilationUnitVisitor.cs @@ -30,6 +30,7 @@ public override void VisitGlobalStatement(GlobalStatementSyntax node) { Context.WriteComment("Begin of global statements."); _globalStatementHandler = new GlobalStatementHandler(Context, node); + MainMethodDefinitionVariable = _globalStatementHandler.MainMethodDefinitionVariable; } if (_globalStatementHandler.HandleGlobalStatement(node)) @@ -63,9 +64,6 @@ private void VisitDeclaredTypesSortedByDependencies() private void UpdateTypeInformation(BaseTypeDeclarationSyntax node) { - if (mainType == null) - mainType = node; - var typeSymbol = ModelExtensions.GetDeclaredSymbol(Context.SemanticModel, node) as ITypeSymbol; if (typeSymbol == null) return; @@ -77,8 +75,8 @@ private void UpdateTypeInformation(BaseTypeDeclarationSyntax node) MainMethodDefinitionVariable = Context.DefinitionVariables.GetMethodVariable(mainMethod.AsMethodDefinitionVariable()); } - var mainTypeSymbol = (ITypeSymbol) ModelExtensions.GetDeclaredSymbol(Context.SemanticModel, mainType); - if (typeSymbol.GetMembers().Length > mainTypeSymbol?.GetMembers().Length) + var mainTypeSymbol = (ITypeSymbol) Context.SemanticModel.GetDeclaredSymbol(mainType ?? node); + if (mainType == null || typeSymbol.GetMembers().Length > mainTypeSymbol?.GetMembers().Length) { mainType = node; } diff --git a/Cecilifier.Core/AST/GlobalStatementHandler.cs b/Cecilifier.Core/AST/GlobalStatementHandler.cs index 52e40063..024a30b0 100644 --- a/Cecilifier.Core/AST/GlobalStatementHandler.cs +++ b/Cecilifier.Core/AST/GlobalStatementHandler.cs @@ -103,6 +103,8 @@ bool IsLastGlobalStatement(CompilationUnitSyntax compilation, int index) } } + public string MainMethodDefinitionVariable => methodVar; + private void WriteCecilExpressions(IEnumerable expressions) { foreach (var exp in expressions) From 75432d61360b43bc1cc819689c45f4dd68de9246 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 22 Jul 2023 12:01:36 -0400 Subject: [PATCH 8/8] experimenting with changing to vertically splitting to have more vertical space for the cecilified code - adds shortcuts to change width of csharp/cecilified views. - enables changing font size for cecilified code. - fixes issue which prevented font size to become smaller than a certain value. --- Cecilifier.Web/Pages/Index.cshtml | 34 +++++++-- Cecilifier.Web/Pages/Shared/_Layout.cshtml | 6 +- Cecilifier.Web/wwwroot/css/site.css | 10 ++- Cecilifier.Web/wwwroot/css/site.min.css | 2 +- Cecilifier.Web/wwwroot/js/cecilifier.js | 88 +++++++++++++++------- 5 files changed, 101 insertions(+), 39 deletions(-) diff --git a/Cecilifier.Web/Pages/Index.cshtml b/Cecilifier.Web/Pages/Index.cshtml index 46cd580b..78ee0a18 100644 --- a/Cecilifier.Web/Pages/Index.cshtml +++ b/Cecilifier.Web/Pages/Index.cshtml @@ -11,7 +11,7 @@ var fromGist = Model.FromGist; } -
+
@@ -54,10 +54,34 @@
-
-
-
- + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+