/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnSpy.AsmEditor.Commands;
using dnSpy.AsmEditor.DnlibDialogs;
using dnSpy.AsmEditor.ViewHelpers;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.MVVM;
namespace dnSpy.AsmEditor.MethodBody {
sealed class CilBodyVM : ViewModelBase {
readonly CilBodyOptions origOptions;
public ISelectItems SelectItems {
set => selectItems = value;
}
ISelectItems? selectItems;
public ICommand ReinitializeCommand => new RelayCommand(a => Reinitialize());
public ICommand SimplifyAllInstructionsCommand => new RelayCommand(a => SimplifyAllInstructions(), a => SimplifyAllInstructionsCanExecute());
public ICommand OptimizeAllInstructionsCommand => new RelayCommand(a => OptimizeAllInstructions(), a => OptimizeAllInstructionsCanExecute());
public ICommand ReplaceInstructionWithNopCommand => new RelayCommand(a => ReplaceInstructionWithNop((InstructionVM[])a!), a => ReplaceInstructionWithNopCanExecute((InstructionVM[])a!));
public ICommand ReplaceInstructionWithMultipleNopsCommand => new RelayCommand(a => ReplaceInstructionWithMultipleNops((InstructionVM[])a!), a => ReplaceInstructionWithMultipleNopsCanExecute((InstructionVM[])a!));
public ICommand InvertBranchCommand => new RelayCommand(a => InvertBranch((InstructionVM[])a!), a => InvertBranchCanExecute((InstructionVM[])a!));
public ICommand ConvertBranchToUnconditionalBranchCommand => new RelayCommand(a => ConvertBranchToUnconditionalBranch((InstructionVM[])a!), a => ConvertBranchToUnconditionalBranchCanExecute((InstructionVM[])a!));
public ICommand RemoveInstructionAndAddPopsCommand => new RelayCommand(a => RemoveInstructionAndAddPops((InstructionVM[])a!), a => RemoveInstructionAndAddPopsCanExecute((InstructionVM[])a!));
public bool KeepOldMaxStack {
get => keepOldMaxStack;
set {
if (keepOldMaxStack != value) {
keepOldMaxStack = value;
OnPropertyChanged(nameof(KeepOldMaxStack));
}
}
}
bool keepOldMaxStack;
public bool InitLocals {
get => initLocals;
set {
if (initLocals != value) {
initLocals = value;
OnPropertyChanged(nameof(InitLocals));
}
}
}
bool initLocals;
public UInt16VM MaxStack { get; }
public UInt32VM LocalVarSigTok { get; }
public ByteVM HeaderSize { get; }
public UInt32VM HeaderRVA { get; }
public UInt64VM HeaderFileOffset { get; }
public UInt32VM RVA { get; }
public UInt64VM FileOffset { get; }
sealed class LocalsIndexObservableCollection : IndexObservableCollection {
readonly CilBodyVM owner;
public LocalsIndexObservableCollection(CilBodyVM owner, Func createNewItem)
: base(createNewItem) => this.owner = owner;
protected override void ClearItems() {
var old_disable_UpdateLocalOperands = owner.disable_UpdateLocalOperands;
var old_disable_UpdateBranchOperands = owner.disable_UpdateBranchOperands;
try {
owner.disable_UpdateLocalOperands = true;
owner.disable_UpdateBranchOperands = true;
base.ClearItems();
}
finally {
owner.disable_UpdateLocalOperands = old_disable_UpdateLocalOperands;
owner.disable_UpdateBranchOperands = old_disable_UpdateBranchOperands;
}
owner.UpdateLocalOperands();
owner.UpdateBranchOperands();
}
}
public IndexObservableCollection LocalsListVM { get; }
public IndexObservableCollection InstructionsListVM { get; }
public IndexObservableCollection ExceptionHandlersListVM { get; }
internal ModuleDef OwnerModule { get; }
internal TypeSigCreatorOptions TypeSigCreatorOptions { get; }
readonly MethodDef ownerMethod;
public CilBodyVM(CilBodyOptions options, ModuleDef ownerModule, IDecompilerService decompilerService, TypeDef ownerType, MethodDef ownerMethod, bool initialize) {
OwnerModule = ownerModule;
this.ownerMethod = ownerMethod;
origOptions = options;
TypeSigCreatorOptions = new TypeSigCreatorOptions(ownerModule, decompilerService) {
CanAddGenericTypeVar = ownerType.HasGenericParameters,
CanAddGenericMethodVar = ownerMethod.MethodSig.GetGenParamCount() > 0,
OwnerType = ownerType,
OwnerMethod = ownerMethod,
};
LocalsListVM = new LocalsIndexObservableCollection(this, () => new LocalVM(TypeSigCreatorOptions, new LocalOptions(new Local(ownerModule.CorLibTypes.Int32))));
InstructionsListVM = new IndexObservableCollection(() => CreateInstructionVM());
ExceptionHandlersListVM = new IndexObservableCollection(() => new ExceptionHandlerVM(TypeSigCreatorOptions, new ExceptionHandlerOptions()));
LocalsListVM.UpdateIndexesDelegate = LocalsUpdateIndexes;
InstructionsListVM.UpdateIndexesDelegate = InstructionsUpdateIndexes;
ExceptionHandlersListVM.UpdateIndexesDelegate = ExceptionHandlersUpdateIndexes;
InstructionsListVM.CollectionChanged += InstructionsListVM_CollectionChanged;
LocalsListVM.CollectionChanged += LocalsListVM_CollectionChanged;
ExceptionHandlersListVM.CollectionChanged += ExceptionHandlersListVM_CollectionChanged;
MaxStack = new UInt16VM(a => CallHasErrorUpdated());
LocalVarSigTok = new UInt32VM(a => CallHasErrorUpdated());
HeaderSize = new ByteVM(a => CallHasErrorUpdated());
HeaderRVA = new UInt32VM(a => CallHasErrorUpdated());
HeaderFileOffset = new UInt64VM(a => CallHasErrorUpdated());
RVA = new UInt32VM(a => CallHasErrorUpdated());
FileOffset = new UInt64VM(a => CallHasErrorUpdated());
if (initialize)
Reinitialize();
}
public void Select(uint[] offsets) {
if (selectItems is null)
throw new InvalidOperationException();
if (offsets is null || offsets.Length == 0)
return;
var dict = InstructionsListVM.ToDictionary(a => a.Offset);
var instrs = offsets.Select(a => {
InstructionVM? instr;
dict.TryGetValue(a, out instr);
return instr;
}).OfType().Distinct().ToArray();
if (instrs.Length == 0)
return;
selectItems.Select(instrs);
}
void LocalsUpdateIndexes(int i) {
var old_disable_UpdateLocalOperands = disable_UpdateLocalOperands;
var old_disable_UpdateBranchOperands = disable_UpdateBranchOperands;
var old = DisableHasError();
try {
disable_UpdateLocalOperands = true;
disable_UpdateBranchOperands = true;
LocalsListVM.DefaultUpdateIndexes(i);
disable_UpdateLocalOperands = old_disable_UpdateLocalOperands;
disable_UpdateBranchOperands = old_disable_UpdateBranchOperands;
UpdateLocalOperands();
UpdateBranchOperands();
}
finally {
disable_UpdateLocalOperands = old_disable_UpdateLocalOperands;
disable_UpdateBranchOperands = old_disable_UpdateBranchOperands;
RestoreHasError(old);
}
}
void InstructionsUpdateIndexes(int i) {
var old_disable_UpdateLocalOperands = disable_UpdateLocalOperands;
var old_disable_UpdateBranchOperands = disable_UpdateBranchOperands;
var old = DisableHasError();
try {
disable_UpdateLocalOperands = true;
disable_UpdateBranchOperands = true;
InstructionsListVM.UpdateIndexesOffsets(i);
disable_UpdateLocalOperands = old_disable_UpdateLocalOperands;
disable_UpdateBranchOperands = old_disable_UpdateBranchOperands;
UpdateLocalOperands();
UpdateBranchOperands();
UpdateParameterOperands();
UpdateExceptionHandlerInstructionReferences();
}
finally {
disable_UpdateLocalOperands = old_disable_UpdateLocalOperands;
disable_UpdateBranchOperands = old_disable_UpdateBranchOperands;
RestoreHasError(old);
}
}
void ExceptionHandlersUpdateIndexes(int i) {
var old = DisableHasError();
try {
ExceptionHandlersListVM.DefaultUpdateIndexes(i);
}
finally {
RestoreHasError(old);
}
}
InstructionVM CreateInstructionVM(Code code = Code.Nop) => new InstructionVM() { Code = code };
void UpdateExceptionHandlerInstructionReferences() {
foreach (var eh in ExceptionHandlersListVM)
eh.InstructionChanged(InstructionsListVM);
}
void UpdateBranchOperands() {
if (disable_UpdateBranchOperands)
return;
foreach (var instr in InstructionsListVM) {
if (instr.InstructionOperandVM.InstructionOperandType == InstructionOperandType.BranchTarget)
instr.InstructionOperandVM.BranchOperandChanged(InstructionsListVM);
else if (instr.InstructionOperandVM.InstructionOperandType == InstructionOperandType.SwitchTargets)
instr.InstructionOperandVM.SwitchOperandChanged();
}
}
bool disable_UpdateBranchOperands = false;
void UpdateLocalOperands() {
if (disable_UpdateLocalOperands)
return;
foreach (var instr in InstructionsListVM) {
if (instr.InstructionOperandVM.InstructionOperandType == InstructionOperandType.Local)
instr.InstructionOperandVM.LocalOperandChanged(LocalsListVM);
}
}
bool disable_UpdateLocalOperands = false;
void UpdateParameterOperands() {
foreach (var instr in InstructionsListVM) {
if (instr.InstructionOperandVM.InstructionOperandType == InstructionOperandType.Parameter)
instr.InstructionOperandVM.ParameterOperandChanged(ownerMethod.Parameters);
}
}
void SimplifyAllInstructions() {
var old1 = InstructionsListVM.DisableAutoUpdateProps;
var old2 = DisableHasError();
try {
InstructionsListVM.DisableAutoUpdateProps = true;
// Absolutely required for speed. The UI list is virtualized so most of the
// instructions won't have a handler to notify, only the visible ones and a few
// nearby instructions will.
UninstallInstructionHandlers(InstructionsListVM);
InstructionsListVM.SimplifyMacros(LocalsListVM, ownerMethod.Parameters);
}
finally {
InstallInstructionHandlers(InstructionsListVM);
RestoreHasError(old2);
InstructionsListVM.DisableAutoUpdateProps = old1;
}
InstructionsUpdateIndexes(0);
}
bool SimplifyAllInstructionsCanExecute() => InstructionsListVM.Count > 0;
void OptimizeAllInstructions() {
var old1 = InstructionsListVM.DisableAutoUpdateProps;
var old2 = DisableHasError();
try {
InstructionsListVM.DisableAutoUpdateProps = true;
// Speed optimization, see comment in SimplifyAllInstructions()
UninstallInstructionHandlers(InstructionsListVM);
InstructionsListVM.OptimizeMacros();
}
finally {
InstallInstructionHandlers(InstructionsListVM);
RestoreHasError(old2);
InstructionsListVM.DisableAutoUpdateProps = old1;
}
InstructionsUpdateIndexes(0);
}
bool OptimizeAllInstructionsCanExecute() => InstructionsListVM.Count > 0;
void ReplaceInstructionWithNop(InstructionVM[] instrs) {
var old1 = InstructionsListVM.DisableAutoUpdateProps;
var old2 = DisableHasError();
try {
InstructionsListVM.DisableAutoUpdateProps = true;
// Speed optimization, see comment in SimplifyAllInstructions()
UninstallInstructionHandlers(instrs);
foreach (var instr in instrs)
instr.Code = Code.Nop;
}
finally {
InstallInstructionHandlers(instrs);
RestoreHasError(old2);
InstructionsListVM.DisableAutoUpdateProps = old1;
}
InstructionsUpdateIndexes(0);
}
bool ReplaceInstructionWithNopCanExecute(InstructionVM[] instrs) => instrs.Any(a => a.Code != Code.Nop);
void ReplaceInstructionWithMultipleNops(InstructionVM[] instrs) {
var old1 = InstructionsListVM.DisableAutoUpdateProps;
var old2 = DisableHasError();
try {
InstructionsListVM.DisableAutoUpdateProps = true;
// Speed optimization, see comment in SimplifyAllInstructions()
UninstallInstructionHandlers(instrs);
Array.Sort(instrs, (a, b) => b.Index - a.Index);
foreach (var instr in instrs) {
var size = instr.GetSize();
for (int i = 1; i < size; i++)
InstructionsListVM.Insert(instr.Index + i, CreateInstructionVM(Code.Nop));
instr.Code = Code.Nop;
}
}
finally {
InstallInstructionHandlers(instrs);
RestoreHasError(old2);
InstructionsListVM.DisableAutoUpdateProps = old1;
}
InstructionsUpdateIndexes(0);
}
bool ReplaceInstructionWithMultipleNopsCanExecute(InstructionVM[] instrs) => instrs.Any(a => a.Code != Code.Nop);
void InvertBranch(InstructionVM[] instrs) {
foreach (var instr in instrs) {
var code = InvertBcc(instr.Code);
if (code is not null)
instr.Code = code.Value;
}
}
bool InvertBranchCanExecute(InstructionVM[] instrs) => instrs.Any(a => InvertBcc(a.Code) is not null);
static Code? InvertBcc(Code code) {
switch (code) {
case Code.Beq: return Code.Bne_Un;
case Code.Beq_S: return Code.Bne_Un_S;
case Code.Bge: return Code.Blt;
case Code.Bge_S: return Code.Blt_S;
case Code.Bge_Un: return Code.Blt_Un;
case Code.Bge_Un_S: return Code.Blt_Un_S;
case Code.Bgt: return Code.Ble;
case Code.Bgt_S: return Code.Ble_S;
case Code.Bgt_Un: return Code.Ble_Un;
case Code.Bgt_Un_S: return Code.Ble_Un_S;
case Code.Ble: return Code.Bgt;
case Code.Ble_S: return Code.Bgt_S;
case Code.Ble_Un: return Code.Bgt_Un;
case Code.Ble_Un_S: return Code.Bgt_Un_S;
case Code.Blt: return Code.Bge;
case Code.Blt_S: return Code.Bge_S;
case Code.Blt_Un: return Code.Bge_Un;
case Code.Blt_Un_S: return Code.Bge_Un_S;
case Code.Bne_Un: return Code.Beq;
case Code.Bne_Un_S: return Code.Beq_S;
case Code.Brfalse: return Code.Brtrue;
case Code.Brfalse_S:return Code.Brtrue_S;
case Code.Brtrue: return Code.Brfalse;
case Code.Brtrue_S: return Code.Brfalse_S;
default: return null;
}
}
void ConvertBranchToUnconditionalBranch(InstructionVM[] instrs) {
foreach (var instr in instrs) {
var popCount = GetBccPopCount(instr.Code);
if (popCount <= 0)
continue;
var targetOperand = instr.InstructionOperandVM.Value;
instr.Code = Code.Pop;
int index = instr.Index + 1;
while (--popCount > 0)
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Pop));
var brInstr = CreateInstructionVM(Code.Br);
brInstr.InstructionOperandVM.OperandListItem = targetOperand ?? InstructionVM.Null;
InstructionsListVM.Insert(index++, brInstr);
}
}
static int GetBccPopCount(Code code) {
switch (code) {
case Code.Beq:
case Code.Beq_S:
case Code.Bge:
case Code.Bge_S:
case Code.Bge_Un:
case Code.Bge_Un_S:
case Code.Bgt:
case Code.Bgt_S:
case Code.Bgt_Un:
case Code.Bgt_Un_S:
case Code.Ble:
case Code.Ble_S:
case Code.Ble_Un:
case Code.Ble_Un_S:
case Code.Blt:
case Code.Blt_S:
case Code.Blt_Un:
case Code.Blt_Un_S:
case Code.Bne_Un:
case Code.Bne_Un_S:
return 2;
case Code.Brfalse:
case Code.Brfalse_S:
case Code.Brtrue:
case Code.Brtrue_S:
return 1;
default:
return 0;
}
}
bool ConvertBranchToUnconditionalBranchCanExecute(InstructionVM[] instrs) => instrs.Any(a => GetBccPopCount(a.Code) > 0);
void RemoveInstructionAndAddPops(InstructionVM[] instrs) {
foreach (var instr in instrs) {
var info = GetInstructionPops(instr);
if (info is null)
continue;
var popCount = info.Value.PopCount;
var origCode = instr.Code;
if (origCode == Code.Callvirt && instr.Index >= 1 && InstructionsListVM[instr.Index - 1].Code == Code.Constrained)
InstructionsListVM[instr.Index - 1].Code = Code.Nop;
int index = instr.Index + 1;
if (popCount == 0)
instr.Code = Code.Nop;
else {
instr.Code = Code.Pop;
while (--popCount > 0)
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Pop));
}
if (info.Value.Pushes)
AddPushDefaultValue(0, ref index, info.Value.PushType);
}
}
bool RemoveInstructionAndAddPopsCanExecute(InstructionVM[] instrs) => instrs.Any(a => GetInstructionPops(a) is not null);
readonly struct InstructionPushPopInfo {
public readonly int PopCount;
public readonly bool Pushes;// Needed in case there's an invalid method sig with a null type
public readonly TypeSig? PushType;
public InstructionPushPopInfo(int pops) {
PopCount = pops;
Pushes = false;
PushType = null;
}
public InstructionPushPopInfo(int pops, TypeSig? pushType) {
PopCount = pops;
Pushes = true;
PushType = pushType;
}
}
static InstructionPushPopInfo? GetInstructionPops(InstructionVM instr) {
var code = instr.Code;
if (code == Code.Pop || code == Code.Nop)
return null;
instr.CalculateStackUsage(out int pushes, out int pops);
if (pops < 0)
return null;
if (pushes == 1 && (code == Code.Call || code == Code.Callvirt || code == Code.Calli))
return new InstructionPushPopInfo(pops, GetMethodSig(instr.InstructionOperandVM.Other).GetRetType());
if (pushes == 1 && code == Code.Newobj) {
var ctor = instr.InstructionOperandVM.Other as IMethod;
return new InstructionPushPopInfo(pops, ctor is null ? null : ctor.DeclaringType.ToTypeSig());
}
if (pushes != 0)
return null;
return new InstructionPushPopInfo(pops);
}
void AddPushDefaultValue(int count, ref int index, TypeSig? pushType) {
pushType = pushType.RemovePinned();
switch (count > 10 ? ElementType.End : pushType.RemovePinnedAndModifiers().GetElementType()) {
case ElementType.Void:
break;
case ElementType.Boolean:
case ElementType.Char:
case ElementType.I1:
case ElementType.U1:
case ElementType.I2:
case ElementType.U2:
case ElementType.I4:
case ElementType.U4:
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Ldc_I4_0));
break;
case ElementType.I8:
case ElementType.U8:
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Ldc_I4_0));
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Conv_I8));
break;
case ElementType.R4:
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Ldc_R4));
break;
case ElementType.R8:
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Ldc_R8));
break;
case ElementType.I:
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Ldc_I4_0));
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Conv_I));
break;
case ElementType.U:
case ElementType.Ptr:
case ElementType.FnPtr:
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Ldc_I4_0));
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Conv_U));
break;
case ElementType.ValueType:
var td = ((ValueTypeSig)pushType!).TypeDefOrRef.ResolveTypeDef();
if (td is not null && td.IsEnum) {
var undType = td.GetEnumUnderlyingType().RemovePinnedAndModifiers();
var et = undType.GetElementType();
if ((ElementType.Boolean <= et && et <= ElementType.R8) || et == ElementType.I || et == ElementType.U) {
AddPushDefaultValue(count + 1, ref index, undType);
break;
}
}
goto case ElementType.TypedByRef;
case ElementType.TypedByRef:
case ElementType.Var:
case ElementType.MVar:
var local = new LocalVM(TypeSigCreatorOptions, new LocalOptions(new Local(pushType!)));
LocalsListVM.Add(local);
var newInstr = CreateInstructionVM(Code.Ldloca);
newInstr.InstructionOperandVM.OperandListItem = local;
InstructionsListVM.Insert(index++, newInstr);
newInstr = CreateInstructionVM(Code.Initobj);
newInstr.InstructionOperandVM.Other = local.Type.ToTypeDefOrRef();
InstructionsListVM.Insert(index++, newInstr);
newInstr = CreateInstructionVM(Code.Ldloc);
newInstr.InstructionOperandVM.OperandListItem = local;
InstructionsListVM.Insert(index++, newInstr);
break;
case ElementType.GenericInst:
if (((GenericInstSig)pushType!).GenericType is ValueTypeSig)
goto case ElementType.TypedByRef;
goto case ElementType.Class;
case ElementType.End:
case ElementType.String:
case ElementType.ByRef:
case ElementType.Class:
case ElementType.Array:
case ElementType.ValueArray:
case ElementType.R:
case ElementType.Object:
case ElementType.SZArray:
case ElementType.CModReqd:
case ElementType.CModOpt:
case ElementType.Internal:
case ElementType.Module:
case ElementType.Sentinel:
case ElementType.Pinned:
default:
InstructionsListVM.Insert(index++, CreateInstructionVM(Code.Ldnull));
break;
}
}
static MethodBaseSig? GetMethodSig(object? operand) {
if (operand is MethodSig msig)
return msig;
if (operand is MethodDef md)
return md.MethodSig;
if (operand is MemberRef mr) {
var type = mr.DeclaringType;
return GetMethodSig(type, mr.MethodSig, null);
}
if (operand is MethodSpec ms) {
var type = ms.DeclaringType;
var genMeth = ms.GenericInstMethodSig;
var meth = ms.Method;
return GetMethodSig(type, meth is null ? null : meth.MethodSig, genMeth is null ? null : genMeth.GenericArguments);
}
return null;
}
static MethodBaseSig? GetMethodSig(ITypeDefOrRef type, MethodSig? msig, IList? methodGenArgs) {
IList? typeGenArgs = null;
if (type is TypeSpec ts) {
var genSig = ts.TypeSig.ToGenericInstSig();
if (genSig is not null)
typeGenArgs = genSig.GenericArguments;
}
if (typeGenArgs is null && methodGenArgs is null)
return msig;
return GenericArgumentResolver.Resolve(msig, typeGenArgs, methodGenArgs);
}
void InstructionsListVM_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) {
if (e.NewItems is not null)
InstallInstructionHandlers(e.NewItems);
if (!InstructionsListVM.DisableAutoUpdateProps)
CallHasErrorUpdated();
}
void InstallInstructionHandlers(System.Collections.IList list) {
foreach (InstructionVM? instr in list) {
instr!.PropertyChanged -= instr_PropertyChanged;
instr.PropertyChanged += instr_PropertyChanged;
instr.InstructionOperandVM.PropertyChanged -= InstructionOperandVM_PropertyChanged;
instr.InstructionOperandVM.PropertyChanged += InstructionOperandVM_PropertyChanged;
}
}
void UninstallInstructionHandlers(IList list) {
foreach (var instr in list) {
instr.PropertyChanged -= instr_PropertyChanged;
instr.InstructionOperandVM.PropertyChanged -= InstructionOperandVM_PropertyChanged;
}
}
void instr_PropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(InstructionVM.HasError))
CallHasErrorUpdated();
else if (!InstructionsListVM.DisableAutoUpdateProps && e.PropertyName == nameof(InstructionVM.Code))
InstructionsUpdateIndexes(0);
}
void InstructionOperandVM_PropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (!disableInstrOpUpdate && e.PropertyName == "Modified") {
try {
disableInstrOpUpdate = true;
InstructionsUpdateIndexes(0);
}
finally {
disableInstrOpUpdate = false;
}
}
CallHasErrorUpdated();
}
bool disableInstrOpUpdate = false;
void LocalsListVM_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) {
if (e.NewItems is not null) {
foreach (LocalVM? local in e.NewItems) {
local!.PropertyChanged -= local_PropertyChanged;
local.PropertyChanged += local_PropertyChanged;
}
}
if (!LocalsListVM.DisableAutoUpdateProps)
CallHasErrorUpdated();
}
void local_PropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(LocalVM.HasError))
CallHasErrorUpdated();
if (!LocalsListVM.DisableAutoUpdateProps && (e.PropertyName == nameof(LocalVM.Index) || e.PropertyName == nameof(LocalVM.Name))) {
UpdateLocalOperands();
UpdateBranchOperands(); // branches whose target instruction is a ldloc/stloc instruction
}
}
void ExceptionHandlersListVM_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) {
if (e.NewItems is not null) {
foreach (ExceptionHandlerVM? eh in e.NewItems) {
eh!.PropertyChanged -= eh_PropertyChanged;
eh.PropertyChanged += eh_PropertyChanged;
}
}
if (!ExceptionHandlersListVM.DisableAutoUpdateProps) {
if (e.Action == NotifyCollectionChangedAction.Add) {
if (e.NewItems is not null) {
foreach (ExceptionHandlerVM? eh in e.NewItems)
eh!.InstructionChanged(InstructionsListVM);
}
}
CallHasErrorUpdated();
}
}
void eh_PropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(ExceptionHandlerVM.HasError))
CallHasErrorUpdated();
}
void Reinitialize() => InitializeFrom(origOptions);
public CilBodyOptions CreateCilBodyOptions() => CopyTo(new CilBodyOptions());
public void InitializeFrom(CilBodyOptions options) {
try {
LocalsListVM.DisableAutoUpdateProps = true;
InstructionsListVM.DisableAutoUpdateProps = true;
ExceptionHandlersListVM.DisableAutoUpdateProps = true;
HasError_disabled = true;
var ops = new Dictionary