diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index 427d57153aa..039804db78b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -223,15 +223,15 @@ public SimpleTypeWithMultipleCtors(int i) private dynamic ViewBag; public static readonly object[] SupportedMethods = new object[2] { - ToCode(null, () => ((IQueryable)null).Aggregate((object o1, object o2) => null)), - ToCode(null, () => ((IEnumerable)null).Aggregate((object o1, object o2) => null)) + ToCode(null, () => ((IQueryable)null).Aggregate((object o1, object o2) => (object)null)), + ToCode(null, () => ((IEnumerable)null).Aggregate((object o1, object o2) => (object)null)) }; public static readonly object[] SupportedMethods2 = new object[4] { - ToCode(null, () => ((IQueryable)null).Aggregate(null, (object o1, object o2) => null)), - ToCode(null, () => ((IQueryable)null).Aggregate((object)null, (Expression>)((object o1, object o2) => null), (Expression>)((object o) => null))), - ToCode(null, () => ((IEnumerable)null).Aggregate(null, (object o1, object o2) => null)), - ToCode(null, () => ((IEnumerable)null).Aggregate((object)null, (Func)((object o1, object o2) => null), (Func)((object o) => null))) + ToCode(null, () => ((IQueryable)null).Aggregate(null, (object o1, object o2) => (object)null)), + ToCode(null, () => ((IQueryable)null).Aggregate(null, (object o1, object o2) => (object)null, (object o) => (object)null)), + ToCode(null, () => ((IEnumerable)null).Aggregate(null, (object o1, object o2) => (object)null)), + ToCode(null, () => ((IEnumerable)null).Aggregate(null, (object o1, object o2) => (object)null, (object o) => (object)null)) }; public static void TestCall(object a) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs index 0675b77781c..4b14803d761 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs @@ -120,7 +120,7 @@ public class Issue1660 : Issue1660Base public Action M(object state) { return delegate (object x) { - base.BaseCall(x, state, (Func)(() => null)); + base.BaseCall(x, state, () => (object)null); }; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs index f0e19c1ba40..90692b1ffff 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs @@ -77,7 +77,7 @@ public struct GenericStruct public int AccessPartiallyNamed => PartiallyNamed.a + PartiallyNamed.Item3; public ValueTuple NewTuple1 => new ValueTuple(1); - public (int a, int b) NewTuple2 => (1, 2); + public (int a, int b) NewTuple2 => (a: 1, b: 2); public object BoxedTuple10 => (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); public (uint, int) SwapUnnamed => (Unnamed2.Item2, Unnamed2.Item1); @@ -115,7 +115,7 @@ public void UnnamedTupleRef(ref (int, string, Action, dynamic) tuple) public void NamedTupleOut(out (int A, string B, Action C, dynamic D) tuple) { - tuple = (42, "Hello", Console.WriteLine, null); + tuple = (A: 42, B: "Hello", C: Console.WriteLine, D: null); } public void NamedTupleIn(in (int A, string B, Action C, dynamic D) tuple) @@ -141,6 +141,27 @@ public void UseDict() Console.WriteLine(TupleDict.Values.ToList().First().d); } + private static (string, string) Issue3014a(string[] args) + { + return (from v in args + select (Name: v, Value: v) into kvp + orderby kvp.Name + select kvp).First(); + } + + private static (string, string) Issue3014b(string[] args) + { + return (from v in args + select ((string Name, string Value))GetTuple() into kvp + orderby kvp.Name + select kvp).First(); + + (string, string) GetTuple() + { + return (args[0], args[1]); + } + } + public void Issue1174() { Console.WriteLine((1, 2, 3).GetHashCode()); diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index d0f4c056c06..d34ec1cfd12 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -189,7 +189,7 @@ public CallBuilder(ExpressionBuilder expressionBuilder, IDecompilerTypeSystem ty this.typeSystem = typeSystem; } - public TranslatedExpression Build(CallInstruction inst) + public TranslatedExpression Build(CallInstruction inst, IType typeHint = null) { if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.MatchDelegateConstruction(newobj, out _, out _, out _)) { @@ -198,20 +198,29 @@ public TranslatedExpression Build(CallInstruction inst) if (settings.TupleTypes && TupleTransform.MatchTupleConstruction(inst as NewObj, out var tupleElements) && tupleElements.Length >= 2) { var elementTypes = TupleType.GetTupleElementTypes(inst.Method.DeclaringType); - Debug.Assert(!elementTypes.IsDefault, "MatchTupleConstruction should not success unless we got a valid tuple type."); + var elementNames = typeHint is TupleType tt ? tt.ElementNames : default; + Debug.Assert(!elementTypes.IsDefault, "MatchTupleConstruction should not succeed unless we got a valid tuple type."); Debug.Assert(elementTypes.Length == tupleElements.Length); var tuple = new TupleExpression(); var elementRRs = new List(); - foreach (var (element, elementType) in tupleElements.Zip(elementTypes)) + foreach (var (index, element, elementType) in tupleElements.ZipWithIndex(elementTypes)) { var translatedElement = expressionBuilder.Translate(element, elementType) .ConvertTo(elementType, expressionBuilder, allowImplicitConversion: true); - tuple.Elements.Add(translatedElement.Expression); + if (elementNames.IsDefaultOrEmpty || elementNames.ElementAtOrDefault(index) is not string { Length: > 0 } name) + { + tuple.Elements.Add(translatedElement.Expression); + } + else + { + tuple.Elements.Add(new NamedArgumentExpression(name, translatedElement.Expression)); + } elementRRs.Add(translatedElement.ResolveResult); } return tuple.WithRR(new TupleResolveResult( expressionBuilder.compilation, elementRRs.ToImmutableArray(), + elementNames, valueTupleAssembly: inst.Method.DeclaringType.GetDefinition()?.ParentModule )).WithILInstruction(inst); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index d0cdde6defe..0a3dddb1552 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -22,7 +22,6 @@ using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; -using System.Runtime.CompilerServices; using System.Threading; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -438,7 +437,7 @@ protected internal override TranslatedExpression VisitNewObj(NewObj inst, Transl return TranslateStackAllocInitializer(b, type.TypeArguments[0]); } } - return new CallBuilder(this, typeSystem, settings).Build(inst); + return new CallBuilder(this, typeSystem, settings).Build(inst, context.TypeHint); } protected internal override TranslatedExpression VisitLdVirtDelegate(LdVirtDelegate inst, TranslationContext context) diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 61471cb35e5..28bccd9526c 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -395,8 +395,14 @@ protected internal override TranslatedStatement VisitLeave(Leave inst) return new YieldBreakStatement().WithILInstruction(inst); else if (!inst.Value.MatchNop()) { + bool isLambdaOrExprTree = currentFunction.Kind is ILFunctionKind.ExpressionTree or ILFunctionKind.Delegate; var expr = exprBuilder.Translate(inst.Value, typeHint: currentResultType) .ConvertTo(currentResultType, exprBuilder, allowImplicitConversion: true); + if (isLambdaOrExprTree && IsPossibleLossOfTypeInformation(expr.Type, currentResultType)) + { + expr = new CastExpression(exprBuilder.ConvertType(currentResultType), expr) + .WithRR(new ConversionResolveResult(currentResultType, expr.ResolveResult, Conversion.IdentityConversion)).WithoutILInstruction(); + } return new ReturnStatement(expr).WithILInstruction(inst); } else @@ -419,6 +425,19 @@ protected internal override TranslatedStatement VisitLeave(Leave inst) return new GotoStatement(label).WithILInstruction(inst); } + private bool IsPossibleLossOfTypeInformation(IType givenType, IType expectedType) + { + if (NormalizeTypeVisitor.IgnoreNullability.EquivalentTypes(givenType, expectedType)) + return false; + if (expectedType is TupleType { ElementNames.IsEmpty: false }) + return true; + if (expectedType == SpecialType.Dynamic) + return true; + if (givenType == SpecialType.NullType) + return true; + return false; + } + protected internal override TranslatedStatement VisitThrow(Throw inst) { return new ThrowStatement(exprBuilder.Translate(inst.Argument)).WithILInstruction(inst); diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 1350d21d3b8..ad864b5acd9 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -272,11 +272,20 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres // Conversion of a tuple literal: convert element-wise var newTupleExpr = new TupleExpression(); var newElementRRs = new List(); - foreach (var (elementExpr, elementTargetType) in tupleExpr.Elements.Zip(targetTupleType.ElementTypes)) + // element names: discard existing names and use targetTupleType instead + var newElementNames = targetTupleType.ElementNames; + foreach (var (index, elementExpr, elementTargetType) in tupleExpr.Elements.ZipWithIndex(targetTupleType.ElementTypes)) { - var newElementExpr = new TranslatedExpression(elementExpr.Detach()) + var newElementExpr = new TranslatedExpression((elementExpr is NamedArgumentExpression nae ? nae.Expression : elementExpr).Detach()) .ConvertTo(elementTargetType, expressionBuilder, checkForOverflow, allowImplicitConversion); - newTupleExpr.Elements.Add(newElementExpr.Expression); + if (newElementNames.IsDefaultOrEmpty || newElementNames.ElementAtOrDefault(index) is not string { Length: > 0 } name) + { + newTupleExpr.Elements.Add(newElementExpr.Expression); + } + else + { + newTupleExpr.Elements.Add(new NamedArgumentExpression(name, newElementExpr.Expression)); + } newElementRRs.Add(newElementExpr.ResolveResult); } return newTupleExpr.WithILInstruction(this.ILInstructions) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs index 91c7f364dd4..bb1365de0ff 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs @@ -88,24 +88,32 @@ public void AddEdgeTo(ControlFlowNode target) public void TraversePreOrder(Func> children, Action visitAction) { - if (Visited) - return; - Visited = true; - visitAction(this); - foreach (ControlFlowNode t in children(this)) - t.TraversePreOrder(children, visitAction); + GraphTraversal.DepthFirstSearch(new[] { this }, Visit, children); + + bool Visit(ControlFlowNode node) + { + if (node.Visited) + return false; + node.Visited = true; + visitAction(node); + return true; + } } public void TraversePostOrder(Func> children, Action visitAction) { - if (Visited) - return; - Visited = true; - foreach (ControlFlowNode t in children(this)) - t.TraversePostOrder(children, visitAction); - visitAction(this); + GraphTraversal.DepthFirstSearch(new[] { this }, Visit, children, postorderAction: visitAction); + + bool Visit(ControlFlowNode node) + { + if (node.Visited) + return false; + node.Visited = true; + return true; + } } + /// /// Gets whether this dominates . /// diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 6816e28835c..38276bb4f91 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -661,6 +661,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs index 79b79d70cae..e4b8ecbefb0 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs @@ -279,7 +279,7 @@ public List TopologicalSort(bool deleteUnreachableBlocks = false) // Visit blocks in post-order BitSet visited = new BitSet(Blocks.Count); List postOrder = new List(); - Visit(EntryPoint); + GraphTraversal.DepthFirstSearch(new[] { EntryPoint }, MarkAsVisited, Successors, postOrder.Add, reverseSuccessors: true); postOrder.Reverse(); if (!deleteUnreachableBlocks) { @@ -291,24 +291,30 @@ public List TopologicalSort(bool deleteUnreachableBlocks = false) } return postOrder; - void Visit(Block block) + bool MarkAsVisited(Block block) { Debug.Assert(block.Parent == this); if (!visited[block.ChildIndex]) { visited[block.ChildIndex] = true; + return true; + } + else + { + return false; + } + } - foreach (var branch in block.Descendants.OfType()) + IEnumerable Successors(Block block) + { + foreach (var branch in block.Descendants.OfType()) + { + if (branch.TargetBlock.Parent == this) { - if (branch.TargetBlock.Parent == this) - { - Visit(branch.TargetBlock); - } + yield return branch.TargetBlock; } - - postOrder.Add(block); } - }; + } } /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs index fdfa3497502..9ab6740feb8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs @@ -5,6 +5,7 @@ using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL.ControlFlow; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { @@ -105,29 +106,36 @@ public void Run(ILFunction function, ILTransformContext context) } } - void VisitBlock(ControlFlowNode cfgNode, BlockTransformContext context) + /// + /// Walks the dominator tree rooted at entryNode, calling the transforms on each block. + /// + void VisitBlock(ControlFlowNode entryNode, BlockTransformContext context) { - Block block = (Block)cfgNode.UserData; - context.StepStartGroup(block.Label, block); - - context.ControlFlowNode = cfgNode; - context.Block = block; - context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; - block.RunTransforms(PreOrderTransforms, context); - - // First, process the children in the dominator tree. - // The ConditionDetection transform requires dominated blocks to - // be already processed. - foreach (var child in cfgNode.DominatorTreeChildren) + IEnumerable Preorder(ControlFlowNode cfgNode) { - VisitBlock(child, context); + // preorder processing: + Block block = (Block)cfgNode.UserData; + context.StepStartGroup(block.Label, block); + + context.ControlFlowNode = cfgNode; + context.Block = block; + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + block.RunTransforms(PreOrderTransforms, context); + + // process the children + return cfgNode.DominatorTreeChildren; } - context.ControlFlowNode = cfgNode; - context.Block = block; - context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; - block.RunTransforms(PostOrderTransforms, context); - context.StepEndGroup(); + foreach (var cfgNode in TreeTraversal.PostOrder(entryNode, Preorder)) + { + // in post-order: + Block block = (Block)cfgNode.UserData; + context.ControlFlowNode = cfgNode; + context.Block = block; + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + block.RunTransforms(PostOrderTransforms, context); + context.StepEndGroup(); + } } } } diff --git a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs index 445d0dffd62..5ce6beafa47 100644 --- a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs +++ b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -384,11 +385,13 @@ public static IEnumerable GetMethodSpecifications(thi yield return Read(row); } - unsafe (Handle Handle, MethodSemanticsAttributes Semantics, MethodDefinitionHandle Method, EntityHandle Association) Read(int row) + (Handle Handle, MethodSemanticsAttributes Semantics, MethodDefinitionHandle Method, EntityHandle Association) Read(int row) { - byte* ptr = metadata.MetadataPointer + offset + rowSize * row; - int methodDef = methodSmall ? *(ushort*)(ptr + 2) : (int)*(uint*)(ptr + 2); - int assocDef = assocSmall ? *(ushort*)(ptr + assocOffset) : (int)*(uint*)(ptr + assocOffset); + var span = metadata.AsReadOnlySpan(); + var methodDefSpan = span.Slice(offset + rowSize * row + 2); + int methodDef = methodSmall ? BinaryPrimitives.ReadUInt16LittleEndian(methodDefSpan) : (int)BinaryPrimitives.ReadUInt32LittleEndian(methodDefSpan); + var assocSpan = span.Slice(assocOffset); + int assocDef = assocSmall ? BinaryPrimitives.ReadUInt16LittleEndian(assocSpan) : (int)BinaryPrimitives.ReadUInt32LittleEndian(assocSpan); EntityHandle propOrEvent; if ((assocDef & 0x1) == 1) { @@ -398,7 +401,7 @@ public static IEnumerable GetMethodSpecifications(thi { propOrEvent = MetadataTokens.EventDefinitionHandle(assocDef >> 1); } - return (MetadataTokens.Handle(0x18000000 | (row + 1)), (MethodSemanticsAttributes)(*(ushort*)ptr), MetadataTokens.MethodDefinitionHandle(methodDef), propOrEvent); + return (MetadataTokens.Handle(0x18000000 | (row + 1)), (MethodSemanticsAttributes)(BinaryPrimitives.ReadUInt16LittleEndian(span)), MetadataTokens.MethodDefinitionHandle(methodDef), propOrEvent); } } @@ -411,9 +414,9 @@ public static IEnumerable GetFieldLayouts(this MetadataReader meta } } - public unsafe static (int Offset, FieldDefinitionHandle FieldDef) GetFieldLayout(this MetadataReader metadata, EntityHandle fieldLayoutHandle) + public static (int Offset, FieldDefinitionHandle FieldDef) GetFieldLayout(this MetadataReader metadata, EntityHandle fieldLayoutHandle) { - byte* startPointer = metadata.MetadataPointer; + var startPointer = metadata.AsReadOnlySpan(); int offset = metadata.GetTableMetadataOffset(TableIndex.FieldLayout); int rowSize = metadata.GetTableRowSize(TableIndex.FieldLayout); int rowCount = metadata.GetTableRowCount(TableIndex.FieldLayout); @@ -422,14 +425,31 @@ public unsafe static (int Offset, FieldDefinitionHandle FieldDef) GetFieldLayout bool small = metadata.GetTableRowCount(TableIndex.Field) <= ushort.MaxValue; for (int row = rowCount - 1; row >= 0; row--) { - byte* ptr = startPointer + offset + rowSize * row; - uint rowNo = small ? *(ushort*)(ptr + 4) : *(uint*)(ptr + 4); + ReadOnlySpan ptr = startPointer.Slice(offset + rowSize * row); + var rowNoSpan = ptr.Slice(4); + uint rowNo = small ? BinaryPrimitives.ReadUInt16LittleEndian(rowNoSpan) : BinaryPrimitives.ReadUInt32LittleEndian(rowNoSpan); if (fieldRowNo == rowNo) { - return (*(int*)ptr, MetadataTokens.FieldDefinitionHandle(fieldRowNo)); + return (BinaryPrimitives.ReadInt32LittleEndian(ptr), MetadataTokens.FieldDefinitionHandle(fieldRowNo)); } } return (0, default); } + + public static ReadOnlySpan AsReadOnlySpan(this MetadataReader metadataReader) + { + unsafe + { + return new(metadataReader.MetadataPointer, metadataReader.MetadataLength); + } + } + + public static BlobReader AsBlobReader(this MetadataReader metadataReader) + { + unsafe + { + return new(metadataReader.MetadataPointer, metadataReader.MetadataLength); + } + } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs index 8f097f6832b..4d140715a7d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs @@ -50,6 +50,17 @@ sealed class NormalizeTypeVisitor : TypeVisitor RemoveNullability = true, }; + internal static readonly NormalizeTypeVisitor IgnoreNullability = new NormalizeTypeVisitor { + ReplaceClassTypeParametersWithDummy = false, + ReplaceMethodTypeParametersWithDummy = false, + DynamicAndObject = false, + IntPtrToNInt = false, + TupleToUnderlyingType = false, + RemoveModOpt = true, + RemoveModReq = true, + RemoveNullability = true, + }; + public bool EquivalentTypes(IType a, IType b) { a = a.AcceptVisitor(this); diff --git a/ICSharpCode.Decompiler/Util/BitOperations.cs b/ICSharpCode.Decompiler/Util/BitOperations.cs index d9e59addb2e..d8d8934f5fb 100644 --- a/ICSharpCode.Decompiler/Util/BitOperations.cs +++ b/ICSharpCode.Decompiler/Util/BitOperations.cs @@ -2,9 +2,10 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace ICSharpCode.Decompiler.Util +#if !NETCORE +namespace System.Numerics { - internal class BitOperations + internal static class BitOperations { private static ReadOnlySpan TrailingZeroCountDeBruijn => new byte[32] { @@ -48,3 +49,4 @@ public static int TrailingZeroCount(ulong value) } } } +#endif diff --git a/ICSharpCode.Decompiler/Util/BitSet.cs b/ICSharpCode.Decompiler/Util/BitSet.cs index a20ba76f785..3980c1f81d5 100644 --- a/ICSharpCode.Decompiler/Util/BitSet.cs +++ b/ICSharpCode.Decompiler/Util/BitSet.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Numerics; using System.Text; namespace ICSharpCode.Decompiler.Util diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 11be785b9c1..2912707ec7e 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -21,6 +21,12 @@ public static void Deconstruct(this KeyValuePair pair, out K key, ou } #endif + public static IEnumerable<(int, A, B)> ZipWithIndex(this IEnumerable input1, IEnumerable input2) + { + int index = 0; + return input1.Zip(input2, (a, b) => (index++, a, b)); + } + public static IEnumerable<(A?, B?)> ZipLongest(this IEnumerable input1, IEnumerable input2) { using (var it1 = input1.GetEnumerator()) diff --git a/ICSharpCode.Decompiler/Util/GraphTraversal.cs b/ICSharpCode.Decompiler/Util/GraphTraversal.cs new file mode 100644 index 00000000000..a2dc4c7b25f --- /dev/null +++ b/ICSharpCode.Decompiler/Util/GraphTraversal.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2023 Daniel Grunwald +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.Util; + +static class GraphTraversal +{ + /// + /// Depth-first-search of an graph data structure. + /// The two callbacks (successorFunc + postorderAction) will be called exactly once for each node reachable from startNodes. + /// + /// The start nodes. + /// Called multiple times per node. The first call should return true, subsequent calls must return false. + /// The first calls to this function occur in pre-order. + /// If null, normal Equals/GetHashCode will be used to compare nodes. + /// The function that gets the successors of an element. Called in pre-order. + /// Called in post-order. + /// + /// With reverse_successors=True, the start_nodes and each list of successors will be handled in reverse order. + /// This is useful if the post-order will be reversed later (e.g. for a topological sort) + /// so that blocks which could be output in either order (e.g. then-block and else-block of an if) + /// will maintain the order of the edges (then-block before else-block). + /// + public static void DepthFirstSearch(IEnumerable startNodes, Func? visitedFunc, Func?> successorFunc, Action? postorderAction = null, bool reverseSuccessors = false) + { + /* + Pseudocode: + def dfs_walk(start_nodes, successor_func, visited, postorder_func, reverse_successors): + if reverse_successors: + start_nodes = reversed(start_nodes) + for node in start_nodes: + if node in visited: continue + visited.insert(node) + children = successor_func(node) + dfs_walk(children, successor_func, visited, postorder_action, reverse_successors) + postorder_action(node) + + The actual implementation here is equivalent but does not use recursion, + so that we don't blow the stack on large graphs. + A single stack holds the "continuations" of work that needs to be done. + These can be either "visit continuations" (=execute the body of the pseudocode + loop for the given node) or "postorder continuations" (=execute postorder_action) + */ + // Use a List as stack (but allowing for the Reverse() usage) + var worklist = new List<(T node, bool isPostOrderContinuation)>(); + visitedFunc ??= new HashSet().Add; + foreach (T node in startNodes) + { + worklist.Add((node, false)); + } + if (!reverseSuccessors) + { + // Our use of a stack will reverse the order of the nodes. + // If that's not desired, restore original order by reversing twice. + worklist.Reverse(); + } + // Process outstanding continuations: + while (worklist.Count > 0) + { + var (node, isPostOrderContinuation) = worklist.Last(); + if (isPostOrderContinuation) + { + // Execute postorder_action + postorderAction?.Invoke(node); + worklist.RemoveAt(worklist.Count - 1); + continue; + } + // Execute body of loop + if (!visitedFunc(node)) + { + // Already visited + worklist.RemoveAt(worklist.Count - 1); + continue; + } + // foreach-loop-iteration will end with postorder_func call, + // so switch the type of continuation for this node + int oldWorkListSize = worklist.Count; + worklist[oldWorkListSize - 1] = (node, true); + // Create "visit continuations" for all successor nodes: + IEnumerable? children = successorFunc(node); + if (children != null) + { + foreach (T child in children) + { + worklist.Add((child, false)); + } + } + // Our use of a stack will reverse the order of the nodes. + // If that's not desired, restore original order by reversing twice. + if (!reverseSuccessors) + { + worklist.Reverse(oldWorkListSize, worklist.Count - oldWorkListSize); + } + } + } +} diff --git a/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs b/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs index 036004b1d44..803c5f9528f 100644 --- a/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs +++ b/ICSharpCode.ILSpyX/Search/AbstractEntitySearchStrategy.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Concurrent; +using System.IO; namespace ICSharpCode.ILSpyX.Search { @@ -66,7 +67,9 @@ protected bool IsInNamespaceOrAssembly(IEntity entity) { if (searchRequest.InAssembly != null) { - if (entity.ParentModule == null || !entity.ParentModule.FullAssemblyName.Contains(searchRequest.InAssembly, StringComparison.OrdinalIgnoreCase)) + if (entity.ParentModule?.PEFile == null || + !(Path.GetFileName(entity.ParentModule.PEFile.FileName).Contains(searchRequest.InAssembly, StringComparison.OrdinalIgnoreCase) + || entity.ParentModule.FullAssemblyName.Contains(searchRequest.InAssembly, StringComparison.OrdinalIgnoreCase))) { return false; } diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj index 021cfed03e7..0b9b3e9bef2 100644 --- a/ILSpy.AddIn/ILSpy.AddIn.csproj +++ b/ILSpy.AddIn/ILSpy.AddIn.csproj @@ -86,10 +86,10 @@ - \ILSpy\zh-Hans\ + \x64\ILSpy\zh-Hans\ - \ILSpy + \x64\ILSpy diff --git a/ILSpy/Controls/SearchBox.cs b/ILSpy/Controls/SearchBox.cs index b36640e1c55..4795bbe11b0 100644 --- a/ILSpy/Controls/SearchBox.cs +++ b/ILSpy/Controls/SearchBox.cs @@ -99,6 +99,15 @@ protected override void OnTextChanged(TextChangedEventArgs e) timer.Stop(); timer.Interval = this.UpdateDelay; timer.Start(); + + UpdateWatermarkLabel(); + } + + private void UpdateWatermarkLabel() + { + Label wl = (Label)GetTemplateChild("WatermarkLabel"); + if (wl != null) + wl.Visibility = HasText ? Visibility.Hidden : Visibility.Visible; } void timer_Tick(object sender, EventArgs e) @@ -114,25 +123,13 @@ void timer_Tick(object sender, EventArgs e) protected override void OnLostFocus(RoutedEventArgs e) { - if (!HasText) - { - Label wl = (Label)GetTemplateChild("WatermarkLabel"); - if (wl != null) - wl.Visibility = Visibility.Visible; - } - + UpdateWatermarkLabel(); base.OnLostFocus(e); } protected override void OnGotFocus(RoutedEventArgs e) { - if (!HasText) - { - Label wl = (Label)GetTemplateChild("WatermarkLabel"); - if (wl != null) - wl.Visibility = Visibility.Hidden; - } - + UpdateWatermarkLabel(); base.OnGotFocus(e); } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index d522ddbb901..fbe3a44aced 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -24,7 +24,6 @@ using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; -using System.Text; using System.Windows; using System.Windows.Controls; @@ -612,30 +611,30 @@ public override string EventToString(IEvent @event, bool includeDeclaringTypeNam return EntityToString(@event, includeDeclaringTypeName, includeNamespace, includeNamespaceOfDeclaringTypeName); } - string ToCSharpString(MetadataReader metadata, TypeDefinitionHandle handle, bool fullName, bool omitGenerics) + static string ToCSharpString(MetadataReader metadata, TypeDefinitionHandle handle, bool fullName, bool omitGenerics) { - StringBuilder builder = new StringBuilder(); var currentTypeDefHandle = handle; var typeDef = metadata.GetTypeDefinition(currentTypeDefHandle); + List builder = new List(); while (!currentTypeDefHandle.IsNil) { - if (builder.Length > 0) - builder.Insert(0, '.'); + if (builder.Count > 0) + builder.Add("."); typeDef = metadata.GetTypeDefinition(currentTypeDefHandle); var part = ReflectionHelper.SplitTypeParameterCountFromReflectionName(metadata.GetString(typeDef.Name), out int typeParamCount); var genericParams = typeDef.GetGenericParameters(); if (!omitGenerics && genericParams.Count > 0) { - builder.Insert(0, '>'); + builder.Add(">"); int firstIndex = genericParams.Count - typeParamCount; for (int i = genericParams.Count - 1; i >= genericParams.Count - typeParamCount; i--) { - builder.Insert(0, metadata.GetString(metadata.GetGenericParameter(genericParams[i]).Name)); - builder.Insert(0, i == firstIndex ? '<' : ','); + builder.Add(metadata.GetString(metadata.GetGenericParameter(genericParams[i]).Name)); + builder.Add(i == firstIndex ? "<" : ","); } } - builder.Insert(0, part); + builder.Add(part); currentTypeDefHandle = typeDef.GetDeclaringType(); if (!fullName) break; @@ -643,11 +642,26 @@ string ToCSharpString(MetadataReader metadata, TypeDefinitionHandle handle, bool if (fullName && !typeDef.Namespace.IsNil) { - builder.Insert(0, '.'); - builder.Insert(0, metadata.GetString(typeDef.Namespace)); + builder.Add("."); + builder.Add(metadata.GetString(typeDef.Namespace)); } - return builder.ToString(); + switch (builder.Count) + { + case 0: + return string.Empty; + case 1: + return builder[0]; + case 2: + return builder[1] + builder[0]; + case 3: + return builder[2] + builder[1] + builder[0]; + case 4: + return builder[3] + builder[2] + builder[1] + builder[0]; + default: + builder.Reverse(); + return string.Concat(builder); + } } public override string GetEntityName(PEFile module, EntityHandle handle, bool fullName, bool omitGenerics) diff --git a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs index af78f304421..bc939f1bcad 100644 --- a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +40,7 @@ public ClassLayoutTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -49,7 +51,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) var list = new List(); var length = metadata.GetTableRowCount(TableIndex.ClassLayout); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; ClassLayoutEntry scrollTargetEntry = default; @@ -80,15 +82,15 @@ readonly struct ClassLayout public readonly EntityHandle Parent; public readonly uint ClassSize; - public unsafe ClassLayout(byte* ptr, int typeDefSize) + public ClassLayout(ReadOnlySpan ptr, int typeDefSize) { - PackingSize = (ushort)Helpers.GetValue(ptr, 2); - ClassSize = (uint)Helpers.GetValue(ptr + 2, 4); - Parent = MetadataTokens.TypeDefinitionHandle(Helpers.GetValue(ptr + 6, typeDefSize)); + PackingSize = BinaryPrimitives.ReadUInt16LittleEndian(ptr); + ClassSize = BinaryPrimitives.ReadUInt32LittleEndian(ptr.Slice(2, 4)); + Parent = MetadataTokens.TypeDefinitionHandle(Helpers.GetValueLittleEndian(ptr.Slice(6, typeDefSize))); } } - unsafe struct ClassLayoutEntry + struct ClassLayoutEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -117,7 +119,7 @@ public void OnParentClick() [ColumnInfo("X8", Kind = ColumnKind.Other)] public uint ClassSize => classLayout.ClassSize; - public ClassLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public ClassLayoutEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -125,7 +127,7 @@ public ClassLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) var rowOffset = metadata.GetTableMetadataOffset(TableIndex.ClassLayout) + metadata.GetTableRowSize(TableIndex.ClassLayout) * (row - 1); this.Offset = metadataOffset + rowOffset; - this.classLayout = new ClassLayout(ptr + rowOffset, metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4); + this.classLayout = new ClassLayout(ptr.Slice(rowOffset), metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4); this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs index ae083370849..1d94b78c219 100644 --- a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +39,7 @@ public EventMapTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -50,7 +51,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) EventMapEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.EventMap); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { @@ -79,14 +80,14 @@ readonly struct EventMap public readonly TypeDefinitionHandle Parent; public readonly EventDefinitionHandle EventList; - public unsafe EventMap(byte* ptr, int typeDefSize, int eventDefSize) + public EventMap(ReadOnlySpan ptr, int typeDefSize, int eventDefSize) { - Parent = MetadataTokens.TypeDefinitionHandle(Helpers.GetValue(ptr, typeDefSize)); - EventList = MetadataTokens.EventDefinitionHandle(Helpers.GetValue(ptr + typeDefSize, eventDefSize)); + Parent = MetadataTokens.TypeDefinitionHandle(Helpers.GetValueLittleEndian(ptr.Slice(0, typeDefSize))); + EventList = MetadataTokens.EventDefinitionHandle(Helpers.GetValueLittleEndian(ptr.Slice(typeDefSize, eventDefSize))); } } - unsafe struct EventMapEntry + struct EventMapEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -120,7 +121,7 @@ public void OnEventListClick() string eventListTooltip; public string EventListTooltip => GenerateTooltip(ref eventListTooltip, module, eventMap.EventList); - public EventMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public EventMapEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -130,7 +131,7 @@ public EventMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) this.Offset = metadataOffset + rowOffset; int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; int eventDefSize = metadata.GetTableRowCount(TableIndex.Event) < ushort.MaxValue ? 2 : 4; - this.eventMap = new EventMap(ptr + rowOffset, typeDefSize, eventDefSize); + this.eventMap = new EventMap(ptr.Slice(rowOffset), typeDefSize, eventDefSize); this.parentTooltip = null; this.eventListTooltip = null; } diff --git a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs index 8a80966b0cb..f5da959eb75 100644 --- a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +40,7 @@ public FieldLayoutTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -50,7 +52,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) FieldLayoutEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.FieldLayout); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { @@ -79,14 +81,14 @@ readonly struct FieldLayout public readonly int Offset; public readonly FieldDefinitionHandle Field; - public unsafe FieldLayout(byte* ptr, int fieldDefSize) + public FieldLayout(ReadOnlySpan ptr, int fieldDefSize) { - Offset = Helpers.GetValue(ptr, 4); - Field = MetadataTokens.FieldDefinitionHandle(Helpers.GetValue(ptr + 4, fieldDefSize)); + Offset = BinaryPrimitives.ReadInt32LittleEndian(ptr); + Field = MetadataTokens.FieldDefinitionHandle(Helpers.GetValueLittleEndian(ptr.Slice(4, fieldDefSize))); } } - unsafe struct FieldLayoutEntry + struct FieldLayoutEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -112,7 +114,7 @@ public void OnFieldClick() [ColumnInfo("X8", Kind = ColumnKind.Other)] public int FieldOffset => fieldLayout.Offset; - public FieldLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public FieldLayoutEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -121,7 +123,7 @@ public FieldLayoutEntry(PEFile module, byte* ptr, int metadataOffset, int row) + metadata.GetTableRowSize(TableIndex.FieldLayout) * (row - 1); this.Offset = metadataOffset + rowOffset; int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4; - this.fieldLayout = new FieldLayout(ptr + rowOffset, fieldDefSize); + this.fieldLayout = new FieldLayout(ptr.Slice(rowOffset), fieldDefSize); this.fieldTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs index a9f26ff130b..b6cc9771eb7 100644 --- a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +39,7 @@ public FieldMarshalTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -50,7 +51,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) FieldMarshalEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.FieldMarshal); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { @@ -79,14 +80,14 @@ readonly struct FieldMarshal public readonly BlobHandle NativeType; public readonly EntityHandle Parent; - public unsafe FieldMarshal(byte* ptr, int blobHeapSize, int hasFieldMarshalRefSize) + public FieldMarshal(ReadOnlySpan ptr, int blobHeapSize, int hasFieldMarshalRefSize) { - Parent = Helpers.FromHasFieldMarshalTag((uint)Helpers.GetValue(ptr, hasFieldMarshalRefSize)); - NativeType = MetadataTokens.BlobHandle(Helpers.GetValue(ptr + hasFieldMarshalRefSize, blobHeapSize)); + Parent = Helpers.FromHasFieldMarshalTag((uint)Helpers.GetValueLittleEndian(ptr, hasFieldMarshalRefSize)); + NativeType = MetadataTokens.BlobHandle(Helpers.GetValueLittleEndian(ptr.Slice(hasFieldMarshalRefSize, blobHeapSize))); } } - unsafe struct FieldMarshalEntry + struct FieldMarshalEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -112,7 +113,7 @@ public void OnParentClick() [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int NativeType => MetadataTokens.GetHeapOffset(fieldMarshal.NativeType); - public FieldMarshalEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public FieldMarshalEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -122,7 +123,7 @@ public FieldMarshalEntry(PEFile module, byte* ptr, int metadataOffset, int row) this.Offset = metadataOffset + rowOffset; int hasFieldMarshalRefSize = metadata.ComputeCodedTokenSize(32768, TableMask.Field | TableMask.Param); int blobHeapSize = metadata.GetHeapSize(HeapIndex.Blob) < ushort.MaxValue ? 2 : 4; - this.fieldMarshal = new FieldMarshal(ptr + rowOffset, blobHeapSize, hasFieldMarshalRefSize); + this.fieldMarshal = new FieldMarshal(ptr.Slice(rowOffset), blobHeapSize, hasFieldMarshalRefSize); this.parentTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs index 9317bb33675..dc34c5c23c1 100644 --- a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +40,7 @@ public FieldRVATableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -50,7 +52,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) FieldRVAEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.FieldRva); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { @@ -79,14 +81,14 @@ readonly struct FieldRVA public readonly int Offset; public readonly FieldDefinitionHandle Field; - public unsafe FieldRVA(byte* ptr, int fieldDefSize) + public FieldRVA(ReadOnlySpan ptr, int fieldDefSize) { - Offset = Helpers.GetValue(ptr, 4); - Field = MetadataTokens.FieldDefinitionHandle(Helpers.GetValue(ptr + 4, fieldDefSize)); + Offset = BinaryPrimitives.ReadInt32LittleEndian(ptr); + Field = MetadataTokens.FieldDefinitionHandle(Helpers.GetValueLittleEndian(ptr.Slice(4, fieldDefSize))); } } - unsafe struct FieldRVAEntry + struct FieldRVAEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -112,7 +114,7 @@ public void OnFieldClick() [ColumnInfo("X8", Kind = ColumnKind.Other)] public int FieldOffset => fieldRVA.Offset; - public FieldRVAEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public FieldRVAEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -121,7 +123,7 @@ public FieldRVAEntry(PEFile module, byte* ptr, int metadataOffset, int row) + metadata.GetTableRowSize(TableIndex.FieldRva) * (row - 1); this.Offset = metadataOffset + rowOffset; int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4; - this.fieldRVA = new FieldRVA(ptr + rowOffset, fieldDefSize); + this.fieldRVA = new FieldRVA(ptr.Slice(rowOffset), fieldDefSize); this.fieldTooltip = null; } } diff --git a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs index c6c228ba69b..9a7642b25ea 100644 --- a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -40,7 +42,9 @@ public ImplMapTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + + + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -52,11 +56,11 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) ImplMapEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.ImplMap); - byte* ptr = metadata.MetadataPointer; + var span = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { - ImplMapEntry entry = new ImplMapEntry(module, ptr, metadataOffset, rid); + ImplMapEntry entry = new ImplMapEntry(module, span, metadataOffset, rid); if (entry.RID == this.scrollTarget) { scrollTargetEntry = entry; @@ -83,16 +87,16 @@ readonly struct ImplMap public readonly StringHandle ImportName; public readonly ModuleReferenceHandle ImportScope; - public unsafe ImplMap(byte* ptr, int moduleRefSize, int memberForwardedTagRefSize, int stringHandleSize) + public ImplMap(ReadOnlySpan span, int moduleRefSize, int memberForwardedTagRefSize, int stringHandleSize) { - MappingFlags = (PInvokeAttributes)Helpers.GetValue(ptr, 2); - MemberForwarded = Helpers.FromMemberForwardedTag((uint)Helpers.GetValue(ptr + 2, memberForwardedTagRefSize)); - ImportName = MetadataTokens.StringHandle(Helpers.GetValue(ptr + 2 + memberForwardedTagRefSize, stringHandleSize)); - ImportScope = MetadataTokens.ModuleReferenceHandle(Helpers.GetValue(ptr + 2 + memberForwardedTagRefSize + stringHandleSize, moduleRefSize)); + MappingFlags = (PInvokeAttributes)BinaryPrimitives.ReadUInt16LittleEndian(span); + MemberForwarded = Helpers.FromMemberForwardedTag((uint)Helpers.GetValueLittleEndian(span.Slice(2, memberForwardedTagRefSize))); + ImportName = MetadataTokens.StringHandle(Helpers.GetValueLittleEndian(span.Slice(2 + memberForwardedTagRefSize, stringHandleSize))); + ImportScope = MetadataTokens.ModuleReferenceHandle(Helpers.GetValueLittleEndian(span.Slice(2 + memberForwardedTagRefSize + stringHandleSize, moduleRefSize))); } } - unsafe struct ImplMapEntry + struct ImplMapEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -141,7 +145,7 @@ public void OnImportScopeClick() public string ImportNameTooltip => $"{MetadataTokens.GetHeapOffset(implMap.ImportName):X} \"{ImportName}\""; - public unsafe ImplMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public ImplMapEntry(PEFile module, ReadOnlySpan span, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -152,7 +156,7 @@ public unsafe ImplMapEntry(PEFile module, byte* ptr, int metadataOffset, int row int moduleRefSize = metadata.GetTableRowCount(TableIndex.ModuleRef) < ushort.MaxValue ? 2 : 4; int memberForwardedTagRefSize = metadata.ComputeCodedTokenSize(32768, TableMask.MethodDef | TableMask.Field); int stringHandleSize = metadata.GetHeapSize(HeapIndex.String) < ushort.MaxValue ? 2 : 4; - this.implMap = new ImplMap(ptr + rowOffset, moduleRefSize, memberForwardedTagRefSize, stringHandleSize); + this.implMap = new ImplMap(span.Slice(rowOffset), moduleRefSize, memberForwardedTagRefSize, stringHandleSize); this.importScopeTooltip = null; this.memberForwardedTooltip = null; } diff --git a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs index 35c66ab8508..e1ea243bfb5 100644 --- a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +39,7 @@ public InterfaceImplTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -50,7 +51,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) InterfaceImplEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.InterfaceImpl); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { @@ -79,14 +80,14 @@ readonly struct InterfaceImpl public readonly EntityHandle Class; public readonly EntityHandle Interface; - public unsafe InterfaceImpl(byte* ptr, int classSize, int interfaceSize) + public InterfaceImpl(ReadOnlySpan ptr, int classSize, int interfaceSize) { - Class = MetadataTokens.TypeDefinitionHandle(Helpers.GetValue(ptr, classSize)); - Interface = Helpers.FromTypeDefOrRefTag((uint)Helpers.GetValue(ptr + classSize, interfaceSize)); + Class = MetadataTokens.TypeDefinitionHandle(Helpers.GetValueLittleEndian(ptr, classSize)); + Interface = Helpers.FromTypeDefOrRefTag((uint)Helpers.GetValueLittleEndian(ptr.Slice(classSize, interfaceSize))); } } - unsafe struct InterfaceImplEntry + struct InterfaceImplEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -120,7 +121,7 @@ public void OnInterfaceClick() string interfaceTooltip; public string InterfaceTooltip => GenerateTooltip(ref interfaceTooltip, module, interfaceImpl.Interface); - public InterfaceImplEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public InterfaceImplEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -128,7 +129,7 @@ public InterfaceImplEntry(PEFile module, byte* ptr, int metadataOffset, int row) var rowOffset = metadata.GetTableMetadataOffset(TableIndex.InterfaceImpl) + metadata.GetTableRowSize(TableIndex.InterfaceImpl) * (row - 1); this.Offset = metadataOffset + rowOffset; - this.interfaceImpl = new InterfaceImpl(ptr + rowOffset, metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4, metadata.ComputeCodedTokenSize(16384, TableMask.TypeDef | TableMask.TypeRef | TableMask.TypeSpec)); + this.interfaceImpl = new InterfaceImpl(ptr.Slice(rowOffset), metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4, metadata.ComputeCodedTokenSize(16384, TableMask.TypeDef | TableMask.TypeRef | TableMask.TypeSpec)); this.interfaceTooltip = null; this.classTooltip = null; } diff --git a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs index de347fd3327..acdcaad8f54 100644 --- a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +39,7 @@ public NestedClassTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -50,7 +51,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) NestedClassEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.NestedClass); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { @@ -79,14 +80,14 @@ readonly struct NestedClass public readonly TypeDefinitionHandle Nested; public readonly TypeDefinitionHandle Enclosing; - public unsafe NestedClass(byte* ptr, int typeDefSize) + public NestedClass(ReadOnlySpan ptr, int typeDefSize) { - Nested = MetadataTokens.TypeDefinitionHandle(Helpers.GetValue(ptr, typeDefSize)); - Enclosing = MetadataTokens.TypeDefinitionHandle(Helpers.GetValue(ptr + typeDefSize, typeDefSize)); + Nested = MetadataTokens.TypeDefinitionHandle(Helpers.GetValueLittleEndian(ptr, typeDefSize)); + Enclosing = MetadataTokens.TypeDefinitionHandle(Helpers.GetValueLittleEndian(ptr.Slice(typeDefSize), typeDefSize)); } } - unsafe struct NestedClassEntry + struct NestedClassEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -120,7 +121,7 @@ public void OnEnclosingClassClick() string enclosingClassTooltip; public string EnclosingClassTooltip => GenerateTooltip(ref enclosingClassTooltip, module, nestedClass.Enclosing); - public unsafe NestedClassEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public NestedClassEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -129,7 +130,7 @@ public unsafe NestedClassEntry(PEFile module, byte* ptr, int metadataOffset, int + metadata.GetTableRowSize(TableIndex.NestedClass) * (row - 1); this.Offset = metadataOffset + rowOffset; int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; - this.nestedClass = new NestedClass(ptr + rowOffset, typeDefSize); + this.nestedClass = new NestedClass(ptr.Slice(rowOffset), typeDefSize); this.nestedClassTooltip = null; this.enclosingClassTooltip = null; } diff --git a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs index 64d9bb19ad8..d07b126c497 100644 --- a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -38,7 +39,7 @@ public PropertyMapTableTreeNode(PEFile module) public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -50,7 +51,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) PropertyMapEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.PropertyMap); - byte* ptr = metadata.MetadataPointer; + ReadOnlySpan ptr = metadata.AsReadOnlySpan(); int metadataOffset = module.Reader.PEHeaders.MetadataStartOffset; for (int rid = 1; rid <= length; rid++) { @@ -79,14 +80,14 @@ readonly struct PropertyMap public readonly TypeDefinitionHandle Parent; public readonly PropertyDefinitionHandle PropertyList; - public unsafe PropertyMap(byte* ptr, int typeDefSize, int propertyDefSize) + public PropertyMap(ReadOnlySpan ptr, int typeDefSize, int propertyDefSize) { - Parent = MetadataTokens.TypeDefinitionHandle(Helpers.GetValue(ptr, typeDefSize)); - PropertyList = MetadataTokens.PropertyDefinitionHandle(Helpers.GetValue(ptr + typeDefSize, propertyDefSize)); + Parent = MetadataTokens.TypeDefinitionHandle(Helpers.GetValueLittleEndian(ptr, typeDefSize)); + PropertyList = MetadataTokens.PropertyDefinitionHandle(Helpers.GetValueLittleEndian(ptr.Slice(typeDefSize, propertyDefSize))); } } - unsafe struct PropertyMapEntry + struct PropertyMapEntry { readonly PEFile module; readonly MetadataReader metadata; @@ -120,7 +121,7 @@ public void OnPropertyListClick() string propertyListTooltip; public string PropertyListTooltip => GenerateTooltip(ref propertyListTooltip, module, propertyMap.PropertyList); - public PropertyMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) + public PropertyMapEntry(PEFile module, ReadOnlySpan ptr, int metadataOffset, int row) { this.module = module; this.metadata = module.Metadata; @@ -130,7 +131,7 @@ public PropertyMapEntry(PEFile module, byte* ptr, int metadataOffset, int row) this.Offset = metadataOffset + rowOffset; int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; int propertyDefSize = metadata.GetTableRowCount(TableIndex.Property) < ushort.MaxValue ? 2 : 4; - this.propertyMap = new PropertyMap(ptr + rowOffset, typeDefSize, propertyDefSize); + this.propertyMap = new PropertyMap(ptr.Slice(rowOffset), typeDefSize, propertyDefSize); this.propertyListTooltip = null; this.parentTooltip = null; } diff --git a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs index e93c0c3d9a1..f4a463e78dc 100644 --- a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs @@ -44,7 +44,7 @@ public StateMachineMethodTableTreeNode(PEFile module, MetadataReader metadata, b public override object Icon => Images.Literal; - public unsafe override bool View(ViewModels.TabPageModel tabPage) + public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); tabPage.SupportsLanguageSwitching = false; @@ -53,7 +53,7 @@ public unsafe override bool View(ViewModels.TabPageModel tabPage) var list = new List(); StateMachineMethodEntry scrollTargetEntry = default; var length = metadata.GetTableRowCount(TableIndex.StateMachineMethod); - var reader = new BlobReader(metadata.MetadataPointer, metadata.MetadataLength); + var reader = metadata.AsBlobReader(); reader.Offset = metadata.GetTableMetadataOffset(TableIndex.StateMachineMethod); for (int rid = 1; rid <= length; rid++) diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index da418b554af..792b73e1f4b 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -212,11 +212,20 @@ static void ApplyAttributes(PropertyDescriptor descriptor, Binding binding, Data } } + [Obsolete("Use safe GetValueLittleEndian(ReadOnlySpan) or appropriate BinaryPrimitives.Read* method")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetValue(byte* ptr, int size) + => GetValueLittleEndian(new ReadOnlySpan(ptr, size)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetValueLittleEndian(ReadOnlySpan ptr, int size) + => GetValueLittleEndian(ptr.Slice(0, size)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetValueLittleEndian(ReadOnlySpan ptr) { int result = 0; - for (int i = 0; i < size; i += 2) + for (int i = 0; i < ptr.Length; i += 2) { result |= ptr[i] << 8 * i; result |= ptr[i + 1] << 8 * (i + 1);