Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/cecil.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvitek-karas <vitek.karas@microsoft.com>2020-08-25 19:47:11 +0300
committerMarek Safar <marek.safar@gmail.com>2020-09-07 22:04:06 +0300
commitc2dec1423990005029279785905e8c54abb706d4 (patch)
tree47a978b650b3a6a325592f249f449d844a508ea1
parent5bf8fbc3615ee6ddd9d20d98b8abafcf998f2c81 (diff)
Better way to update local scopes when method bodies are editted
This change will make sure that all local scopes are resolved (they refer to instructions directly and don't rely on instruction offset alone) then fixup local scopes by simply updating instruction references. Added better tests to validate that the behavior is correct across PDB write/read and some variations of the resolved/unresolved scopes behavior.
-rw-r--r--Mono.Cecil.Cil/MethodBody.cs121
-rw-r--r--Mono.Cecil.Cil/Symbols.cs2
-rw-r--r--Test/Mono.Cecil.Tests/ILProcessorTests.cs308
3 files changed, 349 insertions, 82 deletions
diff --git a/Mono.Cecil.Cil/MethodBody.cs b/Mono.Cecil.Cil/MethodBody.cs
index 0b5692d..4aa71a9 100644
--- a/Mono.Cecil.Cil/MethodBody.cs
+++ b/Mono.Cecil.Cil/MethodBody.cs
@@ -258,9 +258,7 @@ namespace Mono.Cecil.Cil {
item.next = current;
}
- var scope = GetLocalScope ();
- if (scope != null)
- UpdateLocalScope (scope, startOffset, item.GetSize (), instructionRemoved: null);
+ UpdateLocalScopes (null, null);
}
protected override void OnSet (Instruction item, int index)
@@ -273,11 +271,7 @@ namespace Mono.Cecil.Cil {
current.previous = null;
current.next = null;
- var scope = GetLocalScope ();
- if (scope != null) {
- var sizeOfCurrent = current.GetSize ();
- UpdateLocalScope (scope, current.Offset + sizeOfCurrent, item.GetSize () - sizeOfCurrent, current);
- }
+ UpdateLocalScopes (item, current);
}
protected override void OnRemove (Instruction item, int index)
@@ -291,12 +285,7 @@ namespace Mono.Cecil.Cil {
next.previous = item.previous;
RemoveSequencePoint (item);
-
- var scope = GetLocalScope ();
- if (scope != null) {
- var size = item.GetSize ();
- UpdateLocalScope (scope, item.Offset + size, -size, item);
- }
+ UpdateLocalScopes (item, next ?? previous);
item.previous = null;
item.next = null;
@@ -317,13 +306,38 @@ namespace Mono.Cecil.Cil {
}
}
- ScopeDebugInformation GetLocalScope ()
+ void UpdateLocalScopes (Instruction removedInstruction, Instruction existingInstruction)
{
var debug_info = method.debug_info;
if (debug_info == null)
- return null;
+ return;
- return debug_info.Scope;
+ // Local scopes store start/end pair of "instruction offsets". Instruction offset can be either resolved, in which case it
+ // has a reference to Instruction, or unresolved in which case it stores numerical offset (instruction offset in the body).
+ // Typically local scopes loaded from PE/PDB files will be resolved, but it's not a requirement.
+ // Each instruction has its own offset, which is populated on load, but never updated (this would be pretty expensive to do).
+ // Instructions created during the editting will typically have offset 0 (so incorrect).
+ // Local scopes created during editing will also likely be resolved (so no numerical offsets).
+ // So while local scopes which are unresolved are relatively rare if they appear, manipulating them based
+ // on the offsets allone is pretty hard (since we can't rely on correct offsets of instructions).
+ // On the other hand resolved local scopes are easy to maintain, since they point to instructions and thus inserting
+ // instructions is basically a no-op and removing instructions is as easy as changing the pointer.
+ // For this reason the algorithm here is:
+ // - First make sure that all instruction offsets are resolved - if not - resolve them
+ // - First time this will be relatively expensinve as it will walk the entire method body to convert offsets to instruction pointers
+ // Almost all local scopes are stored in the "right" order (sequentially per start offsets), so the code uses a simple one-item
+ // cache instruction<->offset to avoid walking instructions multiple times (that would only happen for scopes which are out of order).
+ // - Subsequent calls should be cheap as it will only walk all local scopes without doing anything
+ // - If there was an edit on local scope which makes some of them unresolved, the cost is proportional
+ // - Then update as necessary by manipulaitng instruction references alone
+
+ InstructionOffsetCache cache = new InstructionOffsetCache () {
+ Offset = 0,
+ Index = 0,
+ Instruction = items [0]
+ };
+
+ UpdateLocalScope (debug_info.Scope, removedInstruction, existingInstruction, ref cache);
}
static void UpdateLocalScope (ScopeDebugInformation scope, int startFromOffset, int offset, Instruction instructionRemoved)
@@ -349,5 +363,78 @@ namespace Mono.Cecil.Cil {
UpdateLocalScope (subScope, startFromOffset, offset, instructionRemoved);
}
}
+
+ void UpdateLocalScope (ScopeDebugInformation scope, Instruction removedInstruction, Instruction existingInstruction, ref InstructionOffsetCache cache)
+ {
+ if (!scope.Start.IsResolved)
+ scope.Start = ResolveInstructionOffset (scope.Start, ref cache);
+
+ if (!scope.Start.IsEndOfMethod && scope.Start.ResolvedInstruction == removedInstruction)
+ scope.Start = new InstructionOffset (existingInstruction);
+
+ if (scope.HasScopes) {
+ foreach (var subScope in scope.Scopes)
+ UpdateLocalScope (subScope, removedInstruction, existingInstruction, ref cache);
+ }
+
+ if (!scope.End.IsResolved)
+ scope.End = ResolveInstructionOffset (scope.End, ref cache);
+
+ if (!scope.End.IsEndOfMethod && scope.End.ResolvedInstruction == removedInstruction)
+ scope.End = new InstructionOffset (existingInstruction);
+ }
+
+ struct InstructionOffsetCache {
+ public int Offset;
+ public int Index;
+ public Instruction Instruction;
+ }
+
+ InstructionOffset ResolveInstructionOffset(InstructionOffset inputOffset, ref InstructionOffsetCache cache)
+ {
+ if (inputOffset.IsResolved)
+ return inputOffset;
+
+ int offset = inputOffset.Offset;
+
+ if (cache.Offset == offset)
+ return new InstructionOffset (cache.Instruction);
+
+ if (cache.Offset > offset) {
+ // This should be rare - we're resolving offset pointing to a place before the current cache position
+ // resolve by walking the instructions from start and don't cache the result.
+ int size = 0;
+ for (int i = 0; i < items.Length; i++) {
+ if (size == offset)
+ return new InstructionOffset (items [i]);
+
+ if (size > offset)
+ return new InstructionOffset (items [i - 1]);
+
+ size += items [i].GetSize ();
+ }
+
+ // Offset is larger than the size of the body - so it points after the end
+ return new InstructionOffset ();
+ } else {
+ // The offset points after the current cache position - so continue counting and update the cache
+ int size = cache.Offset;
+ for (int i = cache.Index; i < items.Length; i++) {
+ cache.Index = i;
+ cache.Offset = size;
+ cache.Instruction = items [i];
+
+ if (cache.Offset == offset)
+ return new InstructionOffset (cache.Instruction);
+
+ if (cache.Offset > offset)
+ return new InstructionOffset (items [i - 1]);
+
+ size += items [i].GetSize ();
+ }
+
+ return new InstructionOffset ();
+ }
+ }
}
}
diff --git a/Mono.Cecil.Cil/Symbols.cs b/Mono.Cecil.Cil/Symbols.cs
index 38b47a4..4cf435d 100644
--- a/Mono.Cecil.Cil/Symbols.cs
+++ b/Mono.Cecil.Cil/Symbols.cs
@@ -207,7 +207,7 @@ namespace Mono.Cecil.Cil {
get { return instruction == null && !offset.HasValue; }
}
- internal bool IsResolved => instruction != null;
+ internal bool IsResolved => instruction != null || !offset.HasValue;
internal Instruction ResolvedInstruction => instruction;
diff --git a/Test/Mono.Cecil.Tests/ILProcessorTests.cs b/Test/Mono.Cecil.Tests/ILProcessorTests.cs
index 17b4fb0..446e8fc 100644
--- a/Test/Mono.Cecil.Tests/ILProcessorTests.cs
+++ b/Test/Mono.Cecil.Tests/ILProcessorTests.cs
@@ -1,9 +1,11 @@
using System;
+using System.Collections.Generic;
+using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
-
+using Mono.Cecil.Pdb;
using NUnit.Framework;
namespace Mono.Cecil.Tests {
@@ -67,60 +69,129 @@ namespace Mono.Cecil.Tests {
}
[Test]
- public void InsertAfterWithLocalScopes ()
+ public void ReplaceUsingIndex ()
{
- var method = CreateTestMethodWithLocalScopes ();
+ var method = CreateTestMethod (OpCodes.Ldloc_0, OpCodes.Ldloc_2, OpCodes.Ldloc_3);
var il = method.GetILProcessor ();
- il.InsertAfter (
- 0,
- il.Create (OpCodes.Nop));
+ il.Replace (1, il.Create (OpCodes.Nop));
- AssertOpCodeSequence (new [] { OpCodes.Ldloc_0, OpCodes.Nop, OpCodes.Ldloc_1, OpCodes.Ldloc_2 }, method);
- var wholeBodyScope = VerifyWholeBodyScope (method);
- AssertLocalScope (wholeBodyScope.Scopes [0], 0, 2);
- AssertLocalScope (wholeBodyScope.Scopes [1], 2, 3);
- AssertLocalScope (wholeBodyScope.Scopes [2], 3, null);
+ AssertOpCodeSequence (new [] { OpCodes.Ldloc_0, OpCodes.Nop, OpCodes.Ldloc_3 }, method);
}
[Test]
- public void ReplaceUsingIndex ()
+ public void Clear ()
{
var method = CreateTestMethod (OpCodes.Ldloc_0, OpCodes.Ldloc_2, OpCodes.Ldloc_3);
var il = method.GetILProcessor ();
- il.Replace (1, il.Create (OpCodes.Nop));
+ il.Clear ();
- AssertOpCodeSequence (new [] { OpCodes.Ldloc_0, OpCodes.Nop, OpCodes.Ldloc_3 }, method);
+ AssertOpCodeSequence (new OpCode[] { }, method);
}
- [Test]
- public void ReplaceWithLocalScopes ()
+ [TestCase (RoundtripType.None, false, false)]
+ [TestCase (RoundtripType.Pdb, false, false)]
+ [TestCase (RoundtripType.Pdb, true, false)]
+ [TestCase (RoundtripType.Pdb, true, true)]
+ [TestCase (RoundtripType.PortablePdb, false, false)]
+ [TestCase (RoundtripType.PortablePdb, true, false)]
+ [TestCase (RoundtripType.PortablePdb, true, true)]
+ public void InsertAfterWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes)
{
- var method = CreateTestMethodWithLocalScopes ();
- var il = method.GetILProcessor ();
+ var methodBody = CreateTestMethodWithLocalScopes ();
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
+
+ var il = methodBody.GetILProcessor ();
+ il.InsertAfter (1, il.Create (OpCodes.Ldstr, "Test"));
- // Replace with larger instruction
- var instruction = il.Create (OpCodes.Ldstr, "test");
- instruction.Offset = method.Instructions [1].Offset;
- il.Replace (1, instruction);
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, false, reverseScopes);
+ var wholeBodyScope = VerifyWholeBodyScope (methodBody);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 3);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 4, null);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 5, 6);
- AssertOpCodeSequence (new [] { OpCodes.Ldloc_0, OpCodes.Ldstr, OpCodes.Ldloc_2 }, method);
- var wholeBodyScope = VerifyWholeBodyScope (method);
- AssertLocalScope (wholeBodyScope.Scopes [0], 0, 1);
- AssertLocalScope (wholeBodyScope.Scopes [1], 1, 6); // size of the new instruction is 5 bytes
- AssertLocalScope (wholeBodyScope.Scopes [2], 6, null);
+ methodBody.Method.Module.Dispose ();
}
- [Test]
- public void Clear ()
+ [TestCase (RoundtripType.None, false, false)]
+ [TestCase (RoundtripType.Pdb, false, false)]
+ [TestCase (RoundtripType.Pdb, true, false)]
+ [TestCase (RoundtripType.Pdb, true, true)]
+ [TestCase (RoundtripType.PortablePdb, false, false)]
+ [TestCase (RoundtripType.PortablePdb, true, false)]
+ [TestCase (RoundtripType.PortablePdb, true, true)]
+ public void RemoveWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes)
{
- var method = CreateTestMethod (OpCodes.Ldloc_0, OpCodes.Ldloc_2, OpCodes.Ldloc_3);
- var il = method.GetILProcessor ();
+ var methodBody = CreateTestMethodWithLocalScopes ();
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
- il.Clear ();
+ var il = methodBody.GetILProcessor ();
+ il.RemoveAt (1);
- AssertOpCodeSequence (new OpCode[] { }, method);
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, false, reverseScopes);
+ var wholeBodyScope = VerifyWholeBodyScope (methodBody);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 1);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 2, null);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 3, 4);
+
+ methodBody.Method.Module.Dispose ();
+ }
+
+ [TestCase (RoundtripType.None, false, false)]
+ [TestCase (RoundtripType.Pdb, false, false)]
+ [TestCase (RoundtripType.Pdb, true, false)]
+ [TestCase (RoundtripType.Pdb, true, true)]
+ [TestCase (RoundtripType.PortablePdb, false, false)]
+ [TestCase (RoundtripType.PortablePdb, true, false)]
+ [TestCase (RoundtripType.PortablePdb, true, true)]
+ public void ReplaceWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes)
+ {
+ var methodBody = CreateTestMethodWithLocalScopes ();
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
+
+ var il = methodBody.GetILProcessor ();
+ il.Replace (1, il.Create (OpCodes.Ldstr, "Test"));
+
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, false, reverseScopes);
+ var wholeBodyScope = VerifyWholeBodyScope (methodBody);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 2);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 3, null);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 4, 5);
+
+ methodBody.Method.Module.Dispose ();
+ }
+
+ [TestCase (RoundtripType.None, false, false)]
+ [TestCase (RoundtripType.Pdb, false, false)]
+ [TestCase (RoundtripType.Pdb, true, false)]
+ [TestCase (RoundtripType.Pdb, true, true)]
+ [TestCase (RoundtripType.PortablePdb, false, false)]
+ [TestCase (RoundtripType.PortablePdb, true, false)]
+ [TestCase (RoundtripType.PortablePdb, true, true)]
+ public void EditBodyWithScopesAndSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes)
+ {
+ var methodBody = CreateTestMethodWithLocalScopes ();
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
+
+ var il = methodBody.GetILProcessor ();
+ il.Replace (4, il.Create (OpCodes.Ldstr, "Test"));
+ il.InsertAfter (5, il.Create (OpCodes.Ldloc_3));
+ var tempVar3 = new VariableDefinition (methodBody.Method.Module.ImportReference (typeof (string)));
+ methodBody.Variables.Add (tempVar3);
+ methodBody.Method.DebugInformation.Scope.Scopes [reverseScopes ? 0 : 1].Scopes.Insert (reverseScopes ? 0 : 1,
+ new ScopeDebugInformation (methodBody.Instructions [5], methodBody.Instructions [6]) {
+ Variables = { new VariableDebugInformation (tempVar3, "tempVar3") }
+ });
+
+ methodBody = RoundtripMethodBody (methodBody, roundtripType, false, reverseScopes);
+ var wholeBodyScope = VerifyWholeBodyScope (methodBody);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 2);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 3, null);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 4, 5);
+ AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [1], 5, 6);
+
+ methodBody.Method.Module.Dispose ();
}
static void AssertOpCodeSequence (OpCode [] expected, MethodBody body)
@@ -136,6 +207,7 @@ namespace Mono.Cecil.Tests {
{
var method = new MethodDefinition {
Name = "function",
+ Attributes = MethodAttributes.Public | MethodAttributes.Static
};
var il = method.Body.GetILProcessor ();
@@ -158,55 +230,163 @@ namespace Mono.Cecil.Tests {
{
var debug_info = body.Method.DebugInformation;
Assert.IsNotNull (debug_info);
- AssertLocalScope (debug_info.Scope, 0, null);
+ AssertLocalScope (body, debug_info.Scope, 0, null);
return debug_info.Scope;
}
- static void AssertLocalScope (ScopeDebugInformation scope, int startOffset, int? endOffset)
+ static void AssertLocalScope (MethodBody methodBody, ScopeDebugInformation scope, int startIndex, int? endIndex)
{
Assert.IsNotNull (scope);
- Assert.AreEqual (startOffset, scope.Start.Offset);
- if (endOffset.HasValue)
- Assert.AreEqual (endOffset.Value, scope.End.Offset);
+ Assert.AreEqual (methodBody.Instructions [startIndex], scope.Start.ResolvedInstruction);
+ if (endIndex.HasValue)
+ Assert.AreEqual (methodBody.Instructions [endIndex.Value], scope.End.ResolvedInstruction);
else
Assert.IsTrue (scope.End.IsEndOfMethod);
}
static MethodBody CreateTestMethodWithLocalScopes ()
{
- var methodBody = CreateTestMethod (OpCodes.Ldloc_0, OpCodes.Ldloc_1, OpCodes.Ldloc_2);
+ var module = ModuleDefinition.CreateModule ("TestILProcessor", ModuleKind.Dll);
+ var type = new TypeDefinition ("NS", "TestType", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed, module.ImportReference (typeof (object)));
+ module.Types.Add (type);
+
+ var methodBody = CreateTestMethod (OpCodes.Nop, OpCodes.Ldloc_0, OpCodes.Nop, OpCodes.Ldloc_1, OpCodes.Nop, OpCodes.Ldloc_2, OpCodes.Nop);
var method = methodBody.Method;
- var debug_info = method.DebugInformation;
+ method.ReturnType = module.ImportReference (typeof (void));
+ type.Methods.Add (method);
- var wholeBodyScope = new ScopeDebugInformation () {
- Start = new InstructionOffset (0),
- End = new InstructionOffset ()
- };
- int size = 0;
- var instruction = methodBody.Instructions [0];
- var innerScopeBegining = new ScopeDebugInformation () {
- Start = new InstructionOffset (size),
- End = new InstructionOffset (size + instruction.GetSize ())
- };
- size += instruction.GetSize ();
- wholeBodyScope.Scopes.Add (innerScopeBegining);
+ methodBody.InitLocals = true;
+ var tempVar1 = new VariableDefinition (module.ImportReference (typeof (string)));
+ methodBody.Variables.Add (tempVar1);
+ var tempVar2 = new VariableDefinition (module.ImportReference (typeof (string)));
+ methodBody.Variables.Add (tempVar2);
- instruction = methodBody.Instructions [1];
- var innerScopeMiddle = new ScopeDebugInformation () {
- Start = new InstructionOffset (size),
- End = new InstructionOffset (size + instruction.GetSize ())
- };
- size += instruction.GetSize ();
- wholeBodyScope.Scopes.Add (innerScopeMiddle);
+ var debug_info = method.DebugInformation;
+
+ // Must add a sequence point, otherwise the native PDB writer will not actually write the method's debug info
+ // into the PDB.
+ foreach (var instr in methodBody.Instructions) {
+ var sequence_point = new SequencePoint (instr, new Document (@"C:\test.cs")) {
+ StartLine = 0,
+ StartColumn = 0,
+ EndLine = 0,
+ EndColumn = 4,
+ };
+ debug_info.SequencePoints.Add (sequence_point);
+ }
- var innerScopeEnd = new ScopeDebugInformation () {
- Start = new InstructionOffset (size),
- End = new InstructionOffset ()
+ // The method looks like this:
+ // | Scope | Scope.Scopes[0] | Scope.Scopes[1] | Scope.Scopes[1].Scopes[0]
+ // #0 Nop | > | | |
+ // #1 Ldloc_0 | . | > | |
+ // #2 Nop | . | < | |
+ // #3 Ldloc_1 | . | | > |
+ // #4 Nop | . | | . | >
+ // #5 Ldloc_2 | . | | . | <
+ // #6 Nop | . | | . |
+ // <end of m> | < | | < |
+
+ var instructions = methodBody.Instructions;
+ debug_info.Scope = new ScopeDebugInformation (instructions[0], null) {
+ Scopes = {
+ new ScopeDebugInformation (instructions[1], instructions[2]) {
+ Variables = { new VariableDebugInformation(tempVar1, "tempVar1") }
+ },
+ new ScopeDebugInformation (instructions[3], null) {
+ Scopes = {
+ new ScopeDebugInformation (instructions[4], instructions[5]) {
+ Variables = { new VariableDebugInformation(tempVar2, "tempVar2") }
+ }
+ }
+ }
+ }
};
- wholeBodyScope.Scopes.Add (innerScopeEnd);
- debug_info.Scope = wholeBodyScope;
return methodBody;
}
+
+ public enum RoundtripType {
+ None,
+ Pdb,
+ PortablePdb
+ }
+
+ static MethodBody RoundtripMethodBody(MethodBody methodBody, RoundtripType roundtripType, bool forceUnresolvedScopes = false, bool reverseScopeOrder = false)
+ {
+ var newModule = RoundtripModule (methodBody.Method.Module, roundtripType);
+ var newMethodBody = newModule.GetType ("NS.TestType").GetMethod ("function").Body;
+
+ if (forceUnresolvedScopes)
+ UnresolveScopes (newMethodBody.Method.DebugInformation.Scope);
+
+ if (reverseScopeOrder)
+ ReverseScopeOrder (newMethodBody.Method.DebugInformation.Scope);
+
+ return newMethodBody;
+ }
+
+ static void UnresolveScopes(ScopeDebugInformation scope)
+ {
+ scope.Start = new InstructionOffset (scope.Start.Offset);
+ if (!scope.End.IsEndOfMethod)
+ scope.End = new InstructionOffset (scope.End.Offset);
+
+ foreach (var subScope in scope.Scopes)
+ UnresolveScopes (subScope);
+ }
+
+ static void ReverseScopeOrder(ScopeDebugInformation scope)
+ {
+ List<ScopeDebugInformation> subScopes = scope.Scopes.ToList ();
+ subScopes.Reverse ();
+ scope.Scopes.Clear ();
+ foreach (var subScope in subScopes)
+ scope.Scopes.Add (subScope);
+
+ foreach (var subScope in scope.Scopes)
+ ReverseScopeOrder (subScope);
+ }
+
+ static ModuleDefinition RoundtripModule(ModuleDefinition module, RoundtripType roundtripType)
+ {
+ if (roundtripType == RoundtripType.None)
+ return module;
+
+ var file = Path.Combine (Path.GetTempPath (), "TestILProcessor.dll");
+ if (File.Exists (file))
+ File.Delete (file);
+
+ ISymbolWriterProvider symbolWriterProvider;
+ switch (roundtripType) {
+ case RoundtripType.Pdb when Platform.HasNativePdbSupport:
+ symbolWriterProvider = new PdbWriterProvider ();
+ break;
+ case RoundtripType.PortablePdb:
+ default:
+ symbolWriterProvider = new PortablePdbWriterProvider ();
+ break;
+ }
+
+ module.Write (file, new WriterParameters {
+ SymbolWriterProvider = symbolWriterProvider,
+ });
+ module.Dispose ();
+
+ ISymbolReaderProvider symbolReaderProvider;
+ switch (roundtripType) {
+ case RoundtripType.Pdb when Platform.HasNativePdbSupport:
+ symbolReaderProvider = new PdbReaderProvider ();
+ break;
+ case RoundtripType.PortablePdb:
+ default:
+ symbolReaderProvider = new PortablePdbReaderProvider ();
+ break;
+ }
+
+ return ModuleDefinition.ReadModule (file, new ReaderParameters {
+ SymbolReaderProvider = symbolReaderProvider,
+ InMemory = true
+ });
+ }
}
}