diff options
-rw-r--r-- | Mono.Cecil.Cil/MethodBody.cs | 81 | ||||
-rw-r--r-- | Mono.Cecil.Cil/Symbols.cs | 4 | ||||
-rw-r--r-- | Test/Mono.Cecil.Tests/ILProcessorTests.cs | 98 |
3 files changed, 168 insertions, 15 deletions
diff --git a/Mono.Cecil.Cil/MethodBody.cs b/Mono.Cecil.Cil/MethodBody.cs index ba3024d..df96849 100644 --- a/Mono.Cecil.Cil/MethodBody.cs +++ b/Mono.Cecil.Cil/MethodBody.cs @@ -197,25 +197,31 @@ namespace Mono.Cecil.Cil { protected override void OnInsert (Instruction item, int index) { - if (size == 0) - return; + int startOffset = 0; + if (size != 0) { + var current = items [index]; + if (current == null) { + var last = items [index - 1]; + last.next = item; + item.previous = last; + return; + } - var current = items [index]; - if (current == null) { - var last = items [index - 1]; - last.next = item; - item.previous = last; - return; - } + startOffset = current.Offset; - var previous = current.previous; - if (previous != null) { - previous.next = item; - item.previous = previous; + var previous = current.previous; + if (previous != null) { + previous.next = item; + item.previous = previous; + } + + current.previous = item; + item.next = current; } - current.previous = item; - item.next = current; + var scope = GetLocalScope (); + if (scope != null) + UpdateLocalScope (scope, startOffset, item.GetSize (), instructionRemoved: null); } protected override void OnSet (Instruction item, int index) @@ -227,6 +233,12 @@ 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); + } } protected override void OnRemove (Instruction item, int index) @@ -241,6 +253,12 @@ namespace Mono.Cecil.Cil { RemoveSequencePoint (item); + var scope = GetLocalScope (); + if (scope != null) { + var size = item.GetSize (); + UpdateLocalScope (scope, item.Offset + size, -size, item); + } + item.previous = null; item.next = null; } @@ -259,5 +277,38 @@ namespace Mono.Cecil.Cil { } } } + + ScopeDebugInformation GetLocalScope () + { + var debug_info = method.debug_info; + if (debug_info == null) + return null; + + return debug_info.Scope; + } + + static void UpdateLocalScope (ScopeDebugInformation scope, int startFromOffset, int offset, Instruction instructionRemoved) + { + // For both start and enf offsets on the scope: + // * If the offset is resolved (points to instruction by reference) only update it if the instruction it points to is being removed. + // For non-removed instructions it remains correct regardless of any updates to the instructions. + // * If the offset is not resolved (stores the instruction offset number itself) + // update the number accordingly to keep it pointing to the correct instruction (by offset). + + if ((!scope.Start.IsResolved && scope.Start.Offset >= startFromOffset) || + (instructionRemoved != null && scope.Start.ResolvedInstruction == instructionRemoved)) + scope.Start = new InstructionOffset (scope.Start.Offset + offset); + + // For end offset only update it if it's not the special sentinel value "EndOfMethod"; that should remain as-is. + if (!scope.End.IsEndOfMethod && + ((!scope.End.IsResolved && scope.End.Offset >= startFromOffset) || + (instructionRemoved != null && scope.End.ResolvedInstruction == instructionRemoved))) + scope.End = new InstructionOffset (scope.End.Offset + offset); + + if (scope.HasScopes) { + foreach (var subScope in scope.Scopes) + UpdateLocalScope (subScope, startFromOffset, offset, instructionRemoved); + } + } } } diff --git a/Mono.Cecil.Cil/Symbols.cs b/Mono.Cecil.Cil/Symbols.cs index 9c095af..59a2bec 100644 --- a/Mono.Cecil.Cil/Symbols.cs +++ b/Mono.Cecil.Cil/Symbols.cs @@ -207,6 +207,10 @@ namespace Mono.Cecil.Cil { get { return instruction == null && !offset.HasValue; } } + internal bool IsResolved => instruction != null; + + internal Instruction ResolvedInstruction => instruction; + public InstructionOffset (Instruction instruction) { if (instruction == null) diff --git a/Test/Mono.Cecil.Tests/ILProcessorTests.cs b/Test/Mono.Cecil.Tests/ILProcessorTests.cs index 1f22905..17b4fb0 100644 --- a/Test/Mono.Cecil.Tests/ILProcessorTests.cs +++ b/Test/Mono.Cecil.Tests/ILProcessorTests.cs @@ -67,6 +67,23 @@ namespace Mono.Cecil.Tests { } [Test] + public void InsertAfterWithLocalScopes () + { + var method = CreateTestMethodWithLocalScopes (); + var il = method.GetILProcessor (); + + il.InsertAfter ( + 0, + 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); + } + + [Test] public void ReplaceUsingIndex () { var method = CreateTestMethod (OpCodes.Ldloc_0, OpCodes.Ldloc_2, OpCodes.Ldloc_3); @@ -78,6 +95,24 @@ namespace Mono.Cecil.Tests { } [Test] + public void ReplaceWithLocalScopes () + { + var method = CreateTestMethodWithLocalScopes (); + var il = method.GetILProcessor (); + + // Replace with larger instruction + var instruction = il.Create (OpCodes.Ldstr, "test"); + instruction.Offset = method.Instructions [1].Offset; + il.Replace (1, instruction); + + 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); + } + + [Test] public void Clear () { var method = CreateTestMethod (OpCodes.Ldloc_0, OpCodes.Ldloc_2, OpCodes.Ldloc_3); @@ -108,7 +143,70 @@ namespace Mono.Cecil.Tests { foreach (var opcode in opcodes) il.Emit (opcode); + var instructions = method.Body.Instructions; + int size = 0; + for (int i = 0; i < instructions.Count; i++) { + var instruction = instructions [i]; + instruction.Offset = size; + size += instruction.GetSize (); + } + return method.Body; } + + static ScopeDebugInformation VerifyWholeBodyScope (MethodBody body) + { + var debug_info = body.Method.DebugInformation; + Assert.IsNotNull (debug_info); + AssertLocalScope (debug_info.Scope, 0, null); + return debug_info.Scope; + } + + static void AssertLocalScope (ScopeDebugInformation scope, int startOffset, int? endOffset) + { + Assert.IsNotNull (scope); + Assert.AreEqual (startOffset, scope.Start.Offset); + if (endOffset.HasValue) + Assert.AreEqual (endOffset.Value, scope.End.Offset); + else + Assert.IsTrue (scope.End.IsEndOfMethod); + } + + static MethodBody CreateTestMethodWithLocalScopes () + { + var methodBody = CreateTestMethod (OpCodes.Ldloc_0, OpCodes.Ldloc_1, OpCodes.Ldloc_2); + var method = methodBody.Method; + var debug_info = method.DebugInformation; + + 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); + + 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 innerScopeEnd = new ScopeDebugInformation () { + Start = new InstructionOffset (size), + End = new InstructionOffset () + }; + wholeBodyScope.Scopes.Add (innerScopeEnd); + + debug_info.Scope = wholeBodyScope; + return methodBody; + } } } |