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:
-rw-r--r--Mono.Cecil.Cil/MethodBody.cs229
-rw-r--r--Test/Mono.Cecil.Tests/ILProcessorTests.cs126
2 files changed, 262 insertions, 93 deletions
diff --git a/Mono.Cecil.Cil/MethodBody.cs b/Mono.Cecil.Cil/MethodBody.cs
index c9236db..89bfc90 100644
--- a/Mono.Cecil.Cil/MethodBody.cs
+++ b/Mono.Cecil.Cil/MethodBody.cs
@@ -258,7 +258,7 @@ namespace Mono.Cecil.Cil {
item.next = current;
}
- UpdateLocalScopes (null, null);
+ UpdateDebugInformation (null, null);
}
protected override void OnSet (Instruction item, int index)
@@ -271,7 +271,7 @@ namespace Mono.Cecil.Cil {
current.previous = null;
current.next = null;
- UpdateLocalScopes (item, current);
+ UpdateDebugInformation (item, current);
}
protected override void OnRemove (Instruction item, int index)
@@ -285,7 +285,7 @@ namespace Mono.Cecil.Cil {
next.previous = item.previous;
RemoveSequencePoint (item);
- UpdateLocalScopes (item, next ?? previous);
+ UpdateDebugInformation (item, next ?? previous);
item.previous = null;
item.next = null;
@@ -306,126 +306,189 @@ namespace Mono.Cecil.Cil {
}
}
- void UpdateLocalScopes (Instruction removedInstruction, Instruction existingInstruction)
+ void UpdateDebugInformation (Instruction removedInstruction, Instruction existingInstruction)
{
- var debug_info = method.debug_info;
- if (debug_info == null)
- return;
-
- // Local scopes store start/end pair of "instruction offsets". Instruction offset can be either resolved, in which case it
+ // Various bits of debug information store instruction offsets (as "pointers" to the IL)
+ // 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.
+ // Depending on where the InstructionOffset comes from (loaded from PE/PDB or constructed) it can be in either state.
// 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
+ // Manipulating unresolved InstructionOffsets is pretty hard (since we can't rely on correct offsets of instructions).
+ // On the other hand resolved InstructionOffsets 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
+ // - First time this will be relatively expensive as it will walk the entire method body to convert offsets to instruction pointers
+ // Within the same debug info, IL offsets are typically 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 (as it checks that they're resolved)
+ // - If there was an edit which adds some unresolved, the cost is proportional (the code will only resolve those)
// - Then update as necessary by manipulaitng instruction references alone
- InstructionOffsetCache cache = new InstructionOffsetCache () {
- Offset = 0,
- Index = 0,
- Instruction = items [0]
- };
+ InstructionOffsetResolver resolver = new InstructionOffsetResolver (items, removedInstruction, existingInstruction);
+
+ if (method.debug_info != null)
+ UpdateLocalScope (method.debug_info.Scope, ref resolver);
+
+ var custom_debug_infos = method.custom_infos ?? method.debug_info?.custom_infos;
+ if (custom_debug_infos != null) {
+ foreach (var custom_debug_info in custom_debug_infos) {
+ switch (custom_debug_info) {
+ case StateMachineScopeDebugInformation state_machine_scope:
+ UpdateStateMachineScope (state_machine_scope, ref resolver);
+ break;
- UpdateLocalScope (debug_info.Scope, removedInstruction, existingInstruction, ref cache);
+ case AsyncMethodBodyDebugInformation async_method_body:
+ UpdateAsyncMethodBody (async_method_body, ref resolver);
+ break;
+
+ default:
+ // No need to update the other debug info as they don't store instruction references
+ break;
+ }
+ }
+ }
}
- void UpdateLocalScope (ScopeDebugInformation scope, Instruction removedInstruction, Instruction existingInstruction, ref InstructionOffsetCache cache)
+ void UpdateLocalScope (ScopeDebugInformation scope, ref InstructionOffsetResolver resolver)
{
if (scope == null)
return;
- if (!scope.Start.IsResolved)
- scope.Start = ResolveInstructionOffset (scope.Start, ref cache);
-
- if (!scope.Start.IsEndOfMethod && scope.Start.ResolvedInstruction == removedInstruction)
- scope.Start = new InstructionOffset (existingInstruction);
+ scope.Start = resolver.Resolve (scope.Start);
if (scope.HasScopes) {
foreach (var subScope in scope.Scopes)
- UpdateLocalScope (subScope, removedInstruction, existingInstruction, ref cache);
+ UpdateLocalScope (subScope, ref resolver);
}
- if (!scope.End.IsResolved)
- scope.End = ResolveInstructionOffset (scope.End, ref cache);
-
- if (!scope.End.IsEndOfMethod && scope.End.ResolvedInstruction == removedInstruction)
- scope.End = new InstructionOffset (existingInstruction);
+ scope.End = resolver.Resolve (scope.End);
}
- struct InstructionOffsetCache {
- public int Offset;
- public int Index;
- public Instruction Instruction;
+ void UpdateStateMachineScope (StateMachineScopeDebugInformation debugInfo, ref InstructionOffsetResolver resolver)
+ {
+ resolver.Restart ();
+ foreach (var scope in debugInfo.Scopes) {
+ scope.Start = resolver.Resolve (scope.Start);
+ scope.End = resolver.Resolve (scope.End);
+ }
}
- InstructionOffset ResolveInstructionOffset(InstructionOffset inputOffset, ref InstructionOffsetCache cache)
+ void UpdateAsyncMethodBody (AsyncMethodBodyDebugInformation debugInfo, ref InstructionOffsetResolver resolver)
{
- if (inputOffset.IsResolved)
- return inputOffset;
+ if (!debugInfo.CatchHandler.IsResolved) {
+ resolver.Restart ();
+ debugInfo.CatchHandler = resolver.Resolve (debugInfo.CatchHandler);
+ }
- int offset = inputOffset.Offset;
+ resolver.Restart ();
+ for (int i = 0; i < debugInfo.Yields.Count; i++) {
+ debugInfo.Yields [i] = resolver.Resolve (debugInfo.Yields [i]);
+ }
- if (cache.Offset == offset)
- return new InstructionOffset (cache.Instruction);
+ resolver.Restart ();
+ for (int i = 0; i < debugInfo.Resumes.Count; i++) {
+ debugInfo.Resumes [i] = resolver.Resolve (debugInfo.Resumes [i]);
+ }
+ }
- 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++) {
- // The array can be larger than the actual size, in which case its padded with nulls at the end
- // so when we reach null, treat it as an end of the IL.
- if (items [i] == null)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ struct InstructionOffsetResolver {
+ readonly Instruction [] items;
+ readonly Instruction removed_instruction;
+ readonly Instruction existing_instruction;
- if (size == offset)
- return new InstructionOffset (items [i]);
+ int cache_offset;
+ int cache_index;
+ Instruction cache_instruction;
- if (size > offset)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ public int LastOffset { get => cache_offset; }
- size += items [i].GetSize ();
- }
+ public InstructionOffsetResolver (Instruction[] instructions, Instruction removedInstruction, Instruction existingInstruction)
+ {
+ items = instructions;
+ removed_instruction = removedInstruction;
+ existing_instruction = existingInstruction;
+ cache_offset = 0;
+ cache_index = 0;
+ cache_instruction = items [0];
+ }
- // 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;
+ public void Restart ()
+ {
+ cache_offset = 0;
+ cache_index = 0;
+ cache_instruction = items [0];
+ }
- var item = items [i];
+ public InstructionOffset Resolve (InstructionOffset inputOffset)
+ {
+ var result = ResolveInstructionOffset (inputOffset);
+ if (!result.IsEndOfMethod && result.ResolvedInstruction == removed_instruction)
+ result = new InstructionOffset (existing_instruction);
- // Allow for trailing null values in the case of
- // instructions.Size < instructions.Capacity
- if (item == null)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ return result;
+ }
- cache.Instruction = item;
+ InstructionOffset ResolveInstructionOffset (InstructionOffset inputOffset)
+ {
+ if (inputOffset.IsResolved)
+ return inputOffset;
- if (cache.Offset == offset)
- return new InstructionOffset (cache.Instruction);
+ int offset = inputOffset.Offset;
- if (cache.Offset > offset)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ if (cache_offset == offset)
+ return new InstructionOffset (cache_instruction);
- size += item.GetSize ();
- }
+ 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++) {
+ // The array can be larger than the actual size, in which case its padded with nulls at the end
+ // so when we reach null, treat it as an end of the IL.
+ if (items [i] == null)
+ return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+
+ if (size == offset)
+ return new InstructionOffset (items [i]);
+
+ if (size > offset)
+ return new InstructionOffset (i == 0 ? items [0] : 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;
- return new InstructionOffset ();
+ var item = items [i];
+
+ // Allow for trailing null values in the case of
+ // instructions.Size < instructions.Capacity
+ if (item == null)
+ return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+
+ cache_instruction = item;
+
+ if (cache_offset == offset)
+ return new InstructionOffset (cache_instruction);
+
+ if (cache_offset > offset)
+ return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+
+ size += item.GetSize ();
+ }
+
+ return new InstructionOffset ();
+ }
}
}
}
diff --git a/Test/Mono.Cecil.Tests/ILProcessorTests.cs b/Test/Mono.Cecil.Tests/ILProcessorTests.cs
index c1dc13c..09b17a6 100644
--- a/Test/Mono.Cecil.Tests/ILProcessorTests.cs
+++ b/Test/Mono.Cecil.Tests/ILProcessorTests.cs
@@ -163,7 +163,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void InsertAfterWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -174,6 +174,10 @@ namespace Mono.Cecil.Tests {
AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 3);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 4, null);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 5, 6);
+ AssertStateMachineScope (methodBody, 1, 7);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 5, 6);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 7, 7);
methodBody.Method.Module.Dispose ();
}
@@ -189,7 +193,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void RemoveWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -200,6 +204,10 @@ namespace Mono.Cecil.Tests {
AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 1);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 2, null);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 3, 4);
+ AssertStateMachineScope (methodBody, 1, 5);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 3, 4);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 5, 5);
methodBody.Method.Module.Dispose ();
}
@@ -215,7 +223,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void ReplaceWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -226,6 +234,10 @@ namespace Mono.Cecil.Tests {
AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 2);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 3, null);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 4, 5);
+ AssertStateMachineScope (methodBody, 1, 6);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 4, 5);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 6, 6);
methodBody.Method.Module.Dispose ();
}
@@ -241,7 +253,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void EditBodyWithScopesAndSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -260,6 +272,10 @@ namespace Mono.Cecil.Tests {
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);
+ AssertStateMachineScope (methodBody, 1, 7);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 4, 5);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 7, 7);
methodBody.Method.Module.Dispose ();
}
@@ -310,6 +326,21 @@ namespace Mono.Cecil.Tests {
return method.Body;
}
+ static MethodDefinition CreateEmptyTestMethod (ModuleDefinition module, string name)
+ {
+ var method = new MethodDefinition {
+ Name = name,
+ Attributes = MethodAttributes.Public | MethodAttributes.Static
+ };
+
+ var il = method.Body.GetILProcessor ();
+ il.Emit (OpCodes.Ret);
+
+ method.ReturnType = module.ImportReference (typeof (void));
+
+ return method;
+ }
+
static ScopeDebugInformation VerifyWholeBodyScope (MethodBody body)
{
var debug_info = body.Method.DebugInformation;
@@ -318,17 +349,51 @@ namespace Mono.Cecil.Tests {
return debug_info.Scope;
}
+ static void AssertInstructionOffset (Instruction instruction, InstructionOffset instructionOffset)
+ {
+ if (instructionOffset.IsResolved)
+ Assert.AreEqual (instruction, instructionOffset.ResolvedInstruction);
+ else
+ Assert.AreEqual (instruction.Offset, instructionOffset.Offset);
+ }
+
+ static void AssertEndOfScopeOffset (MethodBody methodBody, InstructionOffset instructionOffset, int? index)
+ {
+ if (index.HasValue)
+ AssertInstructionOffset (methodBody.Instructions [index.Value], instructionOffset);
+ else
+ Assert.IsTrue (instructionOffset.IsEndOfMethod);
+ }
+
static void AssertLocalScope (MethodBody methodBody, ScopeDebugInformation scope, int startIndex, int? endIndex)
{
Assert.IsNotNull (scope);
- 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);
+ AssertInstructionOffset (methodBody.Instructions [startIndex], scope.Start);
+ AssertEndOfScopeOffset (methodBody, scope.End, endIndex);
+ }
+
+ static void AssertStateMachineScope (MethodBody methodBody, int startIndex, int? endIndex)
+ {
+ var customDebugInfo = methodBody.Method.HasCustomDebugInformations ? methodBody.Method.CustomDebugInformations : methodBody.Method.DebugInformation.CustomDebugInformations;
+ var stateMachineScope = customDebugInfo.OfType<StateMachineScopeDebugInformation> ().SingleOrDefault ();
+ Assert.IsNotNull (stateMachineScope);
+ Assert.AreEqual (1, stateMachineScope.Scopes.Count);
+ AssertInstructionOffset (methodBody.Instructions [startIndex], stateMachineScope.Scopes [0].Start);
+ AssertEndOfScopeOffset (methodBody, stateMachineScope.Scopes [0].End, endIndex);
+ }
+
+ static void AssertAsyncMethodSteppingInfo (MethodBody methodBody, int infoNumber, int yieldIndex, int resumeIndex)
+ {
+ var customDebugInfo = methodBody.Method.HasCustomDebugInformations ? methodBody.Method.CustomDebugInformations : methodBody.Method.DebugInformation.CustomDebugInformations;
+ var asyncMethodInfo = customDebugInfo.OfType<AsyncMethodBodyDebugInformation> ().SingleOrDefault ();
+ Assert.IsNotNull (asyncMethodInfo);
+ Assert.Greater (asyncMethodInfo.Yields.Count, infoNumber);
+ Assert.AreEqual (asyncMethodInfo.Yields.Count, asyncMethodInfo.Resumes.Count);
+ AssertInstructionOffset (methodBody.Instructions [yieldIndex], asyncMethodInfo.Yields [infoNumber]);
+ AssertInstructionOffset (methodBody.Instructions [resumeIndex], asyncMethodInfo.Resumes [infoNumber]);
}
- static MethodBody CreateTestMethodWithLocalScopes (bool padILWithNulls)
+ static MethodBody CreateTestMethodWithLocalScopes (RoundtripType roundtripType, bool padILWithNulls)
{
var module = ModuleDefinition.CreateModule ("TestILProcessor", ModuleKind.Dll);
var type = new TypeDefinition ("NS", "TestType", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed, module.ImportReference (typeof (object)));
@@ -342,6 +407,9 @@ namespace Mono.Cecil.Tests {
method.ReturnType = module.ImportReference (typeof (void));
type.Methods.Add (method);
+ var emptyMethod = CreateEmptyTestMethod (module, "empty");
+ type.Methods.Add (emptyMethod);
+
methodBody.InitLocals = true;
var tempVar1 = new VariableDefinition (module.ImportReference (typeof (string)));
methodBody.Variables.Add (tempVar1);
@@ -363,6 +431,7 @@ namespace Mono.Cecil.Tests {
}
// The method looks like this:
+ // Scopes
// | Scope | Scope.Scopes[0] | Scope.Scopes[1] | Scope.Scopes[1].Scopes[0]
// #0 Nop | > | | |
// #1 Ldloc_0 | . | > | |
@@ -372,6 +441,17 @@ namespace Mono.Cecil.Tests {
// #5 Ldloc_2 | . | | . | <
// #6 Nop | . | | . |
// <end of m> | < | | < |
+ //
+ // Async and state machine infos
+ // | Catch handler | Yields | Resumes | State machine |
+ // #0 Nop | | | | |
+ // #1 Ldloc_0 | | 0 | 0 | > |
+ // #2 Nop | | | | . |
+ // #3 Ldloc_1 | | | | . |
+ // #4 Nop | | 1 | | . |
+ // #5 Ldloc_2 | | | 1 | . |
+ // #6 Nop | * | 2 | 2 | < |
+ // <end of m> | | | | |
var instructions = methodBody.Instructions;
debug_info.Scope = new ScopeDebugInformation (instructions[0], null) {
@@ -389,6 +469,32 @@ namespace Mono.Cecil.Tests {
}
};
+ // For some reason the Native PDB reader/writer store the custom info on the method.DebugInfo.CustomInfo, while portable PDB stores it on method.CustomInfo.
+ var customDebugInfo = roundtripType == RoundtripType.Pdb ? method.DebugInformation.CustomDebugInformations : method.CustomDebugInformations;
+ customDebugInfo.Add (new StateMachineScopeDebugInformation () {
+ Scopes = {
+ new StateMachineScope(instructions[1], instructions[6])
+ }
+ });
+ customDebugInfo.Add (new AsyncMethodBodyDebugInformation () {
+ CatchHandler = new InstructionOffset (instructions [6]),
+ Yields = {
+ new InstructionOffset (instructions [1]),
+ new InstructionOffset (instructions [4]),
+ new InstructionOffset (instructions [6])
+ },
+ Resumes = {
+ new InstructionOffset (instructions [1]),
+ new InstructionOffset (instructions [5]),
+ new InstructionOffset (instructions [6]),
+ },
+ ResumeMethods = {
+ emptyMethod,
+ emptyMethod,
+ emptyMethod
+ }
+ });
+
return methodBody;
}