406 lines
14 KiB
C#
Raw Normal View History

2021-09-20 18:20:01 +02:00
/*
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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using dnlib.DotNet;
using dnSpy.AsmEditor.Properties;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.MVVM;
namespace dnSpy.AsmEditor.DnlibDialogs {
sealed class CAArgumentVM : ViewModelBase {
public ConstantTypeVM ConstantTypeVM { get; }
public bool IsEnabled {
get => isEnabled;
set {
if (isEnabled != value) {
isEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
HasErrorUpdated();
}
}
}
bool isEnabled = true;
static readonly ConstantType[] ConstantTypes = new ConstantType[] {
ConstantType.Null,
ConstantType.Boolean,
ConstantType.Char,
ConstantType.SByte,
ConstantType.Byte,
ConstantType.Int16,
ConstantType.UInt16,
ConstantType.Int32,
ConstantType.UInt32,
ConstantType.Int64,
ConstantType.UInt64,
ConstantType.Single,
ConstantType.Double,
ConstantType.String,
ConstantType.Enum,
ConstantType.Type,
ConstantType.ObjectArray,
ConstantType.BooleanArray,
ConstantType.CharArray,
ConstantType.SByteArray,
ConstantType.ByteArray,
ConstantType.Int16Array,
ConstantType.UInt16Array,
ConstantType.Int32Array,
ConstantType.UInt32Array,
ConstantType.Int64Array,
ConstantType.UInt64Array,
ConstantType.SingleArray,
ConstantType.DoubleArray,
ConstantType.StringArray,
ConstantType.EnumArray,
ConstantType.TypeArray,
};
bool modified;
readonly CAArgument originalArg;
readonly ModuleDef module;
public TypeSig? StorageType {
get => storageType;
set {
if (storageType != value) {
storageType = value;
OnPropertyChanged(nameof(StorageType));
}
}
}
TypeSig? storageType;
public CAArgumentVM(ModuleDef ownerModule, CAArgument arg, TypeSigCreatorOptions options, TypeSig? storageType) {
module = options.OwnerModule;
originalArg = arg.Clone();
ConstantTypeVM = new DnlibDialogs.ConstantTypeVM(ownerModule, null, ConstantTypes, true, true, options);
ConstantTypeVM.PropertyChanged += ConstantTypeVM_PropertyChanged;
InitializeFrom(arg, storageType);
modified = false;
}
void ConstantTypeVM_PropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "Modified") {
OnPropertyChanged("Modified");
modified = true;
}
HasErrorUpdated();
}
void InitializeFrom(CAArgument arg, TypeSig? storageType) {
StorageType = storageType;
ConstantTypeVM.Value = ConvertFromModel(arg.Type, arg.Value);
}
object? ConvertFromModel(TypeSig valueType, object? value) {
var type = valueType.RemovePinnedAndModifiers();
var et = type.GetElementType();
ITypeDefOrRef tdr;
TypeDef td;
switch (et) {
case ElementType.Boolean:
case ElementType.Char:
case ElementType.I1:
case ElementType.U1:
case ElementType.I2:
case ElementType.U2:
case ElementType.I4:
case ElementType.U4:
case ElementType.I8:
case ElementType.U8:
case ElementType.R4:
case ElementType.R8:
if (ModelUtils.GetElementType(value?.GetType()) == et)
return value;
break;
case ElementType.String:
if (value is null)
return Null<string>.Instance;
else if (value is string)
return value;
else if (value is UTF8String)
return ((UTF8String)value).String;
break;
case ElementType.ValueType:
case ElementType.Class:
tdr = ((ClassOrValueTypeSig)type).TypeDefOrRef;
if (tdr.IsSystemType()) {
if (value is null)
return Null<TypeSig>.Instance;
return value;
}
td = tdr.ResolveTypeDef();
if (td is not null && !td.IsEnum)
break;
return new EnumInfo() {
EnumType = tdr,
Value = value,
IsArray = false,
};
case ElementType.SZArray:
var elemType = type.Next.RemovePinnedAndModifiers();
if (value is null) {
switch (elemType.GetElementType()) {
case ElementType.Boolean: return Null<bool[]>.Instance;
case ElementType.Char: return Null<char[]>.Instance;
case ElementType.I1: return Null<sbyte[]>.Instance;
case ElementType.U1: return Null<byte[]>.Instance;
case ElementType.I2: return Null<short[]>.Instance;
case ElementType.U2: return Null<ushort[]>.Instance;
case ElementType.I4: return Null<int[]>.Instance;
case ElementType.U4: return Null<uint[]>.Instance;
case ElementType.I8: return Null<long[]>.Instance;
case ElementType.U8: return Null<ulong[]>.Instance;
case ElementType.R4: return Null<float[]>.Instance;
case ElementType.R8: return Null<double[]>.Instance;
case ElementType.String: return Null<string[]>.Instance;
case ElementType.Object: return Null<object[]>.Instance;
case ElementType.ValueType:
case ElementType.Class:
tdr = ((ClassOrValueTypeSig)elemType).TypeDefOrRef;
if (tdr.IsSystemType())
return Null<Type[]>.Instance;
td = tdr.ResolveTypeDef();
if (td is not null && !td.IsEnum)
break;
return EnumInfo.CreateNullArray(tdr);
}
break;
}
var oldList = value as IList<CAArgument>;
if (oldList is null)
break;
switch (elemType.GetElementType()) {
case ElementType.Boolean: return ConvertArray<bool>(elemType, oldList);
case ElementType.Char: return ConvertArray<char>(elemType, oldList);
case ElementType.I1: return ConvertArray<sbyte>(elemType, oldList);
case ElementType.U1: return ConvertArray<byte>(elemType, oldList);
case ElementType.I2: return ConvertArray<short>(elemType, oldList);
case ElementType.U2: return ConvertArray<ushort>(elemType, oldList);
case ElementType.I4: return ConvertArray<int>(elemType, oldList);
case ElementType.U4: return ConvertArray<uint>(elemType, oldList);
case ElementType.I8: return ConvertArray<long>(elemType, oldList);
case ElementType.U8: return ConvertArray<ulong>(elemType, oldList);
case ElementType.R4: return ConvertArray<float>(elemType, oldList);
case ElementType.R8: return ConvertArray<double>(elemType, oldList);
case ElementType.String: return ConvertArray<string>(elemType, oldList);
case ElementType.Object: return ConvertArray<object>(elemType, oldList);
case ElementType.ValueType:
case ElementType.Class:
tdr = ((ClassOrValueTypeSig)elemType).TypeDefOrRef;
if (tdr.IsSystemType())
return ConvertArray<TypeSig>(elemType, oldList);
td = tdr.ResolveTypeDef();
if (td is not null && !td.IsEnum)
break;
return ConvertEnum(elemType, oldList);
}
break;
default:
break;
}
return value;
}
object ConvertEnum(TypeSig elemType, IList<CAArgument> oldList) {
var td = elemType.GetScopeTypeDefOrRef().ResolveTypeDef();
ElementType underlyingElemType = ElementType.End;
if (td is not null && td.IsEnum)
underlyingElemType = td.GetEnumUnderlyingType().RemovePinnedAndModifiers().GetElementType();
if (!(ElementType.Boolean <= underlyingElemType && underlyingElemType <= ElementType.R8)) {
if (oldList.Count > 0 && oldList[0].Value is not null)
underlyingElemType = ModelUtils.GetElementType(oldList[0].Value.GetType());
}
switch (underlyingElemType) {
case ElementType.Boolean: return ConvertEnum<bool>(elemType, oldList);
case ElementType.Char: return ConvertEnum<char>(elemType, oldList);
case ElementType.I1: return ConvertEnum<sbyte>(elemType, oldList);
case ElementType.U1: return ConvertEnum<byte>(elemType, oldList);
case ElementType.I2: return ConvertEnum<short>(elemType, oldList);
case ElementType.U2: return ConvertEnum<ushort>(elemType, oldList);
case ElementType.I4: return ConvertEnum<int>(elemType, oldList);
case ElementType.U4: return ConvertEnum<uint>(elemType, oldList);
case ElementType.I8: return ConvertEnum<long>(elemType, oldList);
case ElementType.U8: return ConvertEnum<ulong>(elemType, oldList);
case ElementType.R4: return ConvertEnum<float>(elemType, oldList);
case ElementType.R8: return ConvertEnum<double>(elemType, oldList);
}
return Array.Empty<int>();
}
object ConvertEnum<T>(TypeSig elemType, IList<CAArgument> oldList) {
var ary = ConvertArray<EnumInfo>(elemType, oldList);
var list = new T[ary.Length];
var sigComparer = new SigComparer(SigComparerOptions.CompareAssemblyPublicKeyToken);
for (int i = 0; i < list.Length; i++) {
if (ary[i].Value is T && sigComparer.Equals(elemType, ary[i].EnumType))
list[i] = (T)ary[i].Value!;
}
return new EnumInfo {
EnumType = elemType.ToTypeDefOrRef(),
Value = list,
IsArray = true,
};
}
T[] ConvertArray<T>(TypeSig elemType, IList<CAArgument> oldList) {
var list = new T[oldList.Count];
bool tIsValueType = typeof(T).IsValueType;
bool tIsSystemObject = typeof(T) == typeof(object);
var sigComparer = new SigComparer(SigComparerOptions.CompareAssemblyPublicKeyToken);
for (int i = 0; i < list.Length; i++) {
var arg = oldList[i];
if (!tIsSystemObject && !sigComparer.Equals(elemType, arg.Type))
return Array.Empty<T>();
var res = ConvertFromModel(arg.Type, arg.Value);
if (res is T)
list[i] = (T)res;
else if (!tIsValueType && (res is null || res == Null<T>.Instance)) {
object? n = null;
list[i] = (T)n!;
}
else
return Array.Empty<T>();
}
return list;
}
public CAArgument CreateCAArgument(TypeSig ownerType) {
if (!modified)
return originalArg.Clone();
return CreateCAArgument(ownerType, ConstantTypeVM.Value);
}
CAArgument CreateCAArgument(TypeSig ownerType, object? value) {
if (value is null || value is Null) {
var t = ownerType.RemovePinnedAndModifiers();
t = t is SZArraySig ? t.Next : t;
if (t.RemovePinnedAndModifiers().GetElementType() == ElementType.Object)
return new CAArgument(module.CorLibTypes.String, null);
return new CAArgument(ownerType, null);
}
switch (ModelUtils.GetElementType(value.GetType())) {
case ElementType.Boolean:return new CAArgument(module.CorLibTypes.Boolean, value);
case ElementType.Char: return new CAArgument(module.CorLibTypes.Char, value);
case ElementType.I1: return new CAArgument(module.CorLibTypes.SByte, value);
case ElementType.U1: return new CAArgument(module.CorLibTypes.Byte, value);
case ElementType.I2: return new CAArgument(module.CorLibTypes.Int16, value);
case ElementType.U2: return new CAArgument(module.CorLibTypes.UInt16, value);
case ElementType.I4: return new CAArgument(module.CorLibTypes.Int32, value);
case ElementType.U4: return new CAArgument(module.CorLibTypes.UInt32, value);
case ElementType.I8: return new CAArgument(module.CorLibTypes.Int64, value);
case ElementType.U8: return new CAArgument(module.CorLibTypes.UInt64, value);
case ElementType.R4: return new CAArgument(module.CorLibTypes.Single, value);
case ElementType.R8: return new CAArgument(module.CorLibTypes.Double, value);
case ElementType.String:return new CAArgument(module.CorLibTypes.String, new UTF8String((string)value));
}
if (value is TypeSig)
return new CAArgument(new ClassSig(module.CorLibTypes.GetTypeRef("System", "Type")), value);
if (value is EnumInfo enumInfo) {
var enumSig = enumInfo.EnumType.ToTypeSig();
if (!enumInfo.IsArray)
return new CAArgument(enumSig, enumInfo.Value);
var res = CreateArray(enumSig, enumInfo.Value);
var list = (IList<CAArgument>?)res.Value;
if (list is not null) {
for (int i = 0; i < list.Count; i++)
list[i] = new CAArgument(enumSig, list[i].Value);
}
return res;
}
var valueType = value.GetType();
if (value is IList<bool>)
return CreateArray(module.CorLibTypes.Boolean, value);
if (value is IList<char>)
return CreateArray(module.CorLibTypes.Char, value);
if (value is IList<sbyte> && valueType != typeof(byte[]))
return CreateArray(module.CorLibTypes.SByte, value);
if (value is IList<short> && valueType != typeof(ushort[]))
return CreateArray(module.CorLibTypes.Int16, value);
if (value is IList<int> && valueType != typeof(uint[]))
return CreateArray(module.CorLibTypes.Int32, value);
if (value is IList<long> && valueType != typeof(ulong[]))
return CreateArray(module.CorLibTypes.Int64, value);
if (value is IList<byte> && valueType != typeof(sbyte[]))
return CreateArray(module.CorLibTypes.Byte, value);
if (value is IList<ushort> && valueType != typeof(short[]))
return CreateArray(module.CorLibTypes.UInt16, value);
if (value is IList<uint> && valueType != typeof(int[]))
return CreateArray(module.CorLibTypes.UInt32, value);
if (value is IList<ulong> && valueType != typeof(long[]))
return CreateArray(module.CorLibTypes.UInt64, value);
if (value is IList<float>)
return CreateArray(module.CorLibTypes.Single, value);
if (value is IList<double>)
return CreateArray(module.CorLibTypes.Double, value);
if (value is IList<string>)
return CreateArray(module.CorLibTypes.String, value);
if (value is IList<TypeSig>)
return CreateArray(new ClassSig(module.CorLibTypes.GetTypeRef("System", "Type")), value);
if (value is IList<object>)
return CreateArray(module.CorLibTypes.Object, value);
Debug.Fail($"Unknown CA arg: {value}, ownerType: {ownerType}");
return new CAArgument();
}
CAArgument CreateArray(TypeSig elemType, object? value) {
var aryType = new SZArraySig(elemType);
var list = value as System.Collections.IList;
Debug2.Assert(list is not null || value is null);
if (list is null)
return new CAArgument(aryType, null);
var ary = new List<CAArgument>(list.Count);
for (int i = 0; i < list.Count; i++)
ary.Add(CreateCAArgument(elemType, list[i]));
return new CAArgument(aryType, ary);
}
public override string ToString() {
if (ConstantTypeVM.HasError)
return dnSpy_AsmEditor_Resources.Error;
return DlgUtils.ValueToString(ConstantTypeVM.Value, StorageType);
}
public override bool HasError => IsEnabled && ConstantTypeVM.HasError;
}
}