mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 11:09:54 +00:00
GetPointerElementType existed because a pointer passing through a stack slot could be typed IntPtr: ILReader replaced the slot type with FindType(StackType) whenever the inferred type did not match the stack type. With InferType() implemented on every ILInstruction that fallback is gone (FlushExpressionStack now asserts the inferred type is stack-accurate), so the target's inferred type is precise and the definition chain no longer needs to be walked. Merged stack slots were never recovered by the helper anyway (it required a single store). Disabling the PointerType arm makes the uint*/byte* deconstruction fixtures fail, so the sign-agnostic stobj.Type fallback remains guarded. Assisted-by: Claude:claude-fable-5:Claude Code
330 lines
9.6 KiB
C#
330 lines
9.6 KiB
C#
// Copyright (c) 2020 Siegfried Pammer
|
|
//
|
|
// 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.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
|
|
namespace ICSharpCode.Decompiler.IL
|
|
{
|
|
partial class DeconstructInstruction
|
|
{
|
|
public static readonly SlotInfo InitSlot = new SlotInfo("Init", canInlineInto: true, isCollection: true);
|
|
public static readonly SlotInfo PatternSlot = new SlotInfo("Pattern", canInlineInto: true);
|
|
public static readonly SlotInfo ConversionsSlot = new SlotInfo("Conversions");
|
|
public static readonly SlotInfo AssignmentsSlot = new SlotInfo("Assignments");
|
|
|
|
public DeconstructInstruction()
|
|
: base(OpCode.DeconstructInstruction)
|
|
{
|
|
this.Init = new InstructionCollection<StLoc>(this, 0);
|
|
}
|
|
|
|
public readonly InstructionCollection<StLoc> Init;
|
|
|
|
MatchInstruction pattern;
|
|
public MatchInstruction Pattern {
|
|
get { return this.pattern; }
|
|
set {
|
|
ValidateChild(value);
|
|
SetChildInstruction(ref this.pattern, value, Init.Count);
|
|
}
|
|
}
|
|
|
|
Block conversions;
|
|
public Block Conversions {
|
|
get { return this.conversions; }
|
|
set {
|
|
ValidateChild(value);
|
|
SetChildInstruction(ref this.conversions, value, Init.Count + 1);
|
|
}
|
|
}
|
|
|
|
Block assignments;
|
|
public Block Assignments {
|
|
get { return this.assignments; }
|
|
set {
|
|
ValidateChild(value);
|
|
SetChildInstruction(ref this.assignments, value, Init.Count + 2);
|
|
}
|
|
}
|
|
|
|
protected sealed override int GetChildCount()
|
|
{
|
|
return Init.Count + 3;
|
|
}
|
|
|
|
protected sealed override ILInstruction GetChild(int index)
|
|
{
|
|
switch (index - Init.Count)
|
|
{
|
|
case 0:
|
|
return this.pattern;
|
|
case 1:
|
|
return this.conversions;
|
|
case 2:
|
|
return this.assignments;
|
|
default:
|
|
return this.Init[index];
|
|
}
|
|
}
|
|
|
|
protected sealed override void SetChild(int index, ILInstruction value)
|
|
{
|
|
switch (index - Init.Count)
|
|
{
|
|
case 0:
|
|
this.Pattern = (MatchInstruction)value;
|
|
break;
|
|
case 1:
|
|
this.Conversions = (Block)value;
|
|
break;
|
|
case 2:
|
|
this.Assignments = (Block)value;
|
|
break;
|
|
default:
|
|
this.Init[index] = (StLoc)value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected sealed override SlotInfo GetChildSlot(int index)
|
|
{
|
|
switch (index - Init.Count)
|
|
{
|
|
case 0:
|
|
return PatternSlot;
|
|
case 1:
|
|
return ConversionsSlot;
|
|
case 2:
|
|
return AssignmentsSlot;
|
|
default:
|
|
return InitSlot;
|
|
}
|
|
}
|
|
|
|
public sealed override ILInstruction Clone()
|
|
{
|
|
var clone = new DeconstructInstruction();
|
|
clone.Init.AddRange(this.Init.Select(inst => (StLoc)inst.Clone()));
|
|
clone.Pattern = (MatchInstruction)this.pattern.Clone();
|
|
clone.Conversions = (Block)this.conversions.Clone();
|
|
clone.Assignments = (Block)this.assignments.Clone();
|
|
return clone;
|
|
}
|
|
|
|
protected override InstructionFlags ComputeFlags()
|
|
{
|
|
var flags = InstructionFlags.None;
|
|
foreach (var inst in Init)
|
|
{
|
|
flags |= inst.Flags;
|
|
}
|
|
flags |= pattern.Flags | conversions.Flags | assignments.Flags;
|
|
return flags;
|
|
}
|
|
|
|
public override InstructionFlags DirectFlags {
|
|
get {
|
|
return InstructionFlags.None;
|
|
}
|
|
}
|
|
|
|
protected internal override void InstructionCollectionUpdateComplete()
|
|
{
|
|
base.InstructionCollectionUpdateComplete();
|
|
if (pattern.Parent == this)
|
|
pattern.ChildIndex = Init.Count;
|
|
if (conversions.Parent == this)
|
|
conversions.ChildIndex = Init.Count + 1;
|
|
if (assignments.Parent == this)
|
|
assignments.ChildIndex = Init.Count + 2;
|
|
}
|
|
|
|
protected override void WriteToCore(ITextOutput output, ILAstWritingOptions options)
|
|
{
|
|
WriteILRange(output, options);
|
|
output.Write("deconstruct ");
|
|
output.MarkFoldStart("{...}");
|
|
output.WriteLine("{");
|
|
output.Indent();
|
|
output.WriteLine("init:");
|
|
output.Indent();
|
|
foreach (var inst in this.Init)
|
|
{
|
|
inst.WriteTo(output, options);
|
|
output.WriteLine();
|
|
}
|
|
output.Unindent();
|
|
output.WriteLine("pattern:");
|
|
output.Indent();
|
|
pattern.WriteTo(output, options);
|
|
output.Unindent();
|
|
output.WriteLine();
|
|
output.Write("conversions: ");
|
|
conversions.WriteTo(output, options);
|
|
output.WriteLine();
|
|
output.Write("assignments: ");
|
|
assignments.WriteTo(output, options);
|
|
output.Unindent();
|
|
output.WriteLine();
|
|
output.Write('}');
|
|
output.MarkFoldEnd();
|
|
}
|
|
|
|
internal static bool IsConversionStLoc(ILInstruction inst, out ILVariable variable, out ILVariable inputVariable)
|
|
{
|
|
inputVariable = null;
|
|
if (!inst.MatchStLoc(out variable, out var value))
|
|
return false;
|
|
ILInstruction input;
|
|
switch (value)
|
|
{
|
|
case Conv conv:
|
|
input = conv.Argument;
|
|
break;
|
|
//case Call { Method: { IsOperator: true, Name: "op_Implicit" }, Arguments: { Count: 1 } } call:
|
|
// input = call.Arguments[0];
|
|
// break;
|
|
default:
|
|
return false;
|
|
}
|
|
return input.MatchLdLoc(out inputVariable) || input.MatchLdLoca(out inputVariable);
|
|
}
|
|
|
|
internal static bool IsAssignment(ILInstruction inst, ICompilation typeSystem, out IType expectedType, out ILInstruction value)
|
|
{
|
|
expectedType = null;
|
|
value = null;
|
|
switch (inst)
|
|
{
|
|
case CallInstruction call:
|
|
if (call.Method.AccessorKind != System.Reflection.MethodSemanticsAttributes.Setter)
|
|
return false;
|
|
for (int i = 0; i < call.Arguments.Count - 1; i++)
|
|
{
|
|
ILInstruction arg = call.Arguments[i];
|
|
if (arg.Flags == InstructionFlags.None)
|
|
{
|
|
// OK - we accept integer literals, etc.
|
|
}
|
|
else if (arg.MatchLdLoc(out var v))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
expectedType = call.Method.Parameters.Last().Type;
|
|
value = call.Arguments.Last();
|
|
return true;
|
|
case StLoc stloc:
|
|
expectedType = stloc.Variable.Type;
|
|
value = stloc.Value;
|
|
return true;
|
|
case StObj stobj:
|
|
var target = stobj.Target;
|
|
while (target.MatchLdFlda(out var nestedTarget, out _))
|
|
target = nestedTarget;
|
|
if (target.Flags == InstructionFlags.None)
|
|
{
|
|
// OK - we accept integer literals, etc.
|
|
}
|
|
else if (target.MatchLdLoc(out var v))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
// stobj.Type comes from the store opcode and is sign-agnostic (an int* and a
|
|
// uint* target both store int32), so for pointer targets the element type of
|
|
// the target's inferred type must be used instead.
|
|
expectedType = stobj.Target.InferType(typeSystem) switch {
|
|
ByReferenceType brt => brt.ElementType,
|
|
PointerType ptr => ptr.ElementType,
|
|
_ => stobj.Type
|
|
};
|
|
value = stobj.Value;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal override void CheckInvariant(ILPhase phase, ICompilation compilation)
|
|
{
|
|
base.CheckInvariant(phase, compilation);
|
|
var patternVariables = new HashSet<ILVariable>();
|
|
var conversionVariables = new HashSet<ILVariable>();
|
|
|
|
foreach (StLoc init in this.Init)
|
|
{
|
|
Debug.Assert(init.Variable.IsSingleDefinition && init.Variable.LoadCount == 1);
|
|
Debug.Assert(init.Variable.LoadInstructions[0].IsDescendantOf(assignments));
|
|
}
|
|
|
|
ValidatePattern(pattern);
|
|
|
|
foreach (var inst in this.conversions.Instructions)
|
|
{
|
|
if (!IsConversionStLoc(inst, out var variable, out var inputVariable))
|
|
Debug.Fail("inst is not a conversion stloc!");
|
|
Debug.Assert(variable.IsSingleDefinition && variable.LoadCount == 1);
|
|
Debug.Assert(variable.LoadInstructions[0].IsDescendantOf(assignments));
|
|
Debug.Assert(patternVariables.Contains(inputVariable));
|
|
conversionVariables.Add(variable);
|
|
}
|
|
Debug.Assert(this.conversions.FinalInstruction is Nop);
|
|
|
|
foreach (var inst in assignments.Instructions)
|
|
{
|
|
if (!(IsAssignment(inst, compilation, out _, out var value) && value.MatchLdLoc(out var inputVariable)))
|
|
throw new InvalidOperationException("inst is not an assignment!");
|
|
Debug.Assert(patternVariables.Contains(inputVariable) || conversionVariables.Contains(inputVariable));
|
|
}
|
|
Debug.Assert(this.assignments.FinalInstruction is Nop);
|
|
|
|
void ValidatePattern(MatchInstruction inst)
|
|
{
|
|
Debug.Assert(inst.IsDeconstructCall || inst.IsDeconstructTuple);
|
|
Debug.Assert(!inst.CheckNotNull && !inst.CheckType);
|
|
Debug.Assert(!inst.HasDesignator);
|
|
foreach (var subPattern in inst.SubPatterns.Cast<MatchInstruction>())
|
|
{
|
|
if (subPattern.IsVar)
|
|
{
|
|
Debug.Assert(subPattern.Variable.IsSingleDefinition && subPattern.Variable.LoadCount <= 1);
|
|
if (subPattern.Variable.LoadCount == 1)
|
|
Debug.Assert(subPattern.Variable.LoadInstructions[0].IsDescendantOf(this));
|
|
patternVariables.Add(subPattern.Variable);
|
|
}
|
|
else
|
|
{
|
|
ValidatePattern(subPattern);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|