From fae6f57b1c8be93a5919b312d2c886e0aec61aaa Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 23 Mar 2026 20:46:15 +0100 Subject: [PATCH] Fix #3671: TransformCollectionAndObjectInitializers mistakenly included trailing variable initialization. --- .../IL/Instructions/Block.cs | 20 ++++++++++- ...ransformCollectionAndObjectInitializers.cs | 33 ++++++++++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index 1749bc1e6..2b0b3991e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -185,7 +185,25 @@ namespace ICSharpCode.Decompiler.IL } for (int i = 1; i < Instructions.Count; i++) { - Debug.Assert(Instructions[i] is StLoc || AccessPathElement.GetAccessPath(Instructions[i], type2).Kind != AccessPathKind.Invalid); + if (Instructions[i] is StLoc { Variable: var v }) + { + foreach (var inst in v.LoadInstructions) + { + Debug.Assert(inst.IsDescendantOf(this)); + } + foreach (var inst in v.AddressInstructions) + { + Debug.Assert(inst.IsDescendantOf(this)); + } + foreach (ILInstruction inst in v.StoreInstructions) + { + Debug.Assert(inst.IsDescendantOf(this)); + } + } + else + { + Debug.Assert(AccessPathElement.GetAccessPath(Instructions[i], type2).Kind != AccessPathKind.Invalid); + } } break; case BlockKind.DeconstructionConversions: diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index 7a3e47d40..20f563cd6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -242,7 +242,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms possibleIndexVariables.Add(stloc.Variable, (stloc.ChildIndex, stloc.Value)); return true; } - (var kind, var newPath, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType, context.Settings, context.CSharpResolver, possibleIndexVariables); + (var kind, var newPath, var values, var targetVariable, var usedIndices) = AccessPathElement.GetAccessPath(instructions[pos], rootType, context.Settings, context.CSharpResolver); if (kind == AccessPathKind.Invalid || target != targetVariable) return false; // Treat last element separately: @@ -274,6 +274,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms isCollection = true; if (pathStack.Peek().Count != 0) return false; + MarkUsedIndices(); return true; case AccessPathKind.Setter: if (isCollection || !pathStack.Peek().Add(lastElement)) @@ -283,10 +284,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer) blockKind = BlockKind.ObjectInitializer; initializerContainsInitOnlyItems |= lastElement.Member is IProperty { Setter.IsInitOnly: true }; + MarkUsedIndices(); return true; default: return false; } + + void MarkUsedIndices() + { + foreach (var index in usedIndices) + { + if (possibleIndexVariables.TryGetValue(index, out var item)) + { + possibleIndexVariables[index] = (-1, item.Value); + } + } + } } bool IsValidObjectInitializerTarget(List path) @@ -330,10 +343,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms public override string ToString() => $"[{Member}, {Indices}]"; - public static (AccessPathKind Kind, List Path, List? Values, ILVariable? Target) GetAccessPath( + public static (AccessPathKind Kind, List Path, List? Values, ILVariable? Target, List UsedIndexVariables) GetAccessPath( ILInstruction instruction, IType rootType, DecompilerSettings? settings = null, - CSharpResolver? resolver = null, - Dictionary? possibleIndexVariables = null) + CSharpResolver? resolver = null) { List path = new List(); ILVariable? target = null; @@ -341,6 +353,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms List? values = null; IMethod method; ILInstruction? inst = instruction; + List usedIndexVariables = new(); while (inst != null) { switch (inst) @@ -368,14 +381,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray(); if (indices.Length > 0 && settings?.DictionaryInitializers == false) goto default; - if (possibleIndexVariables != null) + // Mark all index variables as used + foreach (var index in indices.OfType()) { - // Mark all index variables as used - foreach (var index in indices.OfType()) - { - if (possibleIndexVariables.TryGetValue(index.Variable, out var info)) - possibleIndexVariables[index.Variable] = (-1, info.Value); - } + usedIndexVariables.Add(index.Variable); } path.Insert(0, new AccessPathElement(call.OpCode, method.AccessorOwner, indices)); } @@ -460,7 +469,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (kind != AccessPathKind.Invalid && values != null && values.SelectMany(v => v.Descendants).OfType().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca))) kind = AccessPathKind.Invalid; - return (kind, path, values, target); + return (kind, path, values, target, usedIndexVariables); } private static bool CanBeUsedInInitializer(IProperty property, ITypeResolveContext? resolveContext, AccessPathKind kind)