diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index b0a7b00..5223e3b 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -27,6 +27,18 @@ jobs:
with:
dotnet-version: 3.1.x
+ - name: Install .NET 5
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 5.0.x
+
+ - name: Add MSBuild to PATH
+ uses: glennawatson/setup-msbuild@v1.0.3
+
+ - name: Update VS2019
+ shell: powershell
+ run: Start-Process -Wait -PassThru -FilePath "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vs_installer.exe" -ArgumentList "update --passive --norestart --installpath ""C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise"""
+
- name: NBGV
id: nbgv
uses: dotnet/nbgv@master
@@ -37,9 +49,6 @@ jobs:
run: dotnet restore
working-directory: src
- - name: Add MSBuild to PATH
- uses: microsoft/setup-msbuild@v1
-
- name: Build
run: msbuild /t:build,pack /maxcpucount /p:NoPackageAnalysis=true /verbosity:minimal /p:Configuration=${{ env.configuration }}
working-directory: src
diff --git a/src/Directory.build.props b/src/Directory.build.props
index 1d87465..164bb6c 100644
--- a/src/Directory.build.props
+++ b/src/Directory.build.props
@@ -13,7 +13,7 @@
https://github.com/reactiveui/Pharmacisthttps://github.com/reactiveui/styleguide/blob/master/logo_pharmacist/main.png?raw=trueProduces from NuGet packages or reference assemblies System.Reactive Observables for all events within the specified target.
- mvvm;reactiveui;rx;reactive extensions;observable;events;frp;xamarin;android;ios;mac;forms;monodroid;monotouch;xamarin.android;xamarin.ios;xamarin.forms;xamarin.mac;xamarin.tvos;wpf;net;netstandard;net461;uwp;tizen;generator
+ mvvm;reactiveui;rx;reactive extensions;observable;events;frp;xamarin;android;ios;mac;forms;monodroid;monotouch;xamarin.android;xamarin.ios;xamarin.forms;xamarin.mac;xamarin.tvos;wpf;net;netstandard;net472;uwp;tizen;generatorhttps://github.com/reactiveui/Pharmacist/releaseshttps://github.com/reactiveui/Pharmacistgit
@@ -31,7 +31,6 @@
Full
-
@@ -49,7 +48,7 @@
-
+
diff --git a/src/ICSharpCode.Decompiler/DecompilerException.cs b/src/ICSharpCode.Decompiler/DecompilerException.cs
new file mode 100644
index 0000000..a121751
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/DecompilerException.cs
@@ -0,0 +1,206 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Reflection.Metadata.Ecma335;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Security;
+using System.Text;
+
+using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.Decompiler.TypeSystem;
+
+namespace ICSharpCode.Decompiler
+{
+ ///
+ /// Description of DecompilerException.
+ ///
+ public class DecompilerException : Exception, ISerializable
+ {
+ public string AssemblyName => File.Name;
+
+ public string FileName => File.FileName;
+
+ public IEntity DecompiledEntity { get; }
+ public IModule Module { get; }
+ public PEFile File { get; }
+
+ public DecompilerException(MetadataModule module, IEntity decompiledEntity,
+ Exception innerException, string message = null)
+ : base(message ?? GetDefaultMessage(decompiledEntity), innerException)
+ {
+ this.File = module.PEFile;
+ this.Module = module;
+ this.DecompiledEntity = decompiledEntity;
+ }
+
+ public DecompilerException(PEFile file, string message, Exception innerException)
+ : base(message, innerException)
+ {
+ this.File = file;
+ }
+
+ static string GetDefaultMessage(IEntity entity)
+ {
+ if (entity == null)
+ return "Error decompiling";
+ return $"Error decompiling @{MetadataTokens.GetToken(entity.MetadataToken):X8} {entity.FullName}";
+ }
+
+ // This constructor is needed for serialization.
+ protected DecompilerException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public override string StackTrace => GetStackTrace(this);
+
+ public override string ToString() => ToString(this);
+
+ string ToString(Exception exception)
+ {
+ if (exception == null)
+ throw new ArgumentNullException(nameof(exception));
+ var exceptionType = GetTypeName(exception);
+ var stacktrace = GetStackTrace(exception);
+ while (exception.InnerException != null)
+ {
+ exception = exception.InnerException;
+
+ stacktrace = GetStackTrace(exception) + Environment.NewLine
+ + "-- continuing with outer exception (" + exceptionType + ") --" + Environment.NewLine
+ + stacktrace;
+ exceptionType = GetTypeName(exception);
+ }
+ return this.Message + Environment.NewLine
+ + $"in assembly \"{this.FileName}\"" + Environment.NewLine
+ + " ---> " + exceptionType + ": " + exception.Message + Environment.NewLine
+ + stacktrace;
+ }
+
+ static string GetTypeName(Exception exception)
+ {
+ var type = exception.GetType().FullName;
+ if (exception is ExternalException || exception is IOException)
+ return type + " (" + Marshal.GetHRForException(exception).ToString("x8") + ")";
+ else
+ return type;
+ }
+
+ static string GetStackTrace(Exception exception)
+ {
+ // Output stacktrace in custom format (very similar to Exception.StackTrace
+ // property on English systems).
+ // Include filenames where available, but no paths.
+ var stackTrace = new StackTrace(exception, true);
+ var b = new StringBuilder();
+ for (var i = 0; i < stackTrace.FrameCount; i++)
+ {
+ var frame = stackTrace.GetFrame(i);
+ var method = frame.GetMethod();
+ if (method == null)
+ continue;
+
+ if (b.Length > 0)
+ b.AppendLine();
+
+ b.Append(" at ");
+ var declaringType = method.DeclaringType;
+ if (declaringType != null)
+ {
+ b.Append(declaringType.FullName.Replace('+', '.'));
+ b.Append('.');
+ }
+ b.Append(method.Name);
+ // output type parameters, if any
+ if ((method is MethodInfo) && ((MethodInfo)method).IsGenericMethod)
+ {
+ var genericArguments = ((MethodInfo)method).GetGenericArguments();
+ b.Append('[');
+ for (var j = 0; j < genericArguments.Length; j++)
+ {
+ if (j > 0)
+ b.Append(',');
+ b.Append(genericArguments[j].Name);
+ }
+ b.Append(']');
+ }
+
+ // output parameters, if any
+ b.Append('(');
+ var parameters = method.GetParameters();
+ for (var j = 0; j < parameters.Length; j++)
+ {
+ if (j > 0)
+ b.Append(", ");
+ if (parameters[j].ParameterType != null)
+ {
+ b.Append(parameters[j].ParameterType.Name);
+ }
+ else
+ {
+ b.Append('?');
+ }
+ if (!string.IsNullOrEmpty(parameters[j].Name))
+ {
+ b.Append(' ');
+ b.Append(parameters[j].Name);
+ }
+ }
+ b.Append(')');
+
+ // source location
+ if (frame.GetILOffset() >= 0)
+ {
+ string filename = null;
+ try
+ {
+ var fullpath = frame.GetFileName();
+ if (fullpath != null)
+ filename = Path.GetFileName(fullpath);
+ }
+ catch (SecurityException)
+ {
+ // StackFrame.GetFileName requires PathDiscovery permission
+ }
+ catch (ArgumentException)
+ {
+ // Path.GetFileName might throw on paths with invalid chars
+ }
+ b.Append(" in ");
+ if (filename != null)
+ {
+ b.Append(filename);
+ b.Append(":line ");
+ b.Append(frame.GetFileLineNumber());
+ }
+ else
+ {
+ b.Append("offset ");
+ b.Append(frame.GetILOffset());
+ }
+ }
+ }
+
+ return b.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ICSharpCode.Decompiler/DecompilerNuGetPackageIcon.png b/src/ICSharpCode.Decompiler/DecompilerNuGetPackageIcon.png
new file mode 100644
index 0000000..cf73b30
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/DecompilerNuGetPackageIcon.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9a405719968acc92a330412b4c1c8ebc8d2c30996c4027c6ba9035db2424bee6
+size 2454
diff --git a/src/ICSharpCode.Decompiler/DisassemblerHelpers.cs b/src/ICSharpCode.Decompiler/DisassemblerHelpers.cs
new file mode 100644
index 0000000..cebfab2
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/DisassemblerHelpers.cs
@@ -0,0 +1,344 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Linq;
+using System.Reflection.Metadata;
+using System.Text;
+
+using ICSharpCode.Decompiler.Metadata;
+
+using SRM = System.Reflection.Metadata;
+
+namespace ICSharpCode.Decompiler.Disassembler
+{
+ public enum ILNameSyntax
+ {
+ ///
+ /// class/valuetype + TypeName (built-in types use keyword syntax)
+ ///
+ Signature,
+ ///
+ /// Like signature, but always refers to type parameters using their position
+ ///
+ SignatureNoNamedTypeParameters,
+ ///
+ /// [assembly]Full.Type.Name (even for built-in types)
+ ///
+ TypeName,
+ ///
+ /// Name (but built-in types use keyword syntax)
+ ///
+ ShortTypeName
+ }
+
+ public static class DisassemblerHelpers
+ {
+ public static string OffsetToString(int offset)
+ {
+ return string.Format("IL_{0:x4}", offset);
+ }
+
+ public static string OffsetToString(long offset)
+ {
+ return string.Format("IL_{0:x4}", offset);
+ }
+
+ public static void WriteOffsetReference(ITextOutput writer, int? offset)
+ {
+ if (offset == null)
+ writer.Write("null");
+ else
+ writer.WriteLocalReference(OffsetToString(offset.Value), offset);
+ }
+
+ static string ToInvariantCultureString(object value)
+ {
+ var convertible = value as IConvertible;
+ return (null != convertible)
+ ? convertible.ToString(System.Globalization.CultureInfo.InvariantCulture)
+ : value.ToString();
+ }
+
+ static bool IsValidIdentifierCharacter(char c)
+ {
+ return c == '_' || c == '$' || c == '@' || c == '?' || c == '`';
+ }
+
+ static bool IsValidIdentifier(string identifier)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ return false;
+ if (!(char.IsLetter(identifier[0]) || IsValidIdentifierCharacter(identifier[0])))
+ {
+ // As a special case, .ctor and .cctor are valid despite starting with a dot
+ return identifier == ".ctor" || identifier == ".cctor";
+ }
+ for (var i = 1; i < identifier.Length; i++)
+ {
+ if (!(char.IsLetterOrDigit(identifier[i]) || IsValidIdentifierCharacter(identifier[i]) || identifier[i] == '.'))
+ return false;
+ }
+ return true;
+ }
+
+ public static string Escape(string identifier)
+ {
+ if (IsValidIdentifier(identifier) && !Metadata.ILOpCodeExtensions.ILKeywords.Contains(identifier))
+ {
+ return identifier;
+ }
+ else
+ {
+ // The ECMA specification says that ' inside SQString should be ecaped using an octal escape sequence,
+ // but we follow Microsoft's ILDasm and use \'.
+ return "'" + EscapeString(identifier).Replace("'", "\\'") + "'";
+ }
+ }
+
+ public static void WriteParameterReference(ITextOutput writer, MetadataReader metadata, MethodDefinitionHandle handle, int sequence)
+ {
+ var methodDefinition = metadata.GetMethodDefinition(handle);
+ var signature = methodDefinition.DecodeSignature(new FullTypeNameSignatureDecoder(metadata), default);
+ var parameters = methodDefinition.GetParameters().Select(p => metadata.GetParameter(p)).ToArray();
+ var signatureHeader = signature.Header;
+ var index = sequence;
+ if (signatureHeader.IsInstance && signature.ParameterTypes.Length == parameters.Length)
+ {
+ index--;
+ }
+ if (index < 0 || index >= parameters.Length)
+ {
+ writer.WriteLocalReference(sequence.ToString(), "param_" + index);
+ }
+ else
+ {
+ var param = parameters[index];
+ if (param.Name.IsNil)
+ {
+ writer.WriteLocalReference(sequence.ToString(), "param_" + index);
+ }
+ else
+ {
+ writer.WriteLocalReference(Escape(metadata.GetString(param.Name)), "param_" + index);
+ }
+ }
+ }
+
+ public static void WriteVariableReference(ITextOutput writer, MetadataReader metadata, MethodDefinitionHandle handle, int index)
+ {
+ writer.WriteLocalReference(index.ToString(), "loc_" + index);
+ }
+
+ public static void WriteOperand(ITextOutput writer, object operand)
+ {
+ if (operand == null)
+ throw new ArgumentNullException(nameof(operand));
+
+ var s = operand as string;
+ if (s != null)
+ {
+ WriteOperand(writer, s);
+ }
+ else if (operand is char)
+ {
+ writer.Write(((int)(char)operand).ToString());
+ }
+ else if (operand is float)
+ {
+ WriteOperand(writer, (float)operand);
+ }
+ else if (operand is double)
+ {
+ WriteOperand(writer, (double)operand);
+ }
+ else if (operand is bool)
+ {
+ writer.Write((bool)operand ? "true" : "false");
+ }
+ else
+ {
+ s = ToInvariantCultureString(operand);
+ writer.Write(s);
+ }
+ }
+
+ public static void WriteOperand(ITextOutput writer, long val)
+ {
+ writer.Write(ToInvariantCultureString(val));
+ }
+
+ public static void WriteOperand(ITextOutput writer, float val)
+ {
+ if (val == 0)
+ {
+ if (1 / val == float.NegativeInfinity)
+ {
+ // negative zero is a special case
+ writer.Write('-');
+ }
+ writer.Write("0.0");
+ }
+ else if (float.IsInfinity(val) || float.IsNaN(val))
+ {
+ var data = BitConverter.GetBytes(val);
+ writer.Write('(');
+ for (var i = 0; i < data.Length; i++)
+ {
+ if (i > 0)
+ writer.Write(' ');
+ writer.Write(data[i].ToString("X2"));
+ }
+ writer.Write(')');
+ }
+ else
+ {
+ writer.Write(val.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
+ }
+ }
+
+ public static void WriteOperand(ITextOutput writer, double val)
+ {
+ if (val == 0)
+ {
+ if (1 / val == double.NegativeInfinity)
+ {
+ // negative zero is a special case
+ writer.Write('-');
+ }
+ writer.Write("0.0");
+ }
+ else if (double.IsInfinity(val) || double.IsNaN(val))
+ {
+ var data = BitConverter.GetBytes(val);
+ writer.Write('(');
+ for (var i = 0; i < data.Length; i++)
+ {
+ if (i > 0)
+ writer.Write(' ');
+ writer.Write(data[i].ToString("X2"));
+ }
+ writer.Write(')');
+ }
+ else
+ {
+ writer.Write(val.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
+ }
+ }
+
+ public static void WriteOperand(ITextOutput writer, string operand)
+ {
+ writer.Write('"');
+ writer.Write(EscapeString(operand));
+ writer.Write('"');
+ }
+
+ public static string EscapeString(string str)
+ {
+ var sb = new StringBuilder();
+ foreach (var ch in str)
+ {
+ switch (ch)
+ {
+ case '"':
+ sb.Append("\\\"");
+ break;
+ case '\\':
+ sb.Append("\\\\");
+ break;
+ case '\0':
+ sb.Append("\\0");
+ break;
+ case '\a':
+ sb.Append("\\a");
+ break;
+ case '\b':
+ sb.Append("\\b");
+ break;
+ case '\f':
+ sb.Append("\\f");
+ break;
+ case '\n':
+ sb.Append("\\n");
+ break;
+ case '\r':
+ sb.Append("\\r");
+ break;
+ case '\t':
+ sb.Append("\\t");
+ break;
+ case '\v':
+ sb.Append("\\v");
+ break;
+ default:
+ // print control characters and uncommon white spaces as numbers
+ if (char.IsControl(ch) || char.IsSurrogate(ch) || (char.IsWhiteSpace(ch) && ch != ' '))
+ {
+ sb.Append("\\u" + ((int)ch).ToString("x4"));
+ }
+ else
+ {
+ sb.Append(ch);
+ }
+ break;
+ }
+ }
+ return sb.ToString();
+ }
+ public static string PrimitiveTypeName(string fullName)
+ {
+ switch (fullName)
+ {
+ case "System.SByte":
+ return "int8";
+ case "System.Int16":
+ return "int16";
+ case "System.Int32":
+ return "int32";
+ case "System.Int64":
+ return "int64";
+ case "System.Byte":
+ return "uint8";
+ case "System.UInt16":
+ return "uint16";
+ case "System.UInt32":
+ return "uint32";
+ case "System.UInt64":
+ return "uint64";
+ case "System.Single":
+ return "float32";
+ case "System.Double":
+ return "float64";
+ case "System.Void":
+ return "void";
+ case "System.Boolean":
+ return "bool";
+ case "System.String":
+ return "string";
+ case "System.Char":
+ return "char";
+ case "System.Object":
+ return "object";
+ case "System.IntPtr":
+ return "native int";
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Documentation/GetPotentiallyNestedClassTypeReference.cs b/src/ICSharpCode.Decompiler/Documentation/GetPotentiallyNestedClassTypeReference.cs
new file mode 100644
index 0000000..3a79923
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Documentation/GetPotentiallyNestedClassTypeReference.cs
@@ -0,0 +1,124 @@
+// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Linq;
+using System.Reflection.Metadata;
+
+using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.TypeSystem.Implementation;
+
+namespace ICSharpCode.Decompiler.Documentation
+{
+ ///
+ /// A type reference of the form 'Some.Namespace.TopLevelType.NestedType`n'.
+ /// We do not know the boundary between namespace name and top level type, so we have to try
+ /// all possibilities.
+ /// The type parameter count only applies to the innermost type, all outer types must be non-generic.
+ ///
+ [Serializable]
+ public class GetPotentiallyNestedClassTypeReference : ITypeReference
+ {
+ readonly string typeName;
+ readonly int typeParameterCount;
+
+ public GetPotentiallyNestedClassTypeReference(string typeName, int typeParameterCount)
+ {
+ this.typeName = typeName;
+ this.typeParameterCount = typeParameterCount;
+ }
+
+ public IType Resolve(ITypeResolveContext context)
+ {
+ var parts = typeName.Split('.');
+ var assemblies = new[] { context.CurrentModule }.Concat(context.Compilation.Modules);
+ for (var i = parts.Length - 1; i >= 0; i--)
+ {
+ var ns = string.Join(".", parts, 0, i);
+ var name = parts[i];
+ var topLevelTPC = (i == parts.Length - 1 ? typeParameterCount : 0);
+ foreach (var asm in assemblies)
+ {
+ if (asm == null)
+ continue;
+ var typeDef = asm.GetTypeDefinition(new TopLevelTypeName(ns, name, topLevelTPC));
+ for (var j = i + 1; j < parts.Length && typeDef != null; j++)
+ {
+ var tpc = (j == parts.Length - 1 ? typeParameterCount : 0);
+ typeDef = typeDef.NestedTypes.FirstOrDefault(n => n.Name == parts[j] && n.TypeParameterCount == tpc);
+ }
+ if (typeDef != null)
+ return typeDef;
+ }
+ }
+ var idx = typeName.LastIndexOf('.');
+ if (idx < 0)
+ return new UnknownType("", typeName, typeParameterCount);
+ // give back a guessed namespace/type name
+ return new UnknownType(typeName.Substring(0, idx), typeName.Substring(idx + 1), typeParameterCount);
+ }
+
+ ///
+ /// Resolves the type reference within the context of the given PE file.
+ ///
+ /// Either TypeDefinitionHandle, if the type is defined in the module or ExportedTypeHandle,
+ /// if the module contains a type forwarder. Returns a nil handle, if the type was not found.
+ public EntityHandle ResolveInPEFile(PEFile module)
+ {
+ var parts = typeName.Split('.');
+ for (var i = parts.Length - 1; i >= 0; i--)
+ {
+ var ns = string.Join(".", parts, 0, i);
+ var name = parts[i];
+ var topLevelTPC = (i == parts.Length - 1 ? typeParameterCount : 0);
+ var topLevelName = new TopLevelTypeName(ns, name, topLevelTPC);
+ var typeHandle = module.GetTypeDefinition(topLevelName);
+
+ for (var j = i + 1; j < parts.Length && !typeHandle.IsNil; j++)
+ {
+ var tpc = (j == parts.Length - 1 ? typeParameterCount : 0);
+ var typeDef = module.Metadata.GetTypeDefinition(typeHandle);
+ var lookupName = parts[j] + (tpc > 0 ? "`" + tpc : "");
+ typeHandle = typeDef.GetNestedTypes().FirstOrDefault(n => IsEqualShortName(n, module.Metadata, lookupName));
+ }
+
+ if (!typeHandle.IsNil)
+ return typeHandle;
+ FullTypeName typeName = topLevelName;
+ for (var j = i + 1; j < parts.Length; j++)
+ {
+ var tpc = (j == parts.Length - 1 ? typeParameterCount : 0);
+ typeName = typeName.NestedType(parts[j], tpc);
+ }
+
+ var exportedType = module.GetTypeForwarder(typeName);
+ if (!exportedType.IsNil)
+ return exportedType;
+ }
+
+ return default;
+
+ bool IsEqualShortName(TypeDefinitionHandle h, MetadataReader metadata, string name)
+ {
+ var nestedType = metadata.GetTypeDefinition(h);
+ return metadata.StringComparer.Equals(nestedType.Name, name);
+ }
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Documentation/IdStringMemberReference.cs b/src/ICSharpCode.Decompiler/Documentation/IdStringMemberReference.cs
new file mode 100644
index 0000000..300f94a
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Documentation/IdStringMemberReference.cs
@@ -0,0 +1,75 @@
+// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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 ICSharpCode.Decompiler.TypeSystem;
+
+namespace ICSharpCode.Decompiler.Documentation
+{
+ [Serializable]
+ class IdStringMemberReference : IMemberReference
+ {
+ readonly ITypeReference declaringTypeReference;
+ readonly char memberType;
+ readonly string memberIdString;
+
+ public IdStringMemberReference(ITypeReference declaringTypeReference, char memberType, string memberIdString)
+ {
+ this.declaringTypeReference = declaringTypeReference;
+ this.memberType = memberType;
+ this.memberIdString = memberIdString;
+ }
+
+ bool CanMatch(IMember member)
+ {
+ switch (member.SymbolKind)
+ {
+ case SymbolKind.Field:
+ return memberType == 'F';
+ case SymbolKind.Property:
+ case SymbolKind.Indexer:
+ return memberType == 'P';
+ case SymbolKind.Event:
+ return memberType == 'E';
+ case SymbolKind.Method:
+ case SymbolKind.Operator:
+ case SymbolKind.Constructor:
+ case SymbolKind.Destructor:
+ return memberType == 'M';
+ default:
+ throw new NotSupportedException(member.SymbolKind.ToString());
+ }
+ }
+
+ public ITypeReference DeclaringTypeReference {
+ get { return declaringTypeReference; }
+ }
+
+ public IMember Resolve(ITypeResolveContext context)
+ {
+ var declaringType = declaringTypeReference.Resolve(context);
+ foreach (var member in declaringType.GetMembers(CanMatch, GetMemberOptions.IgnoreInheritedMembers))
+ {
+ if (IdStringProvider.GetIdString(member) == memberIdString)
+ return member;
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Documentation/IdStringProvider.cs b/src/ICSharpCode.Decompiler/Documentation/IdStringProvider.cs
new file mode 100644
index 0000000..22ddf5b
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Documentation/IdStringProvider.cs
@@ -0,0 +1,433 @@
+// Copyright (c) 2010-2018 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Linq;
+using System.Text;
+
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.TypeSystem.Implementation;
+
+namespace ICSharpCode.Decompiler.Documentation
+{
+ ///
+ /// Provides ID strings for entities. (C# 4.0 spec, §A.3.1)
+ /// ID strings are used to identify members in XML documentation files.
+ ///
+ public static class IdStringProvider
+ {
+ #region GetIdString
+ ///
+ /// Gets the ID string (C# 4.0 spec, §A.3.1) for the specified entity.
+ ///
+ public static string GetIdString(this IEntity entity)
+ {
+ var b = new StringBuilder();
+ switch (entity.SymbolKind)
+ {
+ case SymbolKind.TypeDefinition:
+ b.Append("T:");
+ AppendTypeName(b, (ITypeDefinition)entity, false);
+ return b.ToString();
+ case SymbolKind.Field:
+ b.Append("F:");
+ break;
+ case SymbolKind.Property:
+ case SymbolKind.Indexer:
+ b.Append("P:");
+ break;
+ case SymbolKind.Event:
+ b.Append("E:");
+ break;
+ default:
+ b.Append("M:");
+ break;
+ }
+ var member = (IMember)entity;
+ if (member.DeclaringType != null)
+ {
+ AppendTypeName(b, member.DeclaringType, false);
+ b.Append('.');
+ }
+ if (member.IsExplicitInterfaceImplementation && member.Name.IndexOf('.') < 0 && member.ExplicitlyImplementedInterfaceMembers.Count() == 1)
+ {
+ AppendTypeName(b, member.ExplicitlyImplementedInterfaceMembers.First().DeclaringType, true);
+ b.Append('#');
+ }
+ b.Append(member.Name.Replace('.', '#').Replace('<', '{').Replace('>', '}'));
+ var method = member as IMethod;
+ if (method != null && method.TypeParameters.Count > 0)
+ {
+ b.Append("``");
+ b.Append(method.TypeParameters.Count);
+ }
+ var parameterizedMember = member as IParameterizedMember;
+ if (parameterizedMember != null && parameterizedMember.Parameters.Count > 0)
+ {
+ b.Append('(');
+ var parameters = parameterizedMember.Parameters;
+ for (var i = 0; i < parameters.Count; i++)
+ {
+ if (i > 0)
+ b.Append(',');
+ AppendTypeName(b, parameters[i].Type, false);
+ }
+ b.Append(')');
+ }
+ if (member.SymbolKind == SymbolKind.Operator && (member.Name == "op_Implicit" || member.Name == "op_Explicit"))
+ {
+ b.Append('~');
+ AppendTypeName(b, member.ReturnType, false);
+ }
+ return b.ToString();
+ }
+ #endregion
+
+ #region GetTypeName
+ public static string GetTypeName(IType type)
+ {
+ if (type == null)
+ throw new ArgumentNullException(nameof(type));
+ var b = new StringBuilder();
+ AppendTypeName(b, type, false);
+ return b.ToString();
+ }
+
+ static void AppendTypeName(StringBuilder b, IType type, bool explicitInterfaceImpl)
+ {
+ switch (type.Kind)
+ {
+ case TypeKind.Dynamic:
+ b.Append(explicitInterfaceImpl ? "System#Object" : "System.Object");
+ break;
+ case TypeKind.TypeParameter:
+ var tp = (ITypeParameter)type;
+ if (explicitInterfaceImpl)
+ {
+ b.Append(tp.Name);
+ }
+ else
+ {
+ b.Append('`');
+ if (tp.OwnerType == SymbolKind.Method)
+ b.Append('`');
+ b.Append(tp.Index);
+ }
+ break;
+ case TypeKind.Array:
+ var array = (ArrayType)type;
+ AppendTypeName(b, array.ElementType, explicitInterfaceImpl);
+ b.Append('[');
+ if (array.Dimensions > 1)
+ {
+ for (var i = 0; i < array.Dimensions; i++)
+ {
+ if (i > 0)
+ b.Append(explicitInterfaceImpl ? '@' : ',');
+ if (!explicitInterfaceImpl)
+ b.Append("0:");
+ }
+ }
+ b.Append(']');
+ break;
+ case TypeKind.Pointer:
+ AppendTypeName(b, ((PointerType)type).ElementType, explicitInterfaceImpl);
+ b.Append('*');
+ break;
+ case TypeKind.ByReference:
+ AppendTypeName(b, ((ByReferenceType)type).ElementType, explicitInterfaceImpl);
+ b.Append('@');
+ break;
+ default:
+ var declType = type.DeclaringType;
+ if (declType != null)
+ {
+ AppendTypeName(b, declType, explicitInterfaceImpl);
+ b.Append(explicitInterfaceImpl ? '#' : '.');
+ b.Append(type.Name);
+ AppendTypeParameters(b, type, declType.TypeParameterCount, explicitInterfaceImpl);
+ }
+ else
+ {
+ if (explicitInterfaceImpl)
+ b.Append(type.FullName.Replace('.', '#'));
+ else
+ b.Append(type.FullName);
+ AppendTypeParameters(b, type, 0, explicitInterfaceImpl);
+ }
+ break;
+ }
+ }
+
+ static void AppendTypeParameters(StringBuilder b, IType type, int outerTypeParameterCount, bool explicitInterfaceImpl)
+ {
+ var tpc = type.TypeParameterCount - outerTypeParameterCount;
+ if (tpc > 0)
+ {
+ var pt = type as ParameterizedType;
+ if (pt != null)
+ {
+ b.Append('{');
+ var ta = pt.TypeArguments;
+ for (var i = outerTypeParameterCount; i < ta.Count; i++)
+ {
+ if (i > outerTypeParameterCount)
+ b.Append(explicitInterfaceImpl ? '@' : ',');
+ AppendTypeName(b, ta[i], explicitInterfaceImpl);
+ }
+ b.Append('}');
+ }
+ else
+ {
+ b.Append('`');
+ b.Append(tpc);
+ }
+ }
+ }
+ #endregion
+
+ #region ParseMemberName
+ ///
+ /// Parse the ID string into a member reference.
+ ///
+ /// The ID string representing the member (with "M:", "F:", "P:" or "E:" prefix).
+ /// A member reference that represents the ID string.
+ /// The syntax of the ID string is invalid
+ ///
+ /// The member reference will look in first,
+ /// and if the member is not found there,
+ /// it will look in all other assemblies of the compilation.
+ ///
+ public static IMemberReference ParseMemberIdString(string memberIdString)
+ {
+ if (memberIdString == null)
+ throw new ArgumentNullException(nameof(memberIdString));
+ if (memberIdString.Length < 2 || memberIdString[1] != ':')
+ throw new ReflectionNameParseException(0, "Missing type tag");
+ var typeChar = memberIdString[0];
+ var parenPos = memberIdString.IndexOf('(');
+ if (parenPos < 0)
+ parenPos = memberIdString.LastIndexOf('~');
+ if (parenPos < 0)
+ parenPos = memberIdString.Length;
+ var dotPos = memberIdString.LastIndexOf('.', parenPos - 1);
+ if (dotPos < 0)
+ throw new ReflectionNameParseException(0, "Could not find '.' separating type name from member name");
+ var typeName = memberIdString.Substring(0, dotPos);
+ var pos = 2;
+ var typeReference = ParseTypeName(typeName, ref pos);
+ if (pos != typeName.Length)
+ throw new ReflectionNameParseException(pos, "Expected end of type name");
+ // string memberName = memberIDString.Substring(dotPos + 1, parenPos - (dotPos + 1));
+ // pos = memberName.LastIndexOf("``");
+ // if (pos > 0)
+ // memberName = memberName.Substring(0, pos);
+ // memberName = memberName.Replace('#', '.');
+ return new IdStringMemberReference(typeReference, typeChar, memberIdString);
+ }
+ #endregion
+
+ #region ParseTypeName
+ ///
+ /// Parse the ID string type name into a type reference.
+ ///
+ /// The ID string representing the type (the "T:" prefix is optional).
+ /// A type reference that represents the ID string.
+ /// The syntax of the ID string is invalid
+ ///
+ ///
+ /// The type reference will look in first,
+ /// and if the type is not found there,
+ /// it will look in all other assemblies of the compilation.
+ ///
+ ///
+ /// If the type is open (contains type parameters '`0' or '``0'),
+ /// an with the appropriate CurrentTypeDefinition/CurrentMember is required
+ /// to resolve the reference to the ITypeParameter.
+ ///
+ ///
+ public static ITypeReference ParseTypeName(string typeName)
+ {
+ if (typeName == null)
+ throw new ArgumentNullException(nameof(typeName));
+ var pos = 0;
+ if (typeName.StartsWith("T:", StringComparison.Ordinal))
+ pos = 2;
+ var r = ParseTypeName(typeName, ref pos);
+ if (pos < typeName.Length)
+ throw new ReflectionNameParseException(pos, "Expected end of type name");
+ return r;
+ }
+
+ static bool IsIDStringSpecialCharacter(char c)
+ {
+ switch (c)
+ {
+ case ':':
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case '(':
+ case ')':
+ case '`':
+ case '*':
+ case '@':
+ case ',':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static ITypeReference ParseTypeName(string typeName, ref int pos)
+ {
+ var reflectionTypeName = typeName;
+ if (pos == typeName.Length)
+ throw new ReflectionNameParseException(pos, "Unexpected end");
+ ITypeReference result;
+ if (reflectionTypeName[pos] == '`')
+ {
+ // type parameter reference
+ pos++;
+ if (pos == reflectionTypeName.Length)
+ throw new ReflectionNameParseException(pos, "Unexpected end");
+ if (reflectionTypeName[pos] == '`')
+ {
+ // method type parameter reference
+ pos++;
+ var index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
+ result = TypeParameterReference.Create(SymbolKind.Method, index);
+ }
+ else
+ {
+ // class type parameter reference
+ var index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
+ result = TypeParameterReference.Create(SymbolKind.TypeDefinition, index);
+ }
+ }
+ else
+ {
+ // not a type parameter reference: read the actual type name
+ var typeArguments = new List();
+ var typeNameWithoutSuffix = ReadTypeName(typeName, ref pos, true, out var typeParameterCount, typeArguments);
+ result = new GetPotentiallyNestedClassTypeReference(typeNameWithoutSuffix, typeParameterCount);
+ while (pos < typeName.Length && typeName[pos] == '.')
+ {
+ pos++;
+ var nestedTypeName = ReadTypeName(typeName, ref pos, false, out typeParameterCount, typeArguments);
+ result = new NestedTypeReference(result, nestedTypeName, typeParameterCount);
+ }
+ if (typeArguments.Count > 0)
+ {
+ result = new ParameterizedTypeReference(result, typeArguments);
+ }
+ }
+ while (pos < typeName.Length)
+ {
+ switch (typeName[pos])
+ {
+ case '[':
+ var dimensions = 1;
+ do
+ {
+ pos++;
+ if (pos == typeName.Length)
+ throw new ReflectionNameParseException(pos, "Unexpected end");
+ if (typeName[pos] == ',')
+ dimensions++;
+ } while (typeName[pos] != ']');
+ result = new ArrayTypeReference(result, dimensions);
+ break;
+ case '*':
+ result = new PointerTypeReference(result);
+ break;
+ case '@':
+ result = new ByReferenceTypeReference(result);
+ break;
+ default:
+ return result;
+ }
+ pos++;
+ }
+ return result;
+ }
+
+ static string ReadTypeName(string typeName, ref int pos, bool allowDottedName, out int typeParameterCount, List typeArguments)
+ {
+ var startPos = pos;
+ // skip the simple name portion:
+ while (pos < typeName.Length && !IsIDStringSpecialCharacter(typeName[pos]) && (allowDottedName || typeName[pos] != '.'))
+ pos++;
+ if (pos == startPos)
+ throw new ReflectionNameParseException(pos, "Expected type name");
+ var shortTypeName = typeName.Substring(startPos, pos - startPos);
+ // read type arguments:
+ typeParameterCount = 0;
+ if (pos < typeName.Length && typeName[pos] == '`')
+ {
+ // unbound generic type
+ pos++;
+ typeParameterCount = ReflectionHelper.ReadTypeParameterCount(typeName, ref pos);
+ }
+ else if (pos < typeName.Length && typeName[pos] == '{')
+ {
+ // bound generic type
+ do
+ {
+ pos++;
+ typeArguments.Add(ParseTypeName(typeName, ref pos));
+ typeParameterCount++;
+ if (pos == typeName.Length)
+ throw new ReflectionNameParseException(pos, "Unexpected end");
+ } while (typeName[pos] == ',');
+ if (typeName[pos] != '}')
+ throw new ReflectionNameParseException(pos, "Expected '}'");
+ pos++;
+ }
+ return shortTypeName;
+ }
+ #endregion
+
+ #region FindEntity
+ ///
+ /// Finds the entity in the given type resolve context.
+ ///
+ /// ID string of the entity.
+ /// Type resolve context
+ /// Returns the entity, or null if it is not found.
+ /// The syntax of the ID string is invalid
+ public static IEntity FindEntity(string idString, ITypeResolveContext context)
+ {
+ if (idString == null)
+ throw new ArgumentNullException(nameof(idString));
+ if (context == null)
+ throw new ArgumentNullException(nameof(context));
+ if (idString.StartsWith("T:", StringComparison.Ordinal))
+ {
+ return ParseTypeName(idString.Substring(2)).Resolve(context).GetDefinition();
+ }
+ else
+ {
+ return ParseMemberIdString(idString).Resolve(context);
+ }
+ }
+ #endregion
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Documentation/XmlDocLoader.cs b/src/ICSharpCode.Decompiler/Documentation/XmlDocLoader.cs
new file mode 100644
index 0000000..248b279
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Documentation/XmlDocLoader.cs
@@ -0,0 +1,146 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ICSharpCode.Decompiler.Documentation
+{
+ ///
+ /// Helps finding and loading .xml documentation.
+ ///
+ public static class XmlDocLoader
+ {
+ static readonly Lazy mscorlibDocumentation = new Lazy(LoadMscorlibDocumentation);
+ static readonly ConditionalWeakTable cache = new ConditionalWeakTable();
+
+ static XmlDocumentationProvider LoadMscorlibDocumentation()
+ {
+ var xmlDocFile = FindXmlDocumentation("mscorlib.dll", TargetRuntime.Net_4_0)
+ ?? FindXmlDocumentation("mscorlib.dll", TargetRuntime.Net_2_0);
+ if (xmlDocFile != null)
+ return new XmlDocumentationProvider(xmlDocFile);
+ else
+ return null;
+ }
+
+ public static XmlDocumentationProvider MscorlibDocumentation {
+ get { return mscorlibDocumentation.Value; }
+ }
+
+ public static XmlDocumentationProvider LoadDocumentation(PEFile module)
+ {
+ if (module == null)
+ throw new ArgumentNullException(nameof(module));
+ lock (cache)
+ {
+ if (!cache.TryGetValue(module, out var xmlDoc))
+ {
+ var xmlDocFile = LookupLocalizedXmlDoc(module.FileName);
+ if (xmlDocFile == null)
+ {
+ xmlDocFile = FindXmlDocumentation(Path.GetFileName(module.FileName), module.GetRuntime());
+ }
+ if (xmlDocFile != null)
+ {
+ xmlDoc = new XmlDocumentationProvider(xmlDocFile);
+ cache.Add(module, xmlDoc);
+ }
+ else
+ {
+ cache.Add(module, null); // add missing documentation files as well
+ xmlDoc = null;
+ }
+ }
+ return xmlDoc;
+ }
+ }
+
+ static readonly string referenceAssembliesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\\Framework");
+ static readonly string frameworkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"Microsoft.NET\Framework");
+
+ static string FindXmlDocumentation(string assemblyFileName, TargetRuntime runtime)
+ {
+ string fileName;
+ switch (runtime)
+ {
+ case TargetRuntime.Net_1_0:
+ fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v1.0.3705", assemblyFileName));
+ break;
+ case TargetRuntime.Net_1_1:
+ fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v1.1.4322", assemblyFileName));
+ break;
+ case TargetRuntime.Net_2_0:
+ fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v2.0.50727", assemblyFileName))
+ ?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, "v3.5"))
+ ?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, "v3.0"))
+ ?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, @".NETFramework\v3.5\Profile\Client"));
+ break;
+ case TargetRuntime.Net_4_0:
+ default:
+ fileName = LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, @".NETFramework\v4.0", assemblyFileName))
+ ?? LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v4.0.30319", assemblyFileName));
+ break;
+ }
+ return fileName;
+ }
+
+ static string LookupLocalizedXmlDoc(string fileName)
+ {
+ if (string.IsNullOrEmpty(fileName))
+ return null;
+
+ var xmlFileName = Path.ChangeExtension(fileName, ".xml");
+ var currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
+ var localizedXmlDocFile = GetLocalizedName(xmlFileName, currentCulture);
+
+ Debug.WriteLine("Try find XMLDoc @" + localizedXmlDocFile);
+ if (File.Exists(localizedXmlDocFile))
+ {
+ return localizedXmlDocFile;
+ }
+ Debug.WriteLine("Try find XMLDoc @" + xmlFileName);
+ if (File.Exists(xmlFileName))
+ {
+ return xmlFileName;
+ }
+ if (currentCulture != "en")
+ {
+ var englishXmlDocFile = GetLocalizedName(xmlFileName, "en");
+ Debug.WriteLine("Try find XMLDoc @" + englishXmlDocFile);
+ if (File.Exists(englishXmlDocFile))
+ {
+ return englishXmlDocFile;
+ }
+ }
+ return null;
+ }
+
+ static string GetLocalizedName(string fileName, string language)
+ {
+ var localizedXmlDocFile = Path.GetDirectoryName(fileName);
+ localizedXmlDocFile = Path.Combine(localizedXmlDocFile, language);
+ localizedXmlDocFile = Path.Combine(localizedXmlDocFile, Path.GetFileName(fileName));
+ return localizedXmlDocFile;
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs b/src/ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs
new file mode 100644
index 0000000..d8b5c7b
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs
@@ -0,0 +1,267 @@
+// Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Linq;
+using System.Text;
+using System.Threading;
+using System.Xml.Linq;
+
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
+
+namespace ICSharpCode.Decompiler.Documentation
+{
+ ///
+ /// Represents an element in the XML documentation.
+ /// Any occurrences of "<inheritdoc/>" are replaced with the inherited documentation.
+ ///
+ public class XmlDocumentationElement
+ {
+ static XmlDocumentationElement Create(string documentationComment, IEntity declaringEntity)
+ {
+ return new XmlDocumentationElement(XElement.Parse(documentationComment), declaringEntity, null);
+ }
+
+ readonly XElement element;
+ readonly IEntity declaringEntity;
+ readonly Func crefResolver;
+ volatile string textContent;
+
+ ///
+ /// Inheritance level; used to prevent cyclic doc inheritance.
+ ///
+ int nestingLevel;
+
+ ///
+ /// Creates a new documentation element.
+ ///
+ public XmlDocumentationElement(XElement element, IEntity declaringEntity, Func crefResolver)
+ {
+ if (element == null)
+ throw new ArgumentNullException(nameof(element));
+ this.element = element;
+ this.declaringEntity = declaringEntity;
+ this.crefResolver = crefResolver;
+ }
+
+ ///
+ /// Creates a new documentation element.
+ ///
+ public XmlDocumentationElement(string text, IEntity declaringEntity)
+ {
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
+ this.declaringEntity = declaringEntity;
+ this.textContent = text;
+ }
+
+ ///
+ /// Gets the entity on which this documentation was originally declared.
+ /// May return null.
+ ///
+ public IEntity DeclaringEntity {
+ get { return declaringEntity; }
+ }
+
+ IEntity referencedEntity;
+ volatile bool referencedEntityInitialized;
+
+ ///
+ /// Gets the entity referenced by the 'cref' attribute.
+ /// May return null.
+ ///
+ public IEntity ReferencedEntity {
+ get {
+ if (!referencedEntityInitialized)
+ {
+ var cref = GetAttribute("cref");
+ try
+ {
+ if (!string.IsNullOrEmpty(cref) && crefResolver != null)
+ referencedEntity = crefResolver(cref);
+ }
+ catch
+ {
+ referencedEntity = null;
+ }
+ referencedEntityInitialized = true;
+ }
+ return referencedEntity;
+ }
+ }
+
+ ///
+ /// Gets the element name.
+ ///
+ public string Name {
+ get {
+ return element != null ? element.Name.LocalName : string.Empty;
+ }
+ }
+
+ ///
+ /// Gets the attribute value.
+ ///
+ public string GetAttribute(string name)
+ {
+ return element?.Attribute(name)?.Value;
+ }
+
+ ///
+ /// Gets whether this is a pure text node.
+ ///
+ public bool IsTextNode {
+ get { return element == null; }
+ }
+
+ ///
+ /// Gets the text content.
+ ///
+ public string TextContent {
+ get {
+ if (textContent == null)
+ {
+ var b = new StringBuilder();
+ foreach (var child in this.Children)
+ b.Append(child.TextContent);
+ textContent = b.ToString();
+ }
+ return textContent;
+ }
+ }
+
+ IList children;
+
+ ///
+ /// Gets the child elements.
+ ///
+ public IList Children {
+ get {
+ if (element == null)
+ return EmptyList.Instance;
+ return LazyInitializer.EnsureInitialized(
+ ref this.children,
+ () => CreateElements(element.Nodes(), declaringEntity, crefResolver, nestingLevel));
+ }
+ }
+
+ static readonly string[] doNotInheritIfAlreadyPresent = {
+ "example", "exclude", "filterpriority", "preliminary", "summary",
+ "remarks", "returns", "threadsafety", "value"
+ };
+
+ static List CreateElements(IEnumerable childObjects, IEntity declaringEntity, Func crefResolver, int nestingLevel)
+ {
+ var list = new List();
+ foreach (var child in childObjects)
+ {
+ var childText = child as XText;
+ var childTag = child as XCData;
+ var childElement = child as XElement;
+ if (childText != null)
+ {
+ list.Add(new XmlDocumentationElement(childText.Value, declaringEntity));
+ }
+ else if (childTag != null)
+ {
+ list.Add(new XmlDocumentationElement(childTag.Value, declaringEntity));
+ }
+ else if (childElement != null)
+ {
+ if (nestingLevel < 5 && childElement.Name == "inheritdoc")
+ {
+ var cref = childElement.Attribute("cref").Value;
+ IEntity inheritedFrom = null;
+ string inheritedDocumentation = null;
+ if (cref != null)
+ {
+ inheritedFrom = crefResolver(cref);
+ if (inheritedFrom != null)
+ inheritedDocumentation = inheritedFrom.GetDocumentation();
+ }
+ else
+ {
+ foreach (var baseMember in InheritanceHelper.GetBaseMembers((IMember)declaringEntity, includeImplementedInterfaces: true))
+ {
+ inheritedDocumentation = baseMember.GetDocumentation();
+ if (inheritedDocumentation != null)
+ {
+ inheritedFrom = baseMember;
+ break;
+ }
+ }
+ }
+
+ if (inheritedDocumentation != null)
+ {
+ var doc = XDocument.Parse(inheritedDocumentation);
+
+ // XPath filter not yet implemented
+ if (childElement.Parent == null && childElement.Attribute("select").Value == null)
+ {
+ // Inheriting documentation at the root level
+ var doNotInherit = new List();
+ doNotInherit.Add("overloads");
+ doNotInherit.AddRange(childObjects.OfType().Select(e => e.Name.LocalName).Intersect(
+ doNotInheritIfAlreadyPresent));
+
+ var inheritedChildren = doc.Nodes().Where(
+ inheritedObject => {
+ var inheritedElement = inheritedObject as XElement;
+ return !(inheritedElement != null && doNotInherit.Contains(inheritedElement.Name.LocalName));
+ });
+
+ list.AddRange(CreateElements(inheritedChildren, inheritedFrom, crefResolver, nestingLevel + 1));
+ }
+ }
+ }
+ else
+ {
+ list.Add(new XmlDocumentationElement(childElement, declaringEntity, crefResolver) { nestingLevel = nestingLevel });
+ }
+ }
+ }
+ if (list.Count > 0 && list[0].IsTextNode)
+ {
+ if (string.IsNullOrWhiteSpace(list[0].textContent))
+ list.RemoveAt(0);
+ else
+ list[0].textContent = list[0].textContent.TrimStart();
+ }
+ if (list.Count > 0 && list[list.Count - 1].IsTextNode)
+ {
+ if (string.IsNullOrWhiteSpace(list[list.Count - 1].textContent))
+ list.RemoveAt(list.Count - 1);
+ else
+ list[list.Count - 1].textContent = list[list.Count - 1].textContent.TrimEnd();
+ }
+ return list;
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (element != null)
+ return "<" + element.Name + ">";
+ else
+ return this.TextContent;
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Documentation/XmlDocumentationProvider.cs b/src/ICSharpCode.Decompiler/Documentation/XmlDocumentationProvider.cs
new file mode 100644
index 0000000..dca680a
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Documentation/XmlDocumentationProvider.cs
@@ -0,0 +1,484 @@
+// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.IO;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Xml;
+
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
+
+namespace ICSharpCode.Decompiler.Documentation
+{
+ ///
+ /// Provides XML documentation for type and member definitions in source code.
+ ///
+ public interface IDocumentationProvider
+ {
+ ///
+ /// Returns the XML documentation for the given .
+ /// May return null, if no documentation is present for the entity.
+ ///
+ /// is null.
+ string GetDocumentation(IEntity entity);
+ }
+
+ ///
+ /// Provides documentation from an .xml file (as generated by the Microsoft C# compiler).
+ ///
+ ///
+ /// This class first creates an in-memory index of the .xml file, and then uses that to read only the requested members.
+ /// This way, we avoid keeping all the documentation in memory.
+ /// The .xml file is only opened when necessary, the file handle is not kept open all the time.
+ /// If the .xml file is changed, the index will automatically be recreated.
+ ///
+ [Serializable]
+ public class XmlDocumentationProvider : IDeserializationCallback, IDocumentationProvider
+ {
+ #region Cache
+ sealed class XmlDocumentationCache
+ {
+ readonly KeyValuePair[] entries;
+ int pos;
+
+ public XmlDocumentationCache(int size = 50)
+ {
+ if (size <= 0)
+ throw new ArgumentOutOfRangeException(nameof(size), size, "Value must be positive");
+ this.entries = new KeyValuePair[size];
+ }
+
+ internal bool TryGet(string key, out string value)
+ {
+ foreach (var pair in entries)
+ {
+ if (pair.Key == key)
+ {
+ value = pair.Value;
+ return true;
+ }
+ }
+ value = null;
+ return false;
+ }
+
+ internal void Add(string key, string value)
+ {
+ entries[pos++] = new KeyValuePair(key, value);
+ if (pos == entries.Length)
+ pos = 0;
+ }
+ }
+ #endregion
+
+ [Serializable]
+ struct IndexEntry : IComparable
+ {
+ ///
+ /// Hash code of the documentation tag
+ ///
+ internal readonly int HashCode;
+
+ ///
+ /// Position in the .xml file where the documentation starts
+ ///
+ internal readonly int PositionInFile;
+
+ internal IndexEntry(int hashCode, int positionInFile)
+ {
+ this.HashCode = hashCode;
+ this.PositionInFile = positionInFile;
+ }
+
+ public int CompareTo(IndexEntry other)
+ {
+ return this.HashCode.CompareTo(other.HashCode);
+ }
+ }
+
+ [NonSerialized]
+ XmlDocumentationCache cache = new XmlDocumentationCache();
+
+ readonly string fileName;
+ readonly Encoding encoding;
+ volatile IndexEntry[] index; // SORTED array of index entries
+
+ #region Constructor / Redirection support
+ ///
+ /// Creates a new XmlDocumentationProvider.
+ ///
+ /// Name of the .xml file.
+ /// Error reading from XML file (or from redirected file)
+ /// Invalid XML file
+ public XmlDocumentationProvider(string fileName)
+ {
+ if (fileName == null)
+ throw new ArgumentNullException(nameof(fileName));
+
+ using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
+ {
+ using (var xmlReader = new XmlTextReader(fs))
+ {
+ xmlReader.XmlResolver = null; // no DTD resolving
+ xmlReader.MoveToContent();
+ if (string.IsNullOrEmpty(xmlReader.GetAttribute("redirect")))
+ {
+ this.fileName = fileName;
+ this.encoding = xmlReader.Encoding;
+ ReadXmlDoc(xmlReader);
+ }
+ else
+ {
+ var redirectionTarget = GetRedirectionTarget(fileName, xmlReader.GetAttribute("redirect"));
+ if (redirectionTarget != null)
+ {
+ Debug.WriteLine("XmlDoc " + fileName + " is redirecting to " + redirectionTarget);
+ using (var redirectedFs = new FileStream(redirectionTarget, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
+ {
+ using (var redirectedXmlReader = new XmlTextReader(redirectedFs))
+ {
+ redirectedXmlReader.XmlResolver = null; // no DTD resolving
+ redirectedXmlReader.MoveToContent();
+ this.fileName = redirectionTarget;
+ this.encoding = redirectedXmlReader.Encoding;
+ ReadXmlDoc(redirectedXmlReader);
+ }
+ }
+ }
+ else
+ {
+ throw new XmlException("XmlDoc " + fileName + " is redirecting to " + xmlReader.GetAttribute("redirect") + ", but that file was not found.");
+ }
+ }
+ }
+ }
+ }
+
+ static string GetRedirectionTarget(string xmlFileName, string target)
+ {
+ var programFilesDir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
+ programFilesDir = AppendDirectorySeparator(programFilesDir);
+
+ var corSysDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
+ corSysDir = AppendDirectorySeparator(corSysDir);
+
+ var fileName = target.Replace("%PROGRAMFILESDIR%", programFilesDir)
+ .Replace("%CORSYSDIR%", corSysDir);
+ if (!Path.IsPathRooted(fileName))
+ fileName = Path.Combine(Path.GetDirectoryName(xmlFileName), fileName);
+ return LookupLocalizedXmlDoc(fileName);
+ }
+
+ static string AppendDirectorySeparator(string dir)
+ {
+ if (dir.EndsWith("\\", StringComparison.Ordinal) || dir.EndsWith("/", StringComparison.Ordinal))
+ return dir;
+ else
+ return dir + Path.DirectorySeparatorChar;
+ }
+
+ ///
+ /// Given the assembly file name, looks up the XML documentation file name.
+ /// Returns null if no XML documentation file is found.
+ ///
+ public static string LookupLocalizedXmlDoc(string fileName)
+ {
+ var xmlFileName = Path.ChangeExtension(fileName, ".xml");
+ var currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
+ var localizedXmlDocFile = GetLocalizedName(xmlFileName, currentCulture);
+
+ Debug.WriteLine("Try find XMLDoc @" + localizedXmlDocFile);
+ if (File.Exists(localizedXmlDocFile))
+ {
+ return localizedXmlDocFile;
+ }
+ Debug.WriteLine("Try find XMLDoc @" + xmlFileName);
+ if (File.Exists(xmlFileName))
+ {
+ return xmlFileName;
+ }
+ if (currentCulture != "en")
+ {
+ var englishXmlDocFile = GetLocalizedName(xmlFileName, "en");
+ Debug.WriteLine("Try find XMLDoc @" + englishXmlDocFile);
+ if (File.Exists(englishXmlDocFile))
+ {
+ return englishXmlDocFile;
+ }
+ }
+ return null;
+ }
+
+ static string GetLocalizedName(string fileName, string language)
+ {
+ var localizedXmlDocFile = Path.GetDirectoryName(fileName);
+ localizedXmlDocFile = Path.Combine(localizedXmlDocFile, language);
+ localizedXmlDocFile = Path.Combine(localizedXmlDocFile, Path.GetFileName(fileName));
+ return localizedXmlDocFile;
+ }
+ #endregion
+
+ #region Load / Create Index
+ void ReadXmlDoc(XmlTextReader reader)
+ {
+ //lastWriteDate = File.GetLastWriteTimeUtc(fileName);
+ // Open up a second file stream for the line<->position mapping
+ using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
+ {
+ var linePosMapper = new LinePositionMapper(fs, encoding);
+ var indexList = new List();
+ while (reader.Read())
+ {
+ if (reader.IsStartElement())
+ {
+ switch (reader.LocalName)
+ {
+ case "members":
+ ReadMembersSection(reader, linePosMapper, indexList);
+ break;
+ }
+ }
+ }
+ indexList.Sort();
+ this.index = indexList.ToArray(); // volatile write
+ }
+ }
+
+ sealed class LinePositionMapper
+ {
+ readonly FileStream fs;
+ readonly Decoder decoder;
+ int currentLine = 1;
+ char prevChar = '\0';
+
+ // buffers for use with Decoder:
+ byte[] input = new byte[1];
+ char[] output = new char[1];
+
+ public LinePositionMapper(FileStream fs, Encoding encoding)
+ {
+ this.decoder = encoding.GetDecoder();
+ this.fs = fs;
+ }
+
+ public int GetPositionForLine(int line)
+ {
+ Debug.Assert(line >= currentLine);
+ while (line > currentLine)
+ {
+ var b = fs.ReadByte();
+ if (b < 0)
+ throw new EndOfStreamException();
+ input[0] = (byte)b;
+ decoder.Convert(input, 0, 1, output, 0, 1, false, out var bytesUsed, out var charsUsed, out _);
+ Debug.Assert(bytesUsed == 1);
+ if (charsUsed == 1)
+ {
+ if ((prevChar != '\r' && output[0] == '\n') || output[0] == '\r')
+ currentLine++;
+ prevChar = output[0];
+ }
+ }
+ return checked((int)fs.Position);
+ }
+ }
+
+ static void ReadMembersSection(XmlTextReader reader, LinePositionMapper linePosMapper, List indexList)
+ {
+ while (reader.Read())
+ {
+ switch (reader.NodeType)
+ {
+ case XmlNodeType.EndElement:
+ if (reader.LocalName == "members")
+ {
+ return;
+ }
+ break;
+ case XmlNodeType.Element:
+ if (reader.LocalName == "member")
+ {
+ var pos = linePosMapper.GetPositionForLine(reader.LineNumber) + Math.Max(reader.LinePosition - 2, 0);
+ var memberAttr = reader.GetAttribute("name");
+ if (memberAttr != null)
+ indexList.Add(new IndexEntry(GetHashCode(memberAttr), pos));
+ reader.Skip();
+ }
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Hash algorithm used for the index.
+ /// This is a custom implementation so that old index files work correctly
+ /// even when the .NET string.GetHashCode implementation changes
+ /// (e.g. due to .NET 4.5 hash randomization)
+ ///
+ static int GetHashCode(string key)
+ {
+ unchecked
+ {
+ var h = 0;
+ foreach (var c in key)
+ {
+ h = (h << 5) - h + c;
+ }
+ return h;
+ }
+ }
+ #endregion
+
+ #region GetDocumentation
+ ///
+ /// Get the documentation for the member with the specified documentation key.
+ ///
+ public string GetDocumentation(string key)
+ {
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+ return GetDocumentation(key, true);
+ }
+
+ ///
+ /// Get the documentation for the specified member.
+ ///
+ public string GetDocumentation(IEntity entity)
+ {
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entity));
+ return GetDocumentation(entity.GetIdString());
+ }
+
+ string GetDocumentation(string key, bool allowReload)
+ {
+ var hashcode = GetHashCode(key);
+ var index = this.index; // read volatile field
+ // index is sorted, so we can use binary search
+ var m = Array.BinarySearch(index, new IndexEntry(hashcode, 0));
+ if (m < 0)
+ return null;
+ // correct hash code found.
+ // possibly there are multiple items with the same hash, so go to the first.
+ while (--m >= 0 && index[m].HashCode == hashcode)
+ ;
+ // m is now 1 before the first item with the correct hash
+
+ var cache = this.cache;
+ lock (cache)
+ {
+ if (!cache.TryGet(key, out var val))
+ {
+ try
+ {
+ // go through all items that have the correct hash
+ while (++m < index.Length && index[m].HashCode == hashcode)
+ {
+ val = LoadDocumentation(key, index[m].PositionInFile);
+ if (val != null)
+ break;
+ }
+ // cache the result (even if it is null)
+ cache.Add(key, val);
+ }
+ catch (IOException)
+ {
+ // may happen if the documentation file was deleted/is inaccessible/changed (EndOfStreamException)
+ return allowReload ? ReloadAndGetDocumentation(key) : null;
+ }
+ catch (XmlException)
+ {
+ // may happen if the documentation file was changed so that the file position no longer starts on a valid XML element
+ return allowReload ? ReloadAndGetDocumentation(key) : null;
+ }
+ }
+ return val;
+ }
+ }
+
+ string ReloadAndGetDocumentation(string key)
+ {
+ try
+ {
+ // Reload the index
+ using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
+ {
+ using (var xmlReader = new XmlTextReader(fs))
+ {
+ xmlReader.XmlResolver = null; // no DTD resolving
+ xmlReader.MoveToContent();
+ ReadXmlDoc(xmlReader);
+ }
+ }
+ }
+ catch (IOException)
+ {
+ // Ignore errors on reload; IEntity.Documentation callers aren't prepared to handle exceptions
+ this.index = Empty.Array; // clear index to avoid future load attempts
+ return null;
+ }
+ catch (XmlException)
+ {
+ this.index = Empty.Array; // clear index to avoid future load attempts
+ return null;
+ }
+ return GetDocumentation(key, allowReload: false); // prevent infinite reload loops
+ }
+ #endregion
+
+ #region Load / Read XML
+ string LoadDocumentation(string key, int positionInFile)
+ {
+ using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
+ {
+ fs.Position = positionInFile;
+ var context = new XmlParserContext(null, null, null, XmlSpace.None) { Encoding = encoding };
+ using (var r = new XmlTextReader(fs, XmlNodeType.Element, context))
+ {
+ r.XmlResolver = null; // no DTD resolving
+ while (r.Read())
+ {
+ if (r.NodeType == XmlNodeType.Element)
+ {
+ var memberAttr = r.GetAttribute("name");
+ if (memberAttr == key)
+ {
+ return r.ReadInnerXml();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+ }
+ }
+ #endregion
+
+ public virtual void OnDeserialization(object sender)
+ {
+ cache = new XmlDocumentationCache();
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/src/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
new file mode 100644
index 0000000..20f3b05
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -0,0 +1,51 @@
+
+
+
+
+ netstandard2.0;net5.0
+
+ IL decompiler engine
+ ic#code
+ ILSpy
+ Copyright 2011-2020 AlphaSierraPapa for the SharpDevelop Team
+ en-US
+
+ latest
+ true
+ 1701;1702;1591;1573
+ false
+ false
+ false
+ true
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ TextTemplatingFileGenerator
+ ILOpCodes.cs
+
+
+
+
+
+
+
+
+
+ True
+ True
+ ILOpCodes.tt
+
+
+
+
+
diff --git a/src/ICSharpCode.Decompiler/IL/PrimitiveType.cs b/src/ICSharpCode.Decompiler/IL/PrimitiveType.cs
new file mode 100644
index 0000000..9d45d54
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/IL/PrimitiveType.cs
@@ -0,0 +1,52 @@
+// Copyright (c) 2014 Daniel Grunwald
+//
+// 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.Reflection.Metadata;
+
+namespace ICSharpCode.Decompiler.IL
+{
+ public enum PrimitiveType : byte
+ {
+ None,
+ I1 = PrimitiveTypeCode.SByte,
+ I2 = PrimitiveTypeCode.Int16,
+ I4 = PrimitiveTypeCode.Int32,
+ I8 = PrimitiveTypeCode.Int64,
+ R4 = PrimitiveTypeCode.Single,
+ R8 = PrimitiveTypeCode.Double,
+ U1 = PrimitiveTypeCode.Byte,
+ U2 = PrimitiveTypeCode.UInt16,
+ U4 = PrimitiveTypeCode.UInt32,
+ U8 = PrimitiveTypeCode.UInt64,
+ I = PrimitiveTypeCode.IntPtr,
+ U = PrimitiveTypeCode.UIntPtr,
+ /// Managed reference
+ Ref = 16,
+ /// Floating point type of unspecified size:
+ /// usually 80 bits on x86 (when the runtime uses x87 instructions);
+ /// but only 64-bit on x64.
+ /// This only occurs for "conv.r.un" instructions. The C# compiler usually follows those
+ /// with a "conv.r4" or "conv.r8" instruction to indicate the desired float type, so
+ /// we only use this as conversion target type and don't bother tracking it as its own stack type:
+ /// basically everything treats R identical to R8, except for the (conv.r.un + conv.r[48] => conv.r[48].un)
+ /// combining logic which should not combine (conv.r.un + conv.r8 + conv.r4) into a single conv.r4.un.
+ ///
+ R = 254,
+ Unknown = 255
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/IL/StackType.cs b/src/ICSharpCode.Decompiler/IL/StackType.cs
new file mode 100644
index 0000000..6c30316
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/IL/StackType.cs
@@ -0,0 +1,74 @@
+// Copyright (c) 2014 Daniel Grunwald
+//
+// 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.
+
+
+namespace ICSharpCode.Decompiler.IL
+{
+ ///
+ /// A type for the purpose of stack analysis.
+ ///
+ public enum StackType : byte
+ {
+ // Note: the numeric of these enum members is relevant for ILReader.MergeStacks:
+ // when two branches meet where a stack slot has different types, the type after
+ // the branch is the one with the higher numeric value.
+
+ ///
+ /// The stack type is unknown; for example a call returning an unknown type
+ /// because an assembly reference isn't loaded.
+ /// Can also occur with invalid IL.
+ ///
+ Unknown,
+ /// 32-bit integer
+ ///
+ /// Used for C# int, uint,
+ /// C# small integer types byte, sbyte, short, ushort,
+ /// bool and char,
+ /// and any enums with one of the above as underlying type.
+ ///
+ I4,
+ /// native-size integer, or unmanaged pointer
+ ///
+ /// Used for C# IntPtr, UIntPtr and any native pointer types (void* etc.)
+ /// Also used for IL function pointer types.
+ ///
+ I,
+ /// 64-bit integer
+ ///
+ /// Used for C# long, ulong,
+ /// and any enums with one of the above as underlying type.
+ ///
+ I8,
+ /// 32-bit floating point number
+ ///
+ /// Used for C# float.
+ ///
+ F4,
+ /// 64-bit floating point number
+ ///
+ /// Used for C# double.
+ ///
+ F8,
+ /// Another stack type. Includes objects, value types, ...
+ O,
+ /// A managed pointer
+ Ref,
+ /// Represents the lack of a stack slot
+ Void
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/ITextOutput.cs b/src/ICSharpCode.Decompiler/ITextOutput.cs
new file mode 100644
index 0000000..0a7bdc9
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/ITextOutput.cs
@@ -0,0 +1,64 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Reflection.Metadata;
+
+using ICSharpCode.Decompiler.Disassembler;
+using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.Decompiler.TypeSystem;
+
+namespace ICSharpCode.Decompiler
+{
+ public interface ITextOutput
+ {
+ string IndentationString { get; set; }
+ void Indent();
+ void Unindent();
+ void Write(char ch);
+ void Write(string text);
+ void WriteLine();
+ void WriteReference(OpCodeInfo opCode, bool omitSuffix = false);
+ void WriteReference(PEFile module, Handle handle, string text, string protocol = "decompile", bool isDefinition = false);
+ void WriteReference(IType type, string text, bool isDefinition = false);
+ void WriteReference(IMember member, string text, bool isDefinition = false);
+ void WriteLocalReference(string text, object reference, bool isDefinition = false);
+
+ void MarkFoldStart(string collapsedText = "...", bool defaultCollapsed = false);
+ void MarkFoldEnd();
+ }
+
+ public static class TextOutputExtensions
+ {
+ public static void Write(this ITextOutput output, string format, params object[] args)
+ {
+ output.Write(string.Format(format, args));
+ }
+
+ public static void WriteLine(this ITextOutput output, string text)
+ {
+ output.Write(text);
+ output.WriteLine();
+ }
+
+ public static void WriteLine(this ITextOutput output, string format, params object[] args)
+ {
+ output.WriteLine(string.Format(format, args));
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs b/src/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
new file mode 100644
index 0000000..740ade3
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
@@ -0,0 +1,278 @@
+// Copyright (c) 2018 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.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ public sealed class AssemblyResolutionException : FileNotFoundException
+ {
+ public IAssemblyReference Reference { get; }
+
+ public AssemblyResolutionException(IAssemblyReference reference)
+ : this(reference, null)
+ {
+ }
+
+ public AssemblyResolutionException(IAssemblyReference reference, Exception innerException)
+ : base($"Failed to resolve assembly: '{reference}'", innerException)
+ {
+ this.Reference = reference;
+ }
+ }
+
+ public interface IAssemblyResolver
+ {
+#if !VSADDIN
+ PEFile Resolve(IAssemblyReference reference);
+ PEFile ResolveModule(PEFile mainModule, string moduleName);
+#endif
+ }
+
+ public class AssemblyReferenceClassifier
+ {
+ ///
+ /// For GAC assembly references, the WholeProjectDecompiler will omit the HintPath in the
+ /// generated .csproj file.
+ ///
+ public virtual bool IsGacAssembly(IAssemblyReference reference)
+ {
+ return UniversalAssemblyResolver.GetAssemblyInGac(reference) != null;
+ }
+
+ ///
+ /// For .NET Core framework references, the WholeProjectDecompiler will omit the
+ /// assembly reference if the runtimePack is already included as an SDK.
+ ///
+ public virtual bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack)
+ {
+ runtimePack = null;
+ return false;
+ }
+ }
+
+ public interface IAssemblyReference
+ {
+ string Name { get; }
+ string FullName { get; }
+ Version Version { get; }
+ string Culture { get; }
+ byte[] PublicKeyToken { get; }
+
+ bool IsWindowsRuntime { get; }
+ bool IsRetargetable { get; }
+ }
+
+ public class AssemblyNameReference : IAssemblyReference
+ {
+ string fullName;
+
+ public string Name { get; private set; }
+
+ public string FullName {
+ get {
+ if (fullName != null)
+ return fullName;
+
+ const string sep = ", ";
+
+ var builder = new StringBuilder();
+ builder.Append(Name);
+ builder.Append(sep);
+ builder.Append("Version=");
+ builder.Append((Version ?? UniversalAssemblyResolver.ZeroVersion).ToString(fieldCount: 4));
+ builder.Append(sep);
+ builder.Append("Culture=");
+ builder.Append(string.IsNullOrEmpty(Culture) ? "neutral" : Culture);
+ builder.Append(sep);
+ builder.Append("PublicKeyToken=");
+
+ var pk_token = PublicKeyToken;
+ if (pk_token != null && pk_token.Length > 0)
+ {
+ for (var i = 0; i < pk_token.Length; i++)
+ {
+ builder.Append(pk_token[i].ToString("x2"));
+ }
+ }
+ else
+ builder.Append("null");
+
+ if (IsRetargetable)
+ {
+ builder.Append(sep);
+ builder.Append("Retargetable=Yes");
+ }
+
+ return fullName = builder.ToString();
+ }
+ }
+
+ public Version Version { get; private set; }
+
+ public string Culture { get; private set; }
+
+ public byte[] PublicKeyToken { get; private set; }
+
+ public bool IsWindowsRuntime { get; private set; }
+
+ public bool IsRetargetable { get; private set; }
+
+ public static AssemblyNameReference Parse(string fullName)
+ {
+ if (fullName == null)
+ throw new ArgumentNullException(nameof(fullName));
+ if (fullName.Length == 0)
+ throw new ArgumentException("Name can not be empty");
+
+ var name = new AssemblyNameReference();
+ var tokens = fullName.Split(',');
+ for (var i = 0; i < tokens.Length; i++)
+ {
+ var token = tokens[i].Trim();
+
+ if (i == 0)
+ {
+ name.Name = token;
+ continue;
+ }
+
+ var parts = token.Split('=');
+ if (parts.Length != 2)
+ throw new ArgumentException("Malformed name");
+
+ switch (parts[0].ToLowerInvariant())
+ {
+ case "version":
+ name.Version = new Version(parts[1]);
+ break;
+ case "culture":
+ name.Culture = parts[1] == "neutral" ? "" : parts[1];
+ break;
+ case "publickeytoken":
+ var pk_token = parts[1];
+ if (pk_token == "null")
+ break;
+
+ name.PublicKeyToken = new byte[pk_token.Length / 2];
+ for (var j = 0; j < name.PublicKeyToken.Length; j++)
+ name.PublicKeyToken[j] = Byte.Parse(pk_token.Substring(j * 2, 2), System.Globalization.NumberStyles.HexNumber);
+
+ break;
+ }
+ }
+
+ return name;
+ }
+
+ public override string ToString()
+ {
+ return FullName;
+ }
+ }
+
+#if !VSADDIN
+ public class AssemblyReference : IAssemblyReference
+ {
+ static readonly SHA1 sha1 = SHA1.Create();
+
+ readonly System.Reflection.Metadata.AssemblyReference entry;
+
+ public MetadataReader Metadata { get; }
+ public AssemblyReferenceHandle Handle { get; }
+
+ public bool IsWindowsRuntime => (entry.Flags & AssemblyFlags.WindowsRuntime) != 0;
+ public bool IsRetargetable => (entry.Flags & AssemblyFlags.Retargetable) != 0;
+
+ public string Name {
+ get {
+ try
+ {
+ return Metadata.GetString(entry.Name);
+ }
+ catch (BadImageFormatException)
+ {
+ return $"AR:{Handle}";
+ }
+ }
+ }
+
+ public string FullName {
+ get {
+ try
+ {
+ return entry.GetFullAssemblyName(Metadata);
+ }
+ catch (BadImageFormatException)
+ {
+ return $"fullname(AR:{Handle})";
+ }
+ }
+ }
+
+ public Version Version => entry.Version;
+ public string Culture => Metadata.GetString(entry.Culture);
+ byte[] IAssemblyReference.PublicKeyToken => GetPublicKeyToken();
+
+ public byte[] GetPublicKeyToken()
+ {
+ if (entry.PublicKeyOrToken.IsNil)
+ return null;
+ var bytes = Metadata.GetBlobBytes(entry.PublicKeyOrToken);
+ if ((entry.Flags & AssemblyFlags.PublicKey) != 0)
+ {
+ return sha1.ComputeHash(bytes).Skip(12).ToArray();
+ }
+ return bytes;
+ }
+
+ public AssemblyReference(MetadataReader metadata, AssemblyReferenceHandle handle)
+ {
+ if (metadata == null)
+ throw new ArgumentNullException(nameof(metadata));
+ if (handle.IsNil)
+ throw new ArgumentNullException(nameof(handle));
+ Metadata = metadata;
+ Handle = handle;
+ entry = metadata.GetAssemblyReference(handle);
+ }
+
+ public AssemblyReference(PEFile module, AssemblyReferenceHandle handle)
+ {
+ if (module == null)
+ throw new ArgumentNullException(nameof(module));
+ if (handle.IsNil)
+ throw new ArgumentNullException(nameof(handle));
+ Metadata = module.Metadata;
+ Handle = handle;
+ entry = Metadata.GetAssemblyReference(handle);
+ }
+
+ public override string ToString()
+ {
+ return FullName;
+ }
+ }
+#endif
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/CodeMappingInfo.cs b/src/ICSharpCode.Decompiler/Metadata/CodeMappingInfo.cs
new file mode 100644
index 0000000..241996e
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/CodeMappingInfo.cs
@@ -0,0 +1,98 @@
+// Copyright (c) 2018 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.Collections.Generic;
+using System.Reflection.Metadata;
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ ///
+ /// Describes which parts of the (compiler-generated) code belong to which user code.
+ /// A part could be:
+ /// - the body (method) of a lambda.
+ /// - the MoveNext method of async/yield state machines.
+ ///
+ public class CodeMappingInfo
+ {
+ ///
+ /// The module containing the code.
+ ///
+ public PEFile Module { get; }
+
+ ///
+ /// The (parent) TypeDef containing the code.
+ ///
+ public TypeDefinitionHandle TypeDefinition { get; }
+
+ readonly Dictionary> parts;
+ readonly Dictionary parents;
+
+ ///
+ /// Creates a instance using the given and .
+ ///
+ public CodeMappingInfo(PEFile module, TypeDefinitionHandle type)
+ {
+ this.Module = module;
+ this.TypeDefinition = type;
+ this.parts = new Dictionary>();
+ this.parents = new Dictionary();
+ }
+
+ ///
+ /// Returns all parts of a method.
+ /// A method has at least one part, that is, the method itself.
+ /// If no parts are found, only the method itself is returned.
+ ///
+ public IEnumerable GetMethodParts(MethodDefinitionHandle method)
+ {
+ if (parts.TryGetValue(method, out var p))
+ return p;
+ return new[] { method };
+ }
+
+ ///
+ /// Returns the parent of a part.
+ /// The parent is usually the "calling method" of lambdas, async and yield state machines.
+ /// The "calling method" has itself as parent.
+ /// If no parent is found, the method itself is returned.
+ ///
+ public MethodDefinitionHandle GetParentMethod(MethodDefinitionHandle method)
+ {
+ if (parents.TryGetValue(method, out var p))
+ return p;
+ return method;
+ }
+
+ ///
+ /// Adds a bidirectional mapping between and .
+ ///
+ public void AddMapping(MethodDefinitionHandle parent, MethodDefinitionHandle part)
+ {
+ //Debug.Print("Parent: " + MetadataTokens.GetRowNumber(parent) + " Part: " + MetadataTokens.GetRowNumber(part));
+ if (parents.ContainsKey(part))
+ return;
+ parents.Add(part, parent);
+ if (!parts.TryGetValue(parent, out var list))
+ {
+ list = new List();
+ parts.Add(parent, list);
+ }
+ list.Add(part);
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/CustomAttributeDecoder.cs b/src/ICSharpCode.Decompiler/Metadata/CustomAttributeDecoder.cs
new file mode 100644
index 0000000..6449532
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/CustomAttributeDecoder.cs
@@ -0,0 +1,235 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Reflection.Metadata;
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ ///
+ /// Decodes custom attribute blobs.
+ ///
+ internal readonly struct CustomAttributeDecoder
+ {
+ // This is a stripped-down copy of SRM's internal CustomAttributeDecoder.
+ // We need it to decode security declarations.
+
+ private readonly ICustomAttributeTypeProvider _provider;
+ private readonly MetadataReader _reader;
+ private readonly bool _provideBoxingTypeInfo;
+
+ public CustomAttributeDecoder(ICustomAttributeTypeProvider provider, MetadataReader reader, bool provideBoxingTypeInfo = false)
+ {
+ _reader = reader;
+ _provider = provider;
+ _provideBoxingTypeInfo = provideBoxingTypeInfo;
+ }
+
+ public ImmutableArray> DecodeNamedArguments(ref BlobReader valueReader, int count)
+ {
+ var arguments = ImmutableArray.CreateBuilder>(count);
+ for (var i = 0; i < count; i++)
+ {
+ var kind = (CustomAttributeNamedArgumentKind)valueReader.ReadSerializationTypeCode();
+ if (kind != CustomAttributeNamedArgumentKind.Field && kind != CustomAttributeNamedArgumentKind.Property)
+ {
+ throw new BadImageFormatException();
+ }
+
+ var info = DecodeNamedArgumentType(ref valueReader);
+ var name = valueReader.ReadSerializedString();
+ var argument = DecodeArgument(ref valueReader, info);
+ arguments.Add(new CustomAttributeNamedArgument(name, kind, argument.Type, argument.Value));
+ }
+
+ return arguments.MoveToImmutable();
+ }
+
+ private struct ArgumentTypeInfo
+ {
+ public TType Type;
+ public TType ElementType;
+ public SerializationTypeCode TypeCode;
+ public SerializationTypeCode ElementTypeCode;
+ }
+
+ private ArgumentTypeInfo DecodeNamedArgumentType(ref BlobReader valueReader, bool isElementType = false)
+ {
+ var info = new ArgumentTypeInfo {
+ TypeCode = valueReader.ReadSerializationTypeCode(),
+ };
+
+ switch (info.TypeCode)
+ {
+ case SerializationTypeCode.Boolean:
+ case SerializationTypeCode.Byte:
+ case SerializationTypeCode.Char:
+ case SerializationTypeCode.Double:
+ case SerializationTypeCode.Int16:
+ case SerializationTypeCode.Int32:
+ case SerializationTypeCode.Int64:
+ case SerializationTypeCode.SByte:
+ case SerializationTypeCode.Single:
+ case SerializationTypeCode.String:
+ case SerializationTypeCode.UInt16:
+ case SerializationTypeCode.UInt32:
+ case SerializationTypeCode.UInt64:
+ info.Type = _provider.GetPrimitiveType((PrimitiveTypeCode)info.TypeCode);
+ break;
+
+ case SerializationTypeCode.Type:
+ info.Type = _provider.GetSystemType();
+ break;
+
+ case SerializationTypeCode.TaggedObject:
+ info.Type = _provider.GetPrimitiveType(PrimitiveTypeCode.Object);
+ break;
+
+ case SerializationTypeCode.SZArray:
+ if (isElementType)
+ {
+ // jagged arrays are not allowed.
+ throw new BadImageFormatException();
+ }
+
+ var elementInfo = DecodeNamedArgumentType(ref valueReader, isElementType: true);
+ info.ElementType = elementInfo.Type;
+ info.ElementTypeCode = elementInfo.TypeCode;
+ info.Type = _provider.GetSZArrayType(info.ElementType);
+ break;
+
+ case SerializationTypeCode.Enum:
+ var typeName = valueReader.ReadSerializedString();
+ info.Type = _provider.GetTypeFromSerializedName(typeName);
+ info.TypeCode = (SerializationTypeCode)_provider.GetUnderlyingEnumType(info.Type);
+ break;
+
+ default:
+ throw new BadImageFormatException();
+ }
+
+ return info;
+ }
+
+ private CustomAttributeTypedArgument DecodeArgument(ref BlobReader valueReader, ArgumentTypeInfo info)
+ {
+ var outer = info;
+ if (info.TypeCode == SerializationTypeCode.TaggedObject)
+ {
+ info = DecodeNamedArgumentType(ref valueReader);
+ }
+
+ // PERF_TODO: https://github.com/dotnet/corefx/issues/6533
+ // Cache /reuse common arguments to avoid boxing (small integers, true, false).
+ object value;
+ switch (info.TypeCode)
+ {
+ case SerializationTypeCode.Boolean:
+ value = valueReader.ReadBoolean();
+ break;
+
+ case SerializationTypeCode.Byte:
+ value = valueReader.ReadByte();
+ break;
+
+ case SerializationTypeCode.Char:
+ value = valueReader.ReadChar();
+ break;
+
+ case SerializationTypeCode.Double:
+ value = valueReader.ReadDouble();
+ break;
+
+ case SerializationTypeCode.Int16:
+ value = valueReader.ReadInt16();
+ break;
+
+ case SerializationTypeCode.Int32:
+ value = valueReader.ReadInt32();
+ break;
+
+ case SerializationTypeCode.Int64:
+ value = valueReader.ReadInt64();
+ break;
+
+ case SerializationTypeCode.SByte:
+ value = valueReader.ReadSByte();
+ break;
+
+ case SerializationTypeCode.Single:
+ value = valueReader.ReadSingle();
+ break;
+
+ case SerializationTypeCode.UInt16:
+ value = valueReader.ReadUInt16();
+ break;
+
+ case SerializationTypeCode.UInt32:
+ value = valueReader.ReadUInt32();
+ break;
+
+ case SerializationTypeCode.UInt64:
+ value = valueReader.ReadUInt64();
+ break;
+
+ case SerializationTypeCode.String:
+ value = valueReader.ReadSerializedString();
+ break;
+
+ case SerializationTypeCode.Type:
+ var typeName = valueReader.ReadSerializedString();
+ value = _provider.GetTypeFromSerializedName(typeName);
+ break;
+
+ case SerializationTypeCode.SZArray:
+ value = DecodeArrayArgument(ref valueReader, info);
+ break;
+
+ default:
+ throw new BadImageFormatException();
+ }
+
+ if (_provideBoxingTypeInfo && outer.TypeCode == SerializationTypeCode.TaggedObject)
+ {
+ return new CustomAttributeTypedArgument(outer.Type, new CustomAttributeTypedArgument(info.Type, value));
+ }
+
+ return new CustomAttributeTypedArgument(info.Type, value);
+ }
+
+ private ImmutableArray>? DecodeArrayArgument(ref BlobReader blobReader, ArgumentTypeInfo info)
+ {
+ var count = blobReader.ReadInt32();
+ if (count == -1)
+ {
+ return null;
+ }
+
+ if (count == 0)
+ {
+ return ImmutableArray>.Empty;
+ }
+
+ if (count < 0)
+ {
+ throw new BadImageFormatException();
+ }
+
+ var elementInfo = new ArgumentTypeInfo {
+ Type = info.ElementType,
+ TypeCode = info.ElementTypeCode,
+ };
+
+ var array = ImmutableArray.CreateBuilder>(count);
+
+ for (var i = 0; i < count; i++)
+ {
+ array.Add(DecodeArgument(ref blobReader, elementInfo));
+ }
+
+ return array.MoveToImmutable();
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/Dom.cs b/src/ICSharpCode.Decompiler/Metadata/Dom.cs
new file mode 100644
index 0000000..ca7fb19
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/Dom.cs
@@ -0,0 +1,300 @@
+using System;
+using System.Collections.Immutable;
+using System.IO;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ public enum TargetRuntime
+ {
+ Unknown,
+ Net_1_0,
+ Net_1_1,
+ Net_2_0,
+ Net_4_0
+ }
+
+ public enum ResourceType
+ {
+ Linked,
+ Embedded,
+ AssemblyLinked,
+ }
+
+ public abstract class Resource
+ {
+ public virtual ResourceType ResourceType => ResourceType.Embedded;
+ public virtual ManifestResourceAttributes Attributes => ManifestResourceAttributes.Public;
+ public abstract string Name { get; }
+ public abstract Stream TryOpenStream();
+ }
+
+ public class ByteArrayResource : Resource
+ {
+ public override string Name { get; }
+ byte[] data;
+
+ public ByteArrayResource(string name, byte[] data)
+ {
+ this.Name = name ?? throw new ArgumentNullException(nameof(name));
+ this.data = data ?? throw new ArgumentNullException(nameof(data));
+ }
+
+ public override Stream TryOpenStream()
+ {
+ return new MemoryStream(data);
+ }
+ }
+
+ sealed class MetadataResource : Resource
+ {
+ public PEFile Module { get; }
+ public ManifestResourceHandle Handle { get; }
+ public bool IsNil => Handle.IsNil;
+
+ public MetadataResource(PEFile module, ManifestResourceHandle handle)
+ {
+ this.Module = module ?? throw new ArgumentNullException(nameof(module));
+ this.Handle = handle;
+ }
+
+ ManifestResource This() => Module.Metadata.GetManifestResource(Handle);
+
+ public bool Equals(MetadataResource other)
+ {
+ return Module == other.Module && Handle == other.Handle;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is MetadataResource res)
+ return Equals(res);
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return unchecked(982451629 * Module.GetHashCode() + 982451653 * MetadataTokens.GetToken(Handle));
+ }
+
+ public override string Name => Module.Metadata.GetString(This().Name);
+
+ public override ManifestResourceAttributes Attributes => This().Attributes;
+ public bool HasFlag(ManifestResourceAttributes flag) => (Attributes & flag) == flag;
+ public override ResourceType ResourceType => GetResourceType();
+
+ ResourceType GetResourceType()
+ {
+ if (This().Implementation.IsNil)
+ return ResourceType.Embedded;
+ if (This().Implementation.Kind == HandleKind.AssemblyReference)
+ return ResourceType.AssemblyLinked;
+ return ResourceType.Linked;
+ }
+
+ public override unsafe Stream TryOpenStream()
+ {
+ if (ResourceType != ResourceType.Embedded)
+ return null;
+ var headers = Module.Reader.PEHeaders;
+ var resources = headers.CorHeader.ResourcesDirectory;
+ var sectionData = Module.Reader.GetSectionData(resources.RelativeVirtualAddress);
+ if (sectionData.Length == 0)
+ throw new BadImageFormatException("RVA could not be found in any section!");
+ var reader = sectionData.GetReader();
+ reader.Offset += (int)This().Offset;
+ var length = reader.ReadInt32();
+ if (length < 0 || length > reader.RemainingBytes)
+ throw new BadImageFormatException("Resource stream length invalid");
+ return new ResourceMemoryStream(Module.Reader, reader.CurrentPointer, length);
+ }
+ }
+
+ sealed unsafe class ResourceMemoryStream : UnmanagedMemoryStream
+ {
+ readonly PEReader peReader;
+
+ public ResourceMemoryStream(PEReader peReader, byte* data, long length)
+ : base(data, length, length, FileAccess.Read)
+ {
+ // Keep the PEReader alive while the stream in in use.
+ this.peReader = peReader;
+ }
+ }
+
+ public sealed class FullTypeNameSignatureDecoder : ISignatureTypeProvider, ICustomAttributeTypeProvider
+ {
+ readonly MetadataReader metadata;
+
+ public FullTypeNameSignatureDecoder(MetadataReader metadata)
+ {
+ this.metadata = metadata;
+ }
+
+ public FullTypeName GetArrayType(FullTypeName elementType, ArrayShape shape)
+ {
+ return elementType;
+ }
+
+ public FullTypeName GetByReferenceType(FullTypeName elementType)
+ {
+ return elementType;
+ }
+
+ public FullTypeName GetFunctionPointerType(MethodSignature signature)
+ {
+ return default;
+ }
+
+ public FullTypeName GetGenericInstantiation(FullTypeName genericType, ImmutableArray typeArguments)
+ {
+ return genericType;
+ }
+
+ public FullTypeName GetGenericMethodParameter(Unit genericContext, int index)
+ {
+ return default;
+ }
+
+ public FullTypeName GetGenericTypeParameter(Unit genericContext, int index)
+ {
+ return default;
+ }
+
+ public FullTypeName GetModifiedType(FullTypeName modifier, FullTypeName unmodifiedType, bool isRequired)
+ {
+ return unmodifiedType;
+ }
+
+ public FullTypeName GetPinnedType(FullTypeName elementType)
+ {
+ return elementType;
+ }
+
+ public FullTypeName GetPointerType(FullTypeName elementType)
+ {
+ return elementType;
+ }
+
+ public FullTypeName GetPrimitiveType(PrimitiveTypeCode typeCode)
+ {
+ var ktr = KnownTypeReference.Get(typeCode.ToKnownTypeCode());
+ return new TopLevelTypeName(ktr.Namespace, ktr.Name, ktr.TypeParameterCount);
+ }
+
+ public FullTypeName GetSystemType()
+ {
+ return new TopLevelTypeName("System", "Type");
+ }
+
+ public FullTypeName GetSZArrayType(FullTypeName elementType)
+ {
+ return elementType;
+ }
+
+ public FullTypeName GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
+ {
+ return handle.GetFullTypeName(reader);
+ }
+
+ public FullTypeName GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
+ {
+ return handle.GetFullTypeName(reader);
+ }
+
+ public FullTypeName GetTypeFromSerializedName(string name)
+ {
+ return new FullTypeName(name);
+ }
+
+ public FullTypeName GetTypeFromSpecification(MetadataReader reader, Unit genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
+ {
+ return reader.GetTypeSpecification(handle).DecodeSignature(new FullTypeNameSignatureDecoder(metadata), default);
+ }
+
+ public PrimitiveTypeCode GetUnderlyingEnumType(FullTypeName type)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsSystemType(FullTypeName type)
+ {
+ return type.IsKnownType(KnownTypeCode.Type);
+ }
+ }
+
+ public class GenericContext
+ {
+ readonly MetadataReader metadata;
+ readonly TypeDefinitionHandle declaringType;
+ readonly MethodDefinitionHandle method;
+
+ public static readonly GenericContext Empty = new GenericContext();
+
+ private GenericContext() { }
+
+ public GenericContext(MethodDefinitionHandle method, PEFile module)
+ {
+ this.metadata = module.Metadata;
+ this.method = method;
+ this.declaringType = module.Metadata.GetMethodDefinition(method).GetDeclaringType();
+ }
+
+ public GenericContext(MethodDefinitionHandle method, MetadataReader metadata)
+ {
+ this.metadata = metadata;
+ this.method = method;
+ this.declaringType = metadata.GetMethodDefinition(method).GetDeclaringType();
+ }
+
+ public GenericContext(TypeDefinitionHandle declaringType, PEFile module)
+ {
+ this.metadata = module.Metadata;
+ this.declaringType = declaringType;
+ }
+
+ public GenericContext(TypeDefinitionHandle declaringType, MetadataReader metadata)
+ {
+ this.metadata = metadata;
+ this.declaringType = declaringType;
+ }
+
+ public string GetGenericTypeParameterName(int index)
+ {
+ var genericParameter = GetGenericTypeParameterHandleOrNull(index);
+ if (genericParameter.IsNil)
+ return index.ToString();
+ return metadata.GetString(metadata.GetGenericParameter(genericParameter).Name);
+ }
+
+ public string GetGenericMethodTypeParameterName(int index)
+ {
+ var genericParameter = GetGenericMethodTypeParameterHandleOrNull(index);
+ if (genericParameter.IsNil)
+ return index.ToString();
+ return metadata.GetString(metadata.GetGenericParameter(genericParameter).Name);
+ }
+
+ public GenericParameterHandle GetGenericTypeParameterHandleOrNull(int index)
+ {
+ GenericParameterHandleCollection genericParameters;
+ if (declaringType.IsNil || index < 0 || index >= (genericParameters = metadata.GetTypeDefinition(declaringType).GetGenericParameters()).Count)
+ return MetadataTokens.GenericParameterHandle(0);
+ return genericParameters[index];
+ }
+
+ public GenericParameterHandle GetGenericMethodTypeParameterHandleOrNull(int index)
+ {
+ GenericParameterHandleCollection genericParameters;
+ if (method.IsNil || index < 0 || index >= (genericParameters = metadata.GetMethodDefinition(method).GetGenericParameters()).Count)
+ return MetadataTokens.GenericParameterHandle(0);
+ return genericParameters[index];
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs b/src/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs
new file mode 100644
index 0000000..e079077
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs
@@ -0,0 +1,294 @@
+// Copyright (c) 2018 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.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+using ICSharpCode.Decompiler.Util;
+
+using LightJson.Serialization;
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ public class DotNetCorePathFinder
+ {
+ class DotNetCorePackageInfo
+ {
+ public readonly string Name;
+ public readonly string Version;
+ public readonly string Type;
+ public readonly string Path;
+ public readonly string[] RuntimeComponents;
+
+ public DotNetCorePackageInfo(string fullName, string type, string path, string[] runtimeComponents)
+ {
+ var parts = fullName.Split('/');
+ this.Name = parts[0];
+ if (parts.Length > 1)
+ {
+ this.Version = parts[1];
+ }
+ else
+ {
+ this.Version = "";
+ }
+
+ this.Type = type;
+ this.Path = path;
+ this.RuntimeComponents = runtimeComponents ?? Empty.Array;
+ }
+ }
+
+ static readonly string[] LookupPaths = new string[] {
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages")
+ };
+
+ static readonly string[] RuntimePacks = new[] {
+ "Microsoft.NETCore.App",
+ "Microsoft.WindowsDesktop.App",
+ "Microsoft.AspNetCore.App",
+ "Microsoft.AspNetCore.All"
+ };
+
+ readonly DotNetCorePackageInfo[] packages;
+ readonly List searchPaths = new List();
+ readonly List packageBasePaths = new List();
+ readonly Version targetFrameworkVersion;
+ readonly string dotnetBasePath = FindDotNetExeDirectory();
+
+ public DotNetCorePathFinder(TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion)
+ {
+ this.targetFrameworkVersion = targetFrameworkVersion;
+
+ if (targetFramework == TargetFrameworkIdentifier.NETStandard)
+ {
+ // .NET Standard 2.1 is implemented by .NET Core 3.0 or higher
+ if (targetFrameworkVersion.Major == 2 && targetFrameworkVersion.Minor == 1)
+ {
+ this.targetFrameworkVersion = new Version(3, 0, 0);
+ }
+ }
+ }
+
+ public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion, ReferenceLoadInfo loadInfo = null)
+ : this(targetFramework, targetFrameworkVersion)
+ {
+ var assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName);
+ var basePath = Path.GetDirectoryName(parentAssemblyFileName);
+
+ searchPaths.Add(basePath);
+
+ var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json");
+ if (File.Exists(depsJsonFileName))
+ {
+ packages = LoadPackageInfos(depsJsonFileName, targetFrameworkIdString).ToArray();
+
+ foreach (var path in LookupPaths)
+ {
+ foreach (var p in packages)
+ {
+ foreach (var item in p.RuntimeComponents)
+ {
+ var itemPath = Path.GetDirectoryName(item);
+ var fullPath = Path.Combine(path, p.Name, p.Version, itemPath).ToLowerInvariant();
+ if (Directory.Exists(fullPath))
+ packageBasePaths.Add(fullPath);
+ }
+ }
+ }
+ }
+ else
+ {
+ loadInfo?.AddMessage(assemblyName, MessageKind.Warning, $"{assemblyName}.deps.json could not be found!");
+ }
+ }
+
+ public void AddSearchDirectory(string path)
+ {
+ this.searchPaths.Add(path);
+ }
+
+ public void RemoveSearchDirectory(string path)
+ {
+ this.searchPaths.Remove(path);
+ }
+
+ public string TryResolveDotNetCore(IAssemblyReference name)
+ {
+ foreach (var basePath in searchPaths.Concat(packageBasePaths))
+ {
+ if (File.Exists(Path.Combine(basePath, name.Name + ".dll")))
+ {
+ return Path.Combine(basePath, name.Name + ".dll");
+ }
+ else if (File.Exists(Path.Combine(basePath, name.Name + ".exe")))
+ {
+ return Path.Combine(basePath, name.Name + ".exe");
+ }
+ }
+
+ return TryResolveDotNetCoreShared(name, out _);
+ }
+
+ internal string GetReferenceAssemblyPath(string targetFramework)
+ {
+ var (tfi, version) = UniversalAssemblyResolver.ParseTargetFramework(targetFramework);
+ string identifier, identifierExt;
+ switch (tfi)
+ {
+ case TargetFrameworkIdentifier.NETCoreApp:
+ identifier = "Microsoft.NETCore.App";
+ identifierExt = "netcoreapp" + version.Major + "." + version.Minor;
+ break;
+ case TargetFrameworkIdentifier.NETStandard:
+ identifier = "NETStandard.Library";
+ identifierExt = "netstandard" + version.Major + "." + version.Minor;
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ return Path.Combine(dotnetBasePath, "packs", identifier + ".Ref", version.ToString(), "ref", identifierExt);
+ }
+
+ static IEnumerable LoadPackageInfos(string depsJsonFileName, string targetFramework)
+ {
+ var dependencies = JsonReader.Parse(File.ReadAllText(depsJsonFileName));
+ var runtimeInfos = dependencies["targets"][targetFramework].AsJsonObject;
+ var libraries = dependencies["libraries"].AsJsonObject;
+ if (runtimeInfos == null || libraries == null)
+ yield break;
+ foreach (var library in libraries)
+ {
+ var type = library.Value["type"].AsString;
+ var path = library.Value["path"].AsString;
+ var runtimeInfo = runtimeInfos[library.Key].AsJsonObject?["runtime"].AsJsonObject;
+ var components = new string[runtimeInfo?.Count ?? 0];
+ if (runtimeInfo != null)
+ {
+ var i = 0;
+ foreach (var component in runtimeInfo)
+ {
+ components[i] = component.Key;
+ i++;
+ }
+ }
+ yield return new DotNetCorePackageInfo(library.Key, type, path, components);
+ }
+ }
+
+ public string TryResolveDotNetCoreShared(IAssemblyReference name, out string runtimePack)
+ {
+ if (dotnetBasePath == null)
+ {
+ runtimePack = null;
+ return null;
+ }
+ foreach (var pack in RuntimePacks)
+ {
+ runtimePack = pack;
+ var basePath = Path.Combine(dotnetBasePath, "shared", pack);
+ if (!Directory.Exists(basePath))
+ continue;
+ var closestVersion = GetClosestVersionFolder(basePath, targetFrameworkVersion);
+ if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".dll")))
+ {
+ return Path.Combine(basePath, closestVersion, name.Name + ".dll");
+ }
+ else if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".exe")))
+ {
+ return Path.Combine(basePath, closestVersion, name.Name + ".exe");
+ }
+ }
+ runtimePack = null;
+ return null;
+ }
+
+ static string GetClosestVersionFolder(string basePath, Version version)
+ {
+ var foundVersions = new DirectoryInfo(basePath).GetDirectories()
+ .Select(d => ConvertToVersion(d.Name))
+ .Where(v => v.version != null);
+ foreach (var folder in foundVersions.OrderBy(v => v.Item1))
+ {
+ if (folder.version >= version)
+ return folder.directoryName;
+ }
+ return version.ToString();
+ }
+
+ internal static (Version version, string directoryName) ConvertToVersion(string name)
+ {
+ string RemoveTrailingVersionInfo()
+ {
+ var shortName = name;
+ var dashIndex = shortName.IndexOf('-');
+ if (dashIndex > 0)
+ {
+ shortName = shortName.Remove(dashIndex);
+ }
+ return shortName;
+ }
+
+ try
+ {
+ return (new Version(RemoveTrailingVersionInfo()), name);
+ }
+ catch (Exception ex)
+ {
+ Trace.TraceWarning(ex.ToString());
+ return (null, null);
+ }
+ }
+
+ public static string FindDotNetExeDirectory()
+ {
+ var dotnetExeName = (Environment.OSVersion.Platform == PlatformID.Unix) ? "dotnet" : "dotnet.exe";
+ foreach (var item in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
+ {
+ try
+ {
+ var fileName = Path.Combine(item, dotnetExeName);
+ if (!File.Exists(fileName))
+ continue;
+ if (Environment.OSVersion.Platform == PlatformID.Unix)
+ {
+ if ((new FileInfo(fileName).Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
+ {
+ var sb = new StringBuilder();
+ realpath(fileName, sb);
+ fileName = sb.ToString();
+ if (!File.Exists(fileName))
+ continue;
+ }
+ }
+ return Path.GetDirectoryName(fileName);
+ }
+ catch (ArgumentException) { }
+ }
+ return null;
+ }
+
+ [DllImport("libc")]
+ static extern void realpath(string path, StringBuilder resolvedPath);
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs b/src/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs
new file mode 100644
index 0000000..aea9fea
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs
@@ -0,0 +1,246 @@
+// Copyright (c) 2018 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.Linq;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using System.Text.RegularExpressions;
+
+using ICSharpCode.Decompiler.TypeSystem;
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ public static class DotNetCorePathFinderExtensions
+ {
+ static readonly string PathPattern =
+ @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?.NETFramework)[/\\]v(?[^/\\]+)[/\\])" +
+ @"|((?Microsoft\.NET)[/\\]assembly[/\\]GAC_(MSIL|32|64)[/\\])" +
+ @"|((?Microsoft\.NET)[/\\]Framework(64)?[/\\](?[^/\\]+)[/\\])" +
+ @"|(NuGetFallbackFolder[/\\](?[^/\\]+)\\(?[^/\\]+)([/\\].*)?[/\\]ref[/\\])" +
+ @"|(shared[/\\](?[^/\\]+)\\(?[^/\\]+)([/\\].*)?[/\\])" +
+ @"|(packs[/\\](?[^/\\]+)\\(?[^/\\]+)\\ref([/\\].*)?[/\\])";
+
+ static readonly string RefPathPattern =
+ @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?.NETFramework)[/\\]v(?[^/\\]+)[/\\])" +
+ @"|(NuGetFallbackFolder[/\\](?[^/\\]+)\\(?[^/\\]+)([/\\].*)?[/\\]ref[/\\])" +
+ @"|(packs[/\\](?[^/\\]+)\\(?[^/\\]+)\\ref([/\\].*)?[/\\])";
+
+ public static string DetectTargetFrameworkId(this PEFile assembly)
+ {
+ return DetectTargetFrameworkId(assembly.Reader, assembly.FileName);
+ }
+
+ public static string DetectTargetFrameworkId(this PEReader assembly, string assemblyPath = null)
+ {
+ if (assembly == null)
+ throw new ArgumentNullException(nameof(assembly));
+
+ const string TargetFrameworkAttributeName = "System.Runtime.Versioning.TargetFrameworkAttribute";
+ var reader = assembly.GetMetadataReader();
+
+ foreach (var h in reader.GetCustomAttributes(Handle.AssemblyDefinition))
+ {
+ try
+ {
+ var attribute = reader.GetCustomAttribute(h);
+ if (attribute.GetAttributeType(reader).GetFullTypeName(reader).ToString() != TargetFrameworkAttributeName)
+ continue;
+ var blobReader = reader.GetBlobReader(attribute.Value);
+ if (blobReader.ReadUInt16() == 0x0001)
+ {
+ return blobReader.ReadSerializedString();
+ }
+ }
+ catch (BadImageFormatException)
+ {
+ // ignore malformed attributes
+ }
+ }
+
+ foreach (var h in reader.AssemblyReferences)
+ {
+ try
+ {
+ var r = reader.GetAssemblyReference(h);
+ if (r.PublicKeyOrToken.IsNil)
+ continue;
+ string version;
+ switch (reader.GetString(r.Name))
+ {
+ case "netstandard":
+ version = r.Version.ToString(3);
+ return $".NETStandard,Version=v{version}";
+ case "System.Runtime":
+ // System.Runtime.dll uses the following scheme:
+ // 4.2.0 => .NET Core 2.0
+ // 4.2.1 => .NET Core 2.1 / 3.0
+ // 4.2.2 => .NET Core 3.1
+ if (r.Version >= new Version(4, 2, 0))
+ {
+ version = "2.0";
+ if (r.Version >= new Version(4, 2, 1))
+ {
+ version = "3.0";
+ }
+ if (r.Version >= new Version(4, 2, 2))
+ {
+ version = "3.1";
+ }
+ return $".NETCoreApp,Version=v{version}";
+ }
+ else
+ {
+ continue;
+ }
+ case "mscorlib":
+ version = r.Version.ToString(2);
+ return $".NETFramework,Version=v{version}";
+ }
+ }
+ catch (BadImageFormatException)
+ {
+ // ignore malformed references
+ }
+ }
+
+ // Optionally try to detect target version through assembly path as a fallback (use case: reference assemblies)
+ if (assemblyPath != null)
+ {
+ /*
+ * Detected path patterns (examples):
+ *
+ * - .NETFramework -> C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
+ * - .NETCore -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Console.dll
+ * - .NETStandard -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll
+ */
+ var pathMatch = Regex.Match(assemblyPath, PathPattern,
+ RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+ if (pathMatch.Success)
+ {
+ var type = pathMatch.Groups["type"].Value;
+ var version = pathMatch.Groups["version"].Value;
+ if (string.IsNullOrEmpty(version))
+ version = reader.MetadataVersion;
+
+ if (type == "Microsoft.NET" || type == ".NETFramework")
+ {
+ return $".NETFramework,Version=v{version.TrimStart('v').Substring(0, 3)}";
+ }
+ else if (type.IndexOf("netcore", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ return $".NETCoreApp,Version=v{version}";
+ }
+ else if (type.IndexOf("netstandard", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ return $".NETStandard,Version=v{version}";
+ }
+ }
+ else
+ {
+ return $".NETFramework,Version={reader.MetadataVersion.Substring(0, 4)}";
+ }
+ }
+
+ return string.Empty;
+ }
+
+ public static bool IsReferenceAssembly(this PEFile assembly)
+ {
+ return IsReferenceAssembly(assembly.Reader, assembly.FileName);
+ }
+
+ public static bool IsReferenceAssembly(this PEReader assembly, string assemblyPath)
+ {
+ if (assembly == null)
+ throw new ArgumentNullException(nameof(assembly));
+
+ var metadata = assembly.GetMetadataReader();
+ if (metadata.GetCustomAttributes(Handle.AssemblyDefinition).HasKnownAttribute(metadata, KnownAttribute.ReferenceAssembly))
+ return true;
+
+ // Try to detect reference assembly through specific path pattern
+ var refPathMatch = Regex.Match(assemblyPath, RefPathPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
+ return refPathMatch.Success;
+ }
+ }
+
+ public class ReferenceLoadInfo
+ {
+ readonly Dictionary loadedAssemblyReferences = new Dictionary();
+
+ public void AddMessage(string fullName, MessageKind kind, string message)
+ {
+ lock (loadedAssemblyReferences)
+ {
+ if (!loadedAssemblyReferences.TryGetValue(fullName, out var referenceInfo))
+ {
+ referenceInfo = new UnresolvedAssemblyNameReference(fullName);
+ loadedAssemblyReferences.Add(fullName, referenceInfo);
+ }
+ referenceInfo.Messages.Add((kind, message));
+ }
+ }
+
+ public void AddMessageOnce(string fullName, MessageKind kind, string message)
+ {
+ lock (loadedAssemblyReferences)
+ {
+ if (!loadedAssemblyReferences.TryGetValue(fullName, out var referenceInfo))
+ {
+ referenceInfo = new UnresolvedAssemblyNameReference(fullName);
+ loadedAssemblyReferences.Add(fullName, referenceInfo);
+ referenceInfo.Messages.Add((kind, message));
+ }
+ else
+ {
+ var lastMsg = referenceInfo.Messages.LastOrDefault();
+ if (kind != lastMsg.Item1 && message != lastMsg.Item2)
+ referenceInfo.Messages.Add((kind, message));
+ }
+ }
+ }
+
+ public bool TryGetInfo(string fullName, out UnresolvedAssemblyNameReference info)
+ {
+ lock (loadedAssemblyReferences)
+ {
+ return loadedAssemblyReferences.TryGetValue(fullName, out info);
+ }
+ }
+
+ public IReadOnlyList Entries {
+ get {
+ lock (loadedAssemblyReferences)
+ {
+ return loadedAssemblyReferences.Values.ToList();
+ }
+ }
+ }
+
+ public bool HasErrors {
+ get {
+ lock (loadedAssemblyReferences)
+ {
+ return loadedAssemblyReferences.Any(i => i.Value.HasErrors);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/EnumUnderlyingTypeResolveException.cs b/src/ICSharpCode.Decompiler/Metadata/EnumUnderlyingTypeResolveException.cs
new file mode 100644
index 0000000..b7be7e9
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/EnumUnderlyingTypeResolveException.cs
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 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.Runtime.Serialization;
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ [Serializable]
+ public class EnumUnderlyingTypeResolveException : Exception
+ {
+ public EnumUnderlyingTypeResolveException() { }
+ public EnumUnderlyingTypeResolveException(string message) : base(message) { }
+ public EnumUnderlyingTypeResolveException(string message, Exception inner) : base(message, inner) { }
+ protected EnumUnderlyingTypeResolveException(
+ SerializationInfo info,
+ StreamingContext context) : base(info, context) { }
+ }
+
+ [Serializable]
+ public class PEFileNotSupportedException : Exception
+ {
+ public PEFileNotSupportedException() { }
+ public PEFileNotSupportedException(string message) : base(message) { }
+ public PEFileNotSupportedException(string message, Exception inner) : base(message, inner) { }
+ protected PEFileNotSupportedException(
+ SerializationInfo info,
+ StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/ILOpCodes.cs b/src/ICSharpCode.Decompiler/Metadata/ILOpCodes.cs
new file mode 100644
index 0000000..6a818d4
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/ILOpCodes.cs
@@ -0,0 +1,29 @@
+// Copyright (c) 2014 Daniel Grunwald
+//
+// 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.
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+
+ static partial class ILOpCodeExtensions
+ {
+ // We use a byte array instead of an enum array because it can be initialized more efficiently
+ static readonly byte[] operandTypes = { (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.ShortVariable, (byte)OperandType.ShortVariable, (byte)OperandType.ShortVariable, (byte)OperandType.ShortVariable, (byte)OperandType.ShortVariable, (byte)OperandType.ShortVariable, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.ShortI, (byte)OperandType.I, (byte)OperandType.I8, (byte)OperandType.ShortR, (byte)OperandType.R, 255, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.Method, (byte)OperandType.Method, (byte)OperandType.Sig, (byte)OperandType.None, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.BrTarget, (byte)OperandType.Switch, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.Method, (byte)OperandType.Type, (byte)OperandType.Type, (byte)OperandType.String, (byte)OperandType.Method, (byte)OperandType.Type, (byte)OperandType.Type, (byte)OperandType.None, 255, 255, (byte)OperandType.Type, (byte)OperandType.None, (byte)OperandType.Field, (byte)OperandType.Field, (byte)OperandType.Field, (byte)OperandType.Field, (byte)OperandType.Field, (byte)OperandType.Field, (byte)OperandType.Type, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.Type, (byte)OperandType.Type, (byte)OperandType.None, (byte)OperandType.Type, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.Type, (byte)OperandType.Type, (byte)OperandType.Type, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, 255, 255, 255, 255, 255, 255, 255, (byte)OperandType.Type, (byte)OperandType.None, 255, 255, (byte)OperandType.Type, 255, 255, 255, 255, 255, 255, 255, 255, 255, (byte)OperandType.Tok, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.BrTarget, (byte)OperandType.ShortBrTarget, (byte)OperandType.None, (byte)OperandType.None, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.Method, (byte)OperandType.Method, 255, (byte)OperandType.Variable, (byte)OperandType.Variable, (byte)OperandType.Variable, (byte)OperandType.Variable, (byte)OperandType.Variable, (byte)OperandType.Variable, (byte)OperandType.None, 255, (byte)OperandType.None, (byte)OperandType.ShortI, (byte)OperandType.None, (byte)OperandType.None, (byte)OperandType.Type, (byte)OperandType.Type, (byte)OperandType.None, (byte)OperandType.None, 255, (byte)OperandType.None, 255, (byte)OperandType.Type, (byte)OperandType.None, (byte)OperandType.None, };
+
+ static readonly string[] operandNames = { "nop", "break", "ldarg.0", "ldarg.1", "ldarg.2", "ldarg.3", "ldloc.0", "ldloc.1", "ldloc.2", "ldloc.3", "stloc.0", "stloc.1", "stloc.2", "stloc.3", "ldarg.s", "ldarga.s", "starg.s", "ldloc.s", "ldloca.s", "stloc.s", "ldnull", "ldc.i4.m1", "ldc.i4.0", "ldc.i4.1", "ldc.i4.2", "ldc.i4.3", "ldc.i4.4", "ldc.i4.5", "ldc.i4.6", "ldc.i4.7", "ldc.i4.8", "ldc.i4.s", "ldc.i4", "ldc.i8", "ldc.r4", "ldc.r8", "", "dup", "pop", "jmp", "call", "calli", "ret", "br.s", "brfalse.s", "brtrue.s", "beq.s", "bge.s", "bgt.s", "ble.s", "blt.s", "bne.un.s", "bge.un.s", "bgt.un.s", "ble.un.s", "blt.un.s", "br", "brfalse", "brtrue", "beq", "bge", "bgt", "ble", "blt", "bne.un", "bge.un", "bgt.un", "ble.un", "blt.un", "switch", "ldind.i1", "ldind.u1", "ldind.i2", "ldind.u2", "ldind.i4", "ldind.u4", "ldind.i8", "ldind.i", "ldind.r4", "ldind.r8", "ldind.ref", "stind.ref", "stind.i1", "stind.i2", "stind.i4", "stind.i8", "stind.r4", "stind.r8", "add", "sub", "mul", "div", "div.un", "rem", "rem.un", "and", "or", "xor", "shl", "shr", "shr.un", "neg", "not", "conv.i1", "conv.i2", "conv.i4", "conv.i8", "conv.r4", "conv.r8", "conv.u4", "conv.u8", "callvirt", "cpobj", "ldobj", "ldstr", "newobj", "castclass", "isinst", "conv.r.un", "", "", "unbox", "throw", "ldfld", "ldflda", "stfld", "ldsfld", "ldsflda", "stsfld", "stobj", "conv.ovf.i1.un", "conv.ovf.i2.un", "conv.ovf.i4.un", "conv.ovf.i8.un", "conv.ovf.u1.un", "conv.ovf.u2.un", "conv.ovf.u4.un", "conv.ovf.u8.un", "conv.ovf.i.un", "conv.ovf.u.un", "box", "newarr", "ldlen", "ldelema", "ldelem.i1", "ldelem.u1", "ldelem.i2", "ldelem.u2", "ldelem.i4", "ldelem.u4", "ldelem.i8", "ldelem.i", "ldelem.r4", "ldelem.r8", "ldelem.ref", "stelem.i", "stelem.i1", "stelem.i2", "stelem.i4", "stelem.i8", "stelem.r4", "stelem.r8", "stelem.ref", "ldelem", "stelem", "unbox.any", "", "", "", "", "", "", "", "", "", "", "", "", "", "conv.ovf.i1", "conv.ovf.u1", "conv.ovf.i2", "conv.ovf.u2", "conv.ovf.i4", "conv.ovf.u4", "conv.ovf.i8", "conv.ovf.u8", "", "", "", "", "", "", "", "refanyval", "ckfinite", "", "", "mkrefany", "", "", "", "", "", "", "", "", "", "ldtoken", "conv.u2", "conv.u1", "conv.i", "conv.ovf.i", "conv.ovf.u", "add.ovf", "add.ovf.un", "mul.ovf", "mul.ovf.un", "sub.ovf", "sub.ovf.un", "endfinally", "leave", "leave.s", "stind.i", "conv.u", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "prefix7", "prefix6", "prefix5", "prefix4", "prefix3", "prefix2", "prefix1", "prefixref", "arglist", "ceq", "cgt", "cgt.un", "clt", "clt.un", "ldftn", "ldvirtftn", "", "ldarg", "ldarga", "starg", "ldloc", "ldloca", "stloc", "localloc", "", "endfilter", "unaligned.", "volatile.", "tail.", "initobj", "constrained.", "cpblk", "initblk", "", "rethrow", "", "sizeof", "refanytype", "readonly.", };
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/ILOpCodes.tt b/src/ICSharpCode.Decompiler/Metadata/ILOpCodes.tt
new file mode 100644
index 0000000..45488d6
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/ILOpCodes.tt
@@ -0,0 +1,64 @@
+// Copyright (c) 2014 Daniel Grunwald
+//
+// 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.
+
+<#@ template debug="false" hostspecific="false" language="C#" #>
+<#@ assembly name="System.Core" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Text" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ import namespace="System.Reflection.Metadata" #>
+<#@ import namespace="System.Reflection.Emit" #>
+<#@ output extension=".cs" #>
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+<#
+ var operandTypes = Enumerable.Repeat((OperandType)0xff, 0x11f).ToArray();
+ var operandNames = new string[0x11f];
+#>
+namespace ICSharpCode.Decompiler.Metadata
+{
+<#
+ foreach (var field in typeof(OpCodes).GetFields()) {
+ var opCode = (OpCode)field.GetValue(null);
+ ushort index = (ushort)(((opCode.Value & 0x200) >> 1) | (opCode.Value & 0xff));
+ operandTypes[index] = opCode.OperandType;
+ operandNames[index] = opCode.Name;
+ } #>
+
+ static partial class ILOpCodeExtensions
+ {
+ // We use a byte array instead of an enum array because it can be initialized more efficiently
+ static readonly byte[] operandTypes = { <#
+ foreach (var operandType in operandTypes) {
+ if ((byte)operandType == 255) {
+ Write("255, ");
+ } else {
+ string operandTypeName = operandType.ToString().Replace("Inline", "").Replace("Var", "Variable");
+ Write("(byte)OperandType." + operandTypeName + ", ");
+ }
+ }
+ #> };
+
+ static readonly string[] operandNames = { <#
+ foreach (var operandName in operandNames) {
+ Write("\"" + operandName + "\", ");
+ }
+ #> };
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonArray.cs b/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonArray.cs
new file mode 100644
index 0000000..df1c02c
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonArray.cs
@@ -0,0 +1,187 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace LightJson
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+
+ ///
+ /// Represents an ordered collection of JsonValues.
+ ///
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(JsonArrayDebugView))]
+ internal sealed class JsonArray : IEnumerable
+ {
+ private IList items;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public JsonArray()
+ {
+ this.items = new List();
+ }
+
+ ///
+ /// Initializes a new instance of the class, adding the given values to the collection.
+ ///
+ /// The values to be added to this collection.
+ public JsonArray(params JsonValue[] values)
+ : this()
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ foreach (var value in values)
+ {
+ this.items.Add(value);
+ }
+ }
+
+ ///
+ /// Gets the number of values in this collection.
+ ///
+ /// The number of values in this collection.
+ public int Count {
+ get {
+ return this.items.Count;
+ }
+ }
+
+ ///
+ /// Gets or sets the value at the given index.
+ ///
+ /// The zero-based index of the value to get or set.
+ ///
+ /// The getter will return JsonValue.Null if the given index is out of range.
+ ///
+ public JsonValue this[int index] {
+ get {
+ if (index >= 0 && index < this.items.Count)
+ {
+ return this.items[index];
+ }
+ else
+ {
+ return JsonValue.Null;
+ }
+ }
+
+ set {
+ this.items[index] = value;
+ }
+ }
+
+ ///
+ /// Adds the given value to this collection.
+ ///
+ /// The value to be added.
+ /// Returns this collection.
+ public JsonArray Add(JsonValue value)
+ {
+ this.items.Add(value);
+ return this;
+ }
+
+ ///
+ /// Inserts the given value at the given index in this collection.
+ ///
+ /// The index where the given value will be inserted.
+ /// The value to be inserted into this collection.
+ /// Returns this collection.
+ public JsonArray Insert(int index, JsonValue value)
+ {
+ this.items.Insert(index, value);
+ return this;
+ }
+
+ ///
+ /// Removes the value at the given index.
+ ///
+ /// The index of the value to be removed.
+ /// Return this collection.
+ public JsonArray Remove(int index)
+ {
+ this.items.RemoveAt(index);
+ return this;
+ }
+
+ ///
+ /// Clears the contents of this collection.
+ ///
+ /// Returns this collection.
+ public JsonArray Clear()
+ {
+ this.items.Clear();
+ return this;
+ }
+
+ ///
+ /// Determines whether the given item is in the JsonArray.
+ ///
+ /// The item to locate in the JsonArray.
+ /// Returns true if the item is found; otherwise, false.
+ public bool Contains(JsonValue item)
+ {
+ return this.items.Contains(item);
+ }
+
+ ///
+ /// Determines the index of the given item in this JsonArray.
+ ///
+ /// The item to locate in this JsonArray.
+ /// The index of the item, if found. Otherwise, returns -1.
+ public int IndexOf(JsonValue item)
+ {
+ return this.items.IndexOf(item);
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// The enumerator that iterates through the collection.
+ public IEnumerator GetEnumerator()
+ {
+ return this.items.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// The enumerator that iterates through the collection.
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ [ExcludeFromCodeCoverage]
+ private class JsonArrayDebugView
+ {
+ private JsonArray jsonArray;
+
+ public JsonArrayDebugView(JsonArray jsonArray)
+ {
+ this.jsonArray = jsonArray;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public JsonValue[] Items {
+ get {
+ var items = new JsonValue[this.jsonArray.Count];
+
+ for (var i = 0; i < this.jsonArray.Count; i += 1)
+ {
+ items[i] = this.jsonArray[i];
+ }
+
+ return items;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonObject.cs b/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonObject.cs
new file mode 100644
index 0000000..ddda8a1
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonObject.cs
@@ -0,0 +1,252 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace LightJson
+{
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+
+ ///
+ /// Represents a key-value pair collection of JsonValue objects.
+ ///
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(JsonObjectDebugView))]
+ internal sealed class JsonObject : IEnumerable>, IEnumerable
+ {
+ private IDictionary properties;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public JsonObject()
+ {
+ this.properties = new Dictionary();
+ }
+
+ ///
+ /// Gets the number of properties in this JsonObject.
+ ///
+ /// The number of properties in this JsonObject.
+ public int Count {
+ get {
+ return this.properties.Count;
+ }
+ }
+
+ ///
+ /// Gets or sets the property with the given key.
+ ///
+ /// The key of the property to get or set.
+ ///
+ /// The getter will return JsonValue.Null if the given key is not associated with any value.
+ ///
+ public JsonValue this[string key] {
+ get {
+ JsonValue value;
+
+ if (this.properties.TryGetValue(key, out value))
+ {
+ return value;
+ }
+ else
+ {
+ return JsonValue.Null;
+ }
+ }
+
+ set {
+ this.properties[key] = value;
+ }
+ }
+
+ ///
+ /// Adds a key with a null value to this collection.
+ ///
+ /// The key of the property to be added.
+ /// Returns this JsonObject.
+ /// The that was added.
+ public JsonObject Add(string key)
+ {
+ return this.Add(key, JsonValue.Null);
+ }
+
+ ///
+ /// Adds a value associated with a key to this collection.
+ ///
+ /// The key of the property to be added.
+ /// The value of the property to be added.
+ /// Returns this JsonObject.
+ public JsonObject Add(string key, JsonValue value)
+ {
+ this.properties.Add(key, value);
+ return this;
+ }
+
+ ///
+ /// Removes a property with the given key.
+ ///
+ /// The key of the property to be removed.
+ ///
+ /// Returns true if the given key is found and removed; otherwise, false.
+ ///
+ public bool Remove(string key)
+ {
+ return this.properties.Remove(key);
+ }
+
+ ///
+ /// Clears the contents of this collection.
+ ///
+ /// Returns this JsonObject.
+ public JsonObject Clear()
+ {
+ this.properties.Clear();
+ return this;
+ }
+
+ ///
+ /// Changes the key of one of the items in the collection.
+ ///
+ ///
+ /// This method has no effects if the oldKey does not exists.
+ /// If the newKey already exists, the value will be overwritten.
+ ///
+ /// The name of the key to be changed.
+ /// The new name of the key.
+ /// Returns this JsonObject.
+ public JsonObject Rename(string oldKey, string newKey)
+ {
+ if (oldKey == newKey)
+ {
+ // Renaming to the same name just does nothing
+ return this;
+ }
+
+ JsonValue value;
+
+ if (this.properties.TryGetValue(oldKey, out value))
+ {
+ this[newKey] = value;
+ this.Remove(oldKey);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Determines whether this collection contains an item assosiated with the given key.
+ ///
+ /// The key to locate in this collection.
+ /// Returns true if the key is found; otherwise, false.
+ public bool ContainsKey(string key)
+ {
+ return this.properties.ContainsKey(key);
+ }
+
+ ///
+ /// Determines whether this collection contains the given JsonValue.
+ ///
+ /// The value to locate in this collection.
+ /// Returns true if the value is found; otherwise, false.
+ public bool Contains(JsonValue value)
+ {
+ return this.properties.Values.Contains(value);
+ }
+
+ ///
+ /// Returns an enumerator that iterates through this collection.
+ ///
+ /// The enumerator that iterates through this collection.
+ public IEnumerator> GetEnumerator()
+ {
+ return this.properties.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through this collection.
+ ///
+ /// The enumerator that iterates through this collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.properties.Values.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through this collection.
+ ///
+ /// The enumerator that iterates through this collection.
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ [ExcludeFromCodeCoverage]
+ private class JsonObjectDebugView
+ {
+ private JsonObject jsonObject;
+
+ public JsonObjectDebugView(JsonObject jsonObject)
+ {
+ this.jsonObject = jsonObject;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public KeyValuePair[] Keys {
+ get {
+ var keys = new KeyValuePair[this.jsonObject.Count];
+
+ var i = 0;
+ foreach (var property in this.jsonObject)
+ {
+ keys[i] = new KeyValuePair(property.Key, property.Value);
+ i += 1;
+ }
+
+ return keys;
+ }
+ }
+
+ [DebuggerDisplay("{value.ToString(),nq}", Name = "{key}", Type = "JsonValue({Type})")]
+ public class KeyValuePair
+ {
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private string key;
+
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private JsonValue value;
+
+ public KeyValuePair(string key, JsonValue value)
+ {
+ this.key = key;
+ this.value = value;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public object View {
+ get {
+ if (this.value.IsJsonObject)
+ {
+ return (JsonObject)this.value;
+ }
+ else if (this.value.IsJsonArray)
+ {
+ return (JsonArray)this.value;
+ }
+ else
+ {
+ return this.value;
+ }
+ }
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private JsonValueType Type {
+ get {
+ return this.value.Type;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonValue.cs b/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonValue.cs
new file mode 100644
index 0000000..c79ac34
--- /dev/null
+++ b/src/ICSharpCode.Decompiler/Metadata/LightJson/JsonValue.cs
@@ -0,0 +1,862 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace LightJson
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Globalization;
+
+ using LightJson.Serialization;
+
+ ///
+ /// A wrapper object that contains a valid JSON value.
+ ///
+ [DebuggerDisplay("{ToString(),nq}", Type = "JsonValue({Type})")]
+ [DebuggerTypeProxy(typeof(JsonValueDebugView))]
+ internal struct JsonValue
+ {
+ ///
+ /// Represents a null JsonValue.
+ ///
+ public static readonly JsonValue Null = new JsonValue(JsonValueType.Null, default(double), null);
+
+ private readonly JsonValueType type;
+ private readonly object reference;
+ private readonly double value;
+
+ ///
+ /// Initializes a new instance of the struct, representing a Boolean value.
+ ///
+ /// The value to be wrapped.
+ public JsonValue(bool? value)
+ {
+ if (value.HasValue)
+ {
+ this.reference = null;
+
+ this.type = JsonValueType.Boolean;
+
+ this.value = value.Value ? 1 : 0;
+ }
+ else
+ {
+ this = JsonValue.Null;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct, representing a Number value.
+ ///
+ /// The value to be wrapped.
+ public JsonValue(double? value)
+ {
+ if (value.HasValue)
+ {
+ this.reference = null;
+
+ this.type = JsonValueType.Number;
+
+ this.value = value.Value;
+ }
+ else
+ {
+ this = JsonValue.Null;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct, representing a String value.
+ ///
+ /// The value to be wrapped.
+ public JsonValue(string value)
+ {
+ if (value != null)
+ {
+ this.value = default(double);
+
+ this.type = JsonValueType.String;
+
+ this.reference = value;
+ }
+ else
+ {
+ this = JsonValue.Null;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct, representing a JsonObject.
+ ///
+ /// The value to be wrapped.
+ public JsonValue(JsonObject value)
+ {
+ if (value != null)
+ {
+ this.value = default(double);
+
+ this.type = JsonValueType.Object;
+
+ this.reference = value;
+ }
+ else
+ {
+ this = JsonValue.Null;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct, representing a Array reference value.
+ ///
+ /// The value to be wrapped.
+ public JsonValue(JsonArray value)
+ {
+ if (value != null)
+ {
+ this.value = default(double);
+
+ this.type = JsonValueType.Array;
+
+ this.reference = value;
+ }
+ else
+ {
+ this = JsonValue.Null;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The Json type of the JsonValue.
+ ///
+ /// The internal value of the JsonValue.
+ /// This is used when the Json type is Number or Boolean.
+ ///
+ ///
+ /// The internal value reference of the JsonValue.
+ /// This value is used when the Json type is String, JsonObject, or JsonArray.
+ ///
+ private JsonValue(JsonValueType type, double value, object reference)
+ {
+ this.type = type;
+ this.value = value;
+ this.reference = reference;
+ }
+
+ ///
+ /// Gets the type of this JsonValue.
+ ///
+ /// The type of this JsonValue.
+ public JsonValueType Type {
+ get {
+ return this.type;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue is Null.
+ ///
+ /// A value indicating whether this JsonValue is Null.
+ public bool IsNull {
+ get {
+ return this.Type == JsonValueType.Null;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue is a Boolean.
+ ///
+ /// A value indicating whether this JsonValue is a Boolean.
+ public bool IsBoolean {
+ get {
+ return this.Type == JsonValueType.Boolean;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue is an Integer.
+ ///
+ /// A value indicating whether this JsonValue is an Integer.
+ public bool IsInteger {
+ get {
+ if (!this.IsNumber)
+ {
+ return false;
+ }
+
+ var value = this.value;
+ return unchecked((int)value) == value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue is a Number.
+ ///
+ /// A value indicating whether this JsonValue is a Number.
+ public bool IsNumber {
+ get {
+ return this.Type == JsonValueType.Number;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue is a String.
+ ///
+ /// A value indicating whether this JsonValue is a String.
+ public bool IsString {
+ get {
+ return this.Type == JsonValueType.String;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue is a JsonObject.
+ ///
+ /// A value indicating whether this JsonValue is a JsonObject.
+ public bool IsJsonObject {
+ get {
+ return this.Type == JsonValueType.Object;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue is a JsonArray.
+ ///
+ /// A value indicating whether this JsonValue is a JsonArray.
+ public bool IsJsonArray {
+ get {
+ return this.Type == JsonValueType.Array;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this JsonValue represents a DateTime.
+ ///
+ /// A value indicating whether this JsonValue represents a DateTime.
+ public bool IsDateTime {
+ get {
+ return this.AsDateTime != null;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this value is true or false.
+ ///
+ /// This value as a Boolean type.
+ public bool AsBoolean {
+ get {
+ switch (this.Type)
+ {
+ case JsonValueType.Boolean:
+ return this.value == 1;
+
+ case JsonValueType.Number:
+ return this.value != 0;
+
+ case JsonValueType.String:
+ return (string)this.reference != string.Empty;
+
+ case JsonValueType.Object:
+ case JsonValueType.Array:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// Gets this value as an Integer type.
+ ///
+ /// This value as an Integer type.
+ public int AsInteger {
+ get {
+ var value = this.AsNumber;
+
+ // Prevent overflow if the value doesn't fit.
+ if (value >= int.MaxValue)
+ {
+ return int.MaxValue;
+ }
+
+ if (value <= int.MinValue)
+ {
+ return int.MinValue;
+ }
+
+ return (int)value;
+ }
+ }
+
+ ///
+ /// Gets this value as a Number type.
+ ///
+ /// This value as a Number type.
+ public double AsNumber {
+ get {
+ switch (this.Type)
+ {
+ case JsonValueType.Boolean:
+ return (this.value == 1)
+ ? 1
+ : 0;
+
+ case JsonValueType.Number:
+ return this.value;
+
+ case JsonValueType.String:
+ double number;
+ if (double.TryParse((string)this.reference, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
+ {
+ return number;
+ }
+ else
+ {
+ goto default;
+ }
+
+ default:
+ return 0;
+ }
+ }
+ }
+
+ ///
+ /// Gets this value as a String type.
+ ///
+ /// This value as a String type.
+ public string AsString {
+ get {
+ switch (this.Type)
+ {
+ case JsonValueType.Boolean:
+ return (this.value == 1)
+ ? "true"
+ : "false";
+
+ case JsonValueType.Number:
+ return this.value.ToString(CultureInfo.InvariantCulture);
+
+ case JsonValueType.String:
+ return (string)this.reference;
+
+ default:
+ return null;
+ }
+ }
+ }
+
+ ///
+ /// Gets this value as an JsonObject.
+ ///
+ /// This value as an JsonObject.
+ public JsonObject AsJsonObject {
+ get {
+ return this.IsJsonObject
+ ? (JsonObject)this.reference
+ : null;
+ }
+ }
+
+ ///
+ /// Gets this value as an JsonArray.
+ ///
+ /// This value as an JsonArray.
+ public JsonArray AsJsonArray {
+ get {
+ return this.IsJsonArray
+ ? (JsonArray)this.reference
+ : null;
+ }
+ }
+
+ ///
+ /// Gets this value as a system.DateTime.
+ ///
+ /// This value as a system.DateTime.
+ public DateTime? AsDateTime {
+ get {
+ DateTime value;
+
+ if (this.IsString && DateTime.TryParse((string)this.reference, out value))
+ {
+ return value;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ ///
+ /// Gets this (inner) value as a System.object.
+ ///
+ /// This (inner) value as a System.object.
+ public object AsObject {
+ get {
+ switch (this.Type)
+ {
+ case JsonValueType.Boolean:
+ case JsonValueType.Number:
+ return this.value;
+
+ case JsonValueType.String:
+ case JsonValueType.Object:
+ case JsonValueType.Array:
+ return this.reference;
+
+ default:
+ return null;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the value associated with the specified key.
+ ///
+ /// The key of the value to get or set.
+ ///
+ /// Thrown when this JsonValue is not a JsonObject.
+ ///
+ public JsonValue this[string key] {
+ get {
+ if (this.IsJsonObject)
+ {
+ return ((JsonObject)this.reference)[key];
+ }
+ else
+ {
+ throw new InvalidOperationException("This value does not represent a JsonObject.");
+ }
+ }
+
+ set {
+ if (this.IsJsonObject)
+ {
+ ((JsonObject)this.reference)[key] = value;
+ }
+ else
+ {
+ throw new InvalidOperationException("This value does not represent a JsonObject.");
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the value at the specified index.
+ ///
+ /// The zero-based index of the value to get or set.
+ ///
+ /// Thrown when this JsonValue is not a JsonArray
+ ///
+ public JsonValue this[int index] {
+ get {
+ if (this.IsJsonArray)
+ {
+ return ((JsonArray)this.reference)[index];
+ }
+ else
+ {
+ throw new InvalidOperationException("This value does not represent a JsonArray.");
+ }
+ }
+
+ set {
+ if (this.IsJsonArray)
+ {
+ ((JsonArray)this.reference)[index] = value;
+ }
+ else
+ {
+ throw new InvalidOperationException("This value does not represent a JsonArray.");
+ }
+ }
+ }
+
+ ///
+ /// Converts the given nullable boolean into a JsonValue.
+ ///
+ /// The value to be converted.
+ public static implicit operator JsonValue(bool? value)
+ {
+ return new JsonValue(value);
+ }
+
+ ///
+ /// Converts the given nullable double into a JsonValue.
+ ///
+ /// The value to be converted.
+ public static implicit operator JsonValue(double? value)
+ {
+ return new JsonValue(value);
+ }
+
+ ///
+ /// Converts the given string into a JsonValue.
+ ///
+ /// The value to be converted.
+ public static implicit operator JsonValue(string value)
+ {
+ return new JsonValue(value);
+ }
+
+ ///
+ /// Converts the given JsonObject into a JsonValue.
+ ///
+ /// The value to be converted.
+ public static implicit operator JsonValue(JsonObject value)
+ {
+ return new JsonValue(value);
+ }
+
+ ///
+ /// Converts the given JsonArray into a JsonValue.
+ ///
+ /// The value to be converted.
+ public static implicit operator JsonValue(JsonArray value)
+ {
+ return new JsonValue(value);
+ }
+
+ ///
+ /// Converts the given DateTime? into a JsonValue.
+ ///
+ ///
+ /// The DateTime value will be stored as a string using ISO 8601 format,
+ /// since JSON does not define a DateTime type.
+ ///
+ /// The value to be converted.
+ public static implicit operator JsonValue(DateTime? value)
+ {
+ if (value == null)
+ {
+ return JsonValue.Null;
+ }
+
+ return new JsonValue(value.Value.ToString("o"));
+ }
+
+ ///
+ /// Converts the given JsonValue into an Int.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator int(JsonValue jsonValue)
+ {
+ if (jsonValue.IsInteger)
+ {
+ return jsonValue.AsInteger;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a nullable Int.
+ ///
+ /// The JsonValue to be converted.
+ ///
+ /// Throws System.InvalidCastException when the inner value type of the
+ /// JsonValue is not the desired type of the conversion.
+ ///
+ public static explicit operator int?(JsonValue jsonValue)
+ {
+ if (jsonValue.IsNull)
+ {
+ return null;
+ }
+ else
+ {
+ return (int)jsonValue;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a Bool.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator bool(JsonValue jsonValue)
+ {
+ if (jsonValue.IsBoolean)
+ {
+ return jsonValue.value == 1;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a nullable Bool.
+ ///
+ /// The JsonValue to be converted.
+ ///
+ /// Throws System.InvalidCastException when the inner value type of the
+ /// JsonValue is not the desired type of the conversion.
+ ///
+ public static explicit operator bool?(JsonValue jsonValue)
+ {
+ if (jsonValue.IsNull)
+ {
+ return null;
+ }
+ else
+ {
+ return (bool)jsonValue;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a Double.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator double(JsonValue jsonValue)
+ {
+ if (jsonValue.IsNumber)
+ {
+ return jsonValue.value;
+ }
+ else
+ {
+ return double.NaN;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a nullable Double.
+ ///
+ /// The JsonValue to be converted.
+ ///
+ /// Throws System.InvalidCastException when the inner value type of the
+ /// JsonValue is not the desired type of the conversion.
+ ///
+ public static explicit operator double?(JsonValue jsonValue)
+ {
+ if (jsonValue.IsNull)
+ {
+ return null;
+ }
+ else
+ {
+ return (double)jsonValue;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a String.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator string(JsonValue jsonValue)
+ {
+ if (jsonValue.IsString || jsonValue.IsNull)
+ {
+ return jsonValue.reference as string;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a JsonObject.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator JsonObject(JsonValue jsonValue)
+ {
+ if (jsonValue.IsJsonObject || jsonValue.IsNull)
+ {
+ return jsonValue.reference as JsonObject;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a JsonArray.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator JsonArray(JsonValue jsonValue)
+ {
+ if (jsonValue.IsJsonArray || jsonValue.IsNull)
+ {
+ return jsonValue.reference as JsonArray;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a DateTime.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator DateTime(JsonValue jsonValue)
+ {
+ var dateTime = jsonValue.AsDateTime;
+
+ if (dateTime.HasValue)
+ {
+ return dateTime.Value;
+ }
+ else
+ {
+ return DateTime.MinValue;
+ }
+ }
+
+ ///
+ /// Converts the given JsonValue into a nullable DateTime.
+ ///
+ /// The JsonValue to be converted.
+ public static explicit operator DateTime?(JsonValue jsonValue)
+ {
+ if (jsonValue.IsDateTime || jsonValue.IsNull)
+ {
+ return jsonValue.AsDateTime;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Returns a value indicating whether the two given JsonValues are equal.
+ ///
+ /// First JsonValue to compare.
+ /// Second JsonValue to compare.
+ public static bool operator ==(JsonValue a, JsonValue b)
+ {
+ return (a.Type == b.Type)
+ && (a.value == b.value)
+ && Equals(a.reference, b.reference);
+ }
+
+ ///
+ /// Returns a value indicating whether the two given JsonValues are unequal.
+ ///
+ /// First JsonValue to compare.
+ /// Second JsonValue to compare.
+ public static bool operator !=(JsonValue a, JsonValue b)
+ {
+ return !(a == b);
+ }
+
+ ///
+ /// Returns a JsonValue by parsing the given string.
+ ///
+ /// The JSON-formatted string to be parsed.
+ /// The representing the parsed text.
+ public static JsonValue Parse(string text)
+ {
+ return JsonReader.Parse(text);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ {
+ return this.IsNull;
+ }
+
+ var jsonValue = obj as JsonValue?;
+ if (jsonValue == null)
+ {
+ return false;
+ }
+ else
+ {
+ return this == jsonValue.Value;
+ }
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ if (this.IsNull)
+ {
+ return this.Type.GetHashCode();
+ }
+ else
+ {
+ return this.Type.GetHashCode()
+ ^ this.value.GetHashCode()
+ ^ EqualityComparer