/*
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.Diagnostics;
using System.Windows.Input;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnSpy.AsmEditor.Properties;
using dnSpy.Contracts.MVVM;
namespace dnSpy.AsmEditor.MethodBody {
enum InstructionOperandType {
None,
SByte,
Byte,
Int32,
Int64,
Single,
Double,
String,
Field,
Method,
Token,
Type,
MethodSig,
BranchTarget,
SwitchTargets,
Local,
Parameter,
}
sealed class InstructionOperandVM : ViewModelBase {
public IEditOperand EditOperand {
set => editOperand = value;
}
IEditOperand? editOperand;
public ICommand EditOtherCommand => new RelayCommand(a => EditOther(a), a => EditOtherCanExecute(a));
public InstructionOperandType InstructionOperandType {
get => operandType;
set {
if (operandType != value) {
operandType = value;
OnPropertyChanged(nameof(InstructionOperandType));
OnPropertyChanged(nameof(Text));
OnPropertyChanged(nameof(Other));
OnPropertyChanged(nameof(StringIsSelected));
FieldUpdated();
}
}
}
InstructionOperandType operandType;
public object? Value {
get {
switch (InstructionOperandType) {
case MethodBody.InstructionOperandType.None: return null;
case MethodBody.InstructionOperandType.SByte: return SByte.Value;
case MethodBody.InstructionOperandType.Byte: return Byte.Value;
case MethodBody.InstructionOperandType.Int32: return Int32.Value;
case MethodBody.InstructionOperandType.Int64: return Int64.Value;
case MethodBody.InstructionOperandType.Single: return Single.Value;
case MethodBody.InstructionOperandType.Double: return Double.Value;
case MethodBody.InstructionOperandType.String: return String.Value;
case MethodBody.InstructionOperandType.BranchTarget:
case MethodBody.InstructionOperandType.Local:
case MethodBody.InstructionOperandType.Parameter:
if (BodyUtils.IsNull(OperandListItem))
return null;
return OperandListItem;
case MethodBody.InstructionOperandType.Field:
case MethodBody.InstructionOperandType.Method:
case MethodBody.InstructionOperandType.Token:
case MethodBody.InstructionOperandType.Type:
case MethodBody.InstructionOperandType.MethodSig:
case MethodBody.InstructionOperandType.SwitchTargets:
return Other;
default: throw new InvalidOperationException();
}
}
}
public void SwitchOperandChanged() => OnPropertyChanged(nameof(Other));
public void BranchOperandChanged(IEnumerable instrs) {
if (OperandListVM.Items.Count > 0 && !(OperandListVM.SelectedItem is InstructionVM))
OperandListVM.SelectedItem = InstructionVM.Null;
OperandListVM.InvalidateSelected(instrs, false, InstructionVM.Null);
if (OperandListVM.Items.Count > 0 && OperandListVM.SelectedItem == InstructionVM.Null)
OperandListVM.SelectedIndex = 0;
}
public void LocalOperandChanged(IEnumerable locals) {
if (OperandListVM.Items.Count > 0 && !(OperandListVM.SelectedItem is LocalVM))
OperandListVM.SelectedItem = LocalVM.Null;
OperandListVM.InvalidateSelected(locals, false, LocalVM.Null);
if (OperandListVM.Items.Count > 0 && OperandListVM.SelectedItem == LocalVM.Null)
OperandListVM.SelectedIndex = 0;
}
public void ParameterOperandChanged(IEnumerable parameters) {
if (OperandListVM.Items.Count > 0 && !(OperandListVM.SelectedItem is Parameter))
OperandListVM.SelectedItem = BodyUtils.NullParameter;
OperandListVM.InvalidateSelected(parameters, false, BodyUtils.NullParameter);
if (OperandListVM.Items.Count > 0 && OperandListVM.SelectedItem == BodyUtils.NullParameter)
OperandListVM.SelectedIndex = 0;
}
public void UpdateOperandType(Code code) => InstructionOperandType = GetOperandType(code);
public void WriteValue(Code code, object? value) {
UpdateOperandType(code);
switch (InstructionOperandType) {
case MethodBody.InstructionOperandType.None: break;
case MethodBody.InstructionOperandType.SByte: SByte.Value = (sbyte)value!; break;
case MethodBody.InstructionOperandType.Byte: Byte.Value = (byte)value!; break;
case MethodBody.InstructionOperandType.Int32: Int32.Value = (int)value!; break;
case MethodBody.InstructionOperandType.Int64: Int64.Value = (long)value!; break;
case MethodBody.InstructionOperandType.Single: Single.Value = (float)value!; break;
case MethodBody.InstructionOperandType.Double: Double.Value = (double)value!; break;
case MethodBody.InstructionOperandType.String: String.Value = (string)value!; break;
case MethodBody.InstructionOperandType.Field: Other = (IField?)value; break;
case MethodBody.InstructionOperandType.Method: Other = (IMethod?)value; break;
case MethodBody.InstructionOperandType.Token: Other = (ITokenOperand?)value; break;
case MethodBody.InstructionOperandType.Type: Other = (ITypeDefOrRef?)value; break;
case MethodBody.InstructionOperandType.MethodSig: Other = (MethodSig?)value; break;
case MethodBody.InstructionOperandType.BranchTarget: OperandListItem = (InstructionVM?)value ?? InstructionVM.Null; break;
case MethodBody.InstructionOperandType.SwitchTargets: Other = (InstructionVM[]?)value; break;
case MethodBody.InstructionOperandType.Local: OperandListItem = (LocalVM?)value ?? LocalVM.Null; break;
case MethodBody.InstructionOperandType.Parameter: OperandListItem = (Parameter?)value ?? BodyUtils.NullParameter; break;
default: throw new InvalidOperationException();
}
}
static MethodBody.InstructionOperandType GetOperandType(Code code) {
switch (code.ToOpCode().OperandType) {
case OperandType.InlineBrTarget: return MethodBody.InstructionOperandType.BranchTarget;
case OperandType.InlineField: return MethodBody.InstructionOperandType.Field;
case OperandType.InlineI: return MethodBody.InstructionOperandType.Int32;
case OperandType.InlineI8: return MethodBody.InstructionOperandType.Int64;
case OperandType.InlineMethod: return MethodBody.InstructionOperandType.Method;
case OperandType.InlineNone: return MethodBody.InstructionOperandType.None;
case OperandType.InlinePhi: return MethodBody.InstructionOperandType.None;
case OperandType.InlineR: return MethodBody.InstructionOperandType.Double;
case OperandType.InlineSig: return MethodBody.InstructionOperandType.MethodSig;
case OperandType.InlineString: return MethodBody.InstructionOperandType.String;
case OperandType.InlineSwitch: return MethodBody.InstructionOperandType.SwitchTargets;
case OperandType.InlineTok: return MethodBody.InstructionOperandType.Token;
case OperandType.InlineType: return MethodBody.InstructionOperandType.Type;
case OperandType.ShortInlineBrTarget: return MethodBody.InstructionOperandType.BranchTarget;
case OperandType.ShortInlineR: return MethodBody.InstructionOperandType.Single;
default: return MethodBody.InstructionOperandType.None;
case OperandType.ShortInlineI:
if (code == Code.Ldc_I4_S)
return MethodBody.InstructionOperandType.SByte;
return MethodBody.InstructionOperandType.Byte;
case OperandType.InlineVar:
case OperandType.ShortInlineVar:
switch (code) {
case Code.Ldloc:
case Code.Ldloc_S:
case Code.Ldloca:
case Code.Ldloca_S:
case Code.Stloc:
case Code.Stloc_S:
return MethodBody.InstructionOperandType.Local;
case Code.Ldarg:
case Code.Ldarg_S:
case Code.Ldarga:
case Code.Ldarga_S:
case Code.Starg:
case Code.Starg_S:
return MethodBody.InstructionOperandType.Parameter;
default:
throw new InvalidOperationException();
}
}
}
public DataFieldVM? Text {
get {
switch (InstructionOperandType) {
case MethodBody.InstructionOperandType.SByte: return SByte;
case MethodBody.InstructionOperandType.Byte: return Byte;
case MethodBody.InstructionOperandType.Int32: return Int32;
case MethodBody.InstructionOperandType.Int64: return Int64;
case MethodBody.InstructionOperandType.Single: return Single;
case MethodBody.InstructionOperandType.Double: return Double;
case MethodBody.InstructionOperandType.String: return String;
case MethodBody.InstructionOperandType.None:
case MethodBody.InstructionOperandType.Field:
case MethodBody.InstructionOperandType.Method:
case MethodBody.InstructionOperandType.Token:
case MethodBody.InstructionOperandType.Type:
case MethodBody.InstructionOperandType.MethodSig:
case MethodBody.InstructionOperandType.BranchTarget:
case MethodBody.InstructionOperandType.SwitchTargets:
case MethodBody.InstructionOperandType.Local:
case MethodBody.InstructionOperandType.Parameter:
return null;
default: throw new InvalidOperationException();
}
}
}
public bool StringIsSelected => InstructionOperandType == MethodBody.InstructionOperandType.String;
public SByteVM SByte { get; }
public ByteVM Byte { get; }
public Int32VM Int32 { get; }
public Int64VM Int64 { get; }
public SingleVM Single { get; }
public DoubleVM Double { get; }
public StringVM String { get; }
public object? Other {
get => other;
set {
if (other != value) {
other = value;
OnPropertyChanged(nameof(Other));
FieldUpdated();
}
}
}
object? other;
public object OperandListItem {
get => OperandListVM.SelectedItem;
set => OperandListVM.SelectedItem = value;
}
public ListVM