Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jbevain/cecil
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.11.4
Choose a base ref
...
head repository: jbevain/cecil
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 0.11.5
Choose a head ref

Commits on Jul 2, 2021

  1. Copy the full SHA
    ede17f9 View commit details

Commits on Jul 22, 2021

  1. Update the version of Microsoft.NETFramework.ReferenceAssemblies.net40 (

    #787)
    
    since is detected as a package downgrade and a failure in newer SDKs
    tlakollo authored Jul 22, 2021
    Copy the full SHA
    2f1077d View commit details

Commits on Aug 12, 2021

  1. Addressing issue #781 (#782)

    * Pose the problem
    
    * Quick and v dirty fix
    
    * A better and more targeted fix (to fix the previous fix)
    SteveGilham authored Aug 12, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a0a6ce4 View commit details

Commits on Jan 13, 2022

  1. Add support for generating the method and generic method comment sign…

    …ature with nested types (#801)
    
    * Add support for generating the method and generic method comment signature with nested types from Xiao Luo
    
    * Use type.GenericParameters.Count instead of custom method
    Michael Jin authored Jan 13, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5f69faa View commit details

Commits on Jan 14, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    75372c7 View commit details

Commits on Jan 19, 2022

  1. FieldRVA alignment (#817)

    * FieldRVA alignment
    
    In support of dotnet/runtime#60948 the linker (an assembly rewriter) will need to be able to preserve the alignment of RVA based fields which are to be used to create the data for `CreateSpan<T>` records
    
    This is implemented by adding a concept that RVA fields detect their required alignment by examining the PackingSize of the type of the field (if the field type is defined locally in the module)
    
    * Update Mono.Cecil.Metadata/Buffers.cs
    
    Co-authored-by: Aaron Robinson <arobins@microsoft.com>
    
    * Enhace logic used to ensure type providing PackingSize is local to the module.
    
    Co-authored-by: Aaron Robinson <arobins@microsoft.com>
    davidwrighton and AaronRobinsonMSFT authored Jan 19, 2022
    Copy the full SHA
    a56b5bd View commit details
  2. Harden debug scope update logic (#824)

    * Harden debug scope update logic
    
    Based on bug reports like #816 it seems there are still cases where the IL and scope offsets are out of sync in weird ways. This change modifies the logic to have no potential to cause the `IndexOutOfRangeException`. While I was not able to determine what combination could cause this, it's better this way.
    
    The corner case comes when there's potential problem with the first/second instruction in the method body. The change in this case will potentially make the debug scopes slightly wrong by not pointing to the previous instruction (as there's none). Without having a real repro it's hard to tell what would be a better solution, this way it won't crash and the scopes still make sense.
    
    * Fix typo
    vitek-karas authored Jan 19, 2022
    Copy the full SHA
    8b593d5 View commit details
  3. Fix deterministic MVID and add PdbChecksum (#810)

    * Fix deterministic MVID and add PdbChecksum (#31)
    
    * Fix how pdb path is calculated in the tests
    
    * Fix portable PDB stamp in CodeView header (#32)
    
    * Introduce ISymbolWriter.Write
    
    This mostly cleans up the code to make it easier to understand. `ISymbolWriter.GetDebugHeader` no longer actually writes the symbols, there's a new `Write` method for just that.
    
    The assembly writer calls `Write` first and then the image writer calls `GetDebugHeader` when it's needed.
    
    This is partially taken from #617.
    vitek-karas authored Jan 19, 2022
    Copy the full SHA
    79b43e8 View commit details

Commits on Feb 21, 2022

  1. Fix custom attribute with enum on generic type (#827)

    * Fix custom attribute with enum on generic type
    
    Fixes both the reader and the write to correctly handle values of type enum on a generic type.
    Cecil represents generic instantiations as typeref which has etype GenericInst, so the exising check for etype doesn't work. Also since attributes only allow simple values and enums (and types), there's technically no other way to get a GenericInst then the enum case.
    
    Added several test for various combinations of boxed an unboxed enums on generic type.
    
    Added a test case provided by @mrvoorhe with array of such enums.
    
    * Disable the new tests on .NET 4
    
    The CodeDom compiler doesn't support parsing enums on generic types in attributes (uses the "old" csc.exe from framework).
    vitek-karas authored Feb 21, 2022
    Copy the full SHA
    f7b64f7 View commit details

Commits on Jun 15, 2022

  1. Fix mixed module ReadSymbols() (#851)

    Fix reading some pdb generated by the C++ compiler for mixed mode assemblies.
    
    Co-authored-by: Marco Rossignoli <mrossignol@microsoft.com>
    MarcoRossignoli and Marco Rossignoli authored Jun 15, 2022
    Copy the full SHA
    2c68927 View commit details
  2. Add Unmanaged calling convention (#852)

    Fixes #842.
    Zastai authored Jun 15, 2022
    Copy the full SHA
    49b1c52 View commit details

Commits on Sep 29, 2022

  1. Add support for generic attributes (#871)

    * Add support for generic attributes
    
    * Compile test assembly against mscorlib
    
    To satisfy PEVerify
    sbomer authored Sep 29, 2022
    Copy the full SHA
    42b9ef1 View commit details
  2. InvariantCulture for operand to string conversion in Instruction.ToSt…

    …ring() (#870)
    
    * Use InvariantCulture for operand to string conversion in Instruction.ToString()
    Fantoom authored Sep 29, 2022
    Copy the full SHA
    6f94613 View commit details
  3. Fix corrupted debug header directory entry when writing multiple such…

    … entries. (#869)
    
    Basically the first two entries are written correctly, and any after that which has data will have the RVA correct, but the virtual address field will be wrong. Depending on the consumer this can work (if they use RVA) or fail (if they use virtual address) as they would read garbage data.
    
    Currently this mostly affects embedded protable PDBs since in that case we write 4 headers: CodeView, PdbChecksum, EmbeddedPdb and Deterministic (in this order), so the embedded PDB data is effectively wrong.
    
    Also adds a test which validates that both the RVA and virtual address point to the same thing.
    vitek-karas authored Sep 29, 2022
    Copy the full SHA
    7d36386 View commit details
  4. ILProcessor should also update custom debug info (#867)

    * ILProcessor should also update custom debug info (#34)
    
    * ILProcessor should also update custom debug info
    
    When eidting IL with ILProcessor various pieces of debug information have references to the origin IL instructions. These references can be either resolved (point to Instruction instance), in which case the editting mostly works, or unresolved (store IL offset only) in which case they need to be resolved before the editting can occur (after the edit the original IL offsets are invalid and unresolvable).
    
    This is effectively a continuation of #687 which implemented this for local scopes. This change extends this to async method stepping info and state machine scopes.
    
    The change refactors the code to make it easier to reuse the same logic between the various debug infos being processed.
    
    Updated the existing tests from #687 to include async and state machine debug info (completely made up) and validate that it gets updated correctly.
    
    * PR Feedback
    
    Renamed some parameters/locals to better match the existing code style.
    
    * PR Feedback
    
    * Fix test on Linux
    
    Native PDB is not supported on Linux and the test infra falls back to portable PDB automatically. Since the two PDB implementations read the custom debug info from a different place the test constructing the input needs to adapt to this difference as well.
    vitek-karas authored Sep 29, 2022
    Copy the full SHA
    9eb00e4 View commit details
  5. Add more style configuration (#854)

    This sets up .NET naming for fields, locals and parameters to use
    snake_case, so that editors won't complain about those.
    
    It also disables trimming of trailing blanks to avoid accidentally
    including such whitespace diffs in PRs.
    Zastai authored Sep 29, 2022
    Copy the full SHA
    65a2912 View commit details
  6. Fix a race condition between certain Has properties and their collect…

    …ion property. (#843)
    
    We found a rare race condition between `MethodDefinition.HasOverrides` and `MethodDefinition.Overrides`.
    
    What can happen is
    
    1) Thread 1 get's past the null check in `MethodDefinition.HasOverrides` and then is suspended.
    
    2) Thread 2, calls `MethodDefinition.Overrides` and executes at least as far as the `metadata.RemoveOverrideMapping (method)` call in `AssemblyReader.ReadOverrides`
    
    3) Thread 1 resumes on `return HasImage && Module.Read (this, (method, reader) => reader.HasOverrides (method));`  It now proceeds to AssemblyReader.HasOverrides.  No overrides are found and false is returned due to the overrides for that method having been removed from `MetadataSystem`
    
    To recap, the two notable behaviors are triggering this are
    
    a) The following check in `MethodDefinition.HasOverrides` happens outside of the lock.
    ```
    if (overrides != null)
        return overrides.Count > 0;
    ```
    
    b) The call to `metadata.RemoveOverrideMapping` in `AssemblyReader.ReadOverrides` means that `AssemblyReader.ReadOverrides` and `AssemblyReader.HasOverrides` cannot be called again after the first call to `AssemblyReader.ReadOverrides`
    
    I did not attempt to reproduce this vulnerability for every pair of properties that follows this pattern.  However, I think it's safe to assume any pair of properties that follows this same pattern is vulnerable.
    
    Using `ReadingMode.Deferred` also appears to be a required prerequisite to encounter this problem.
    
    We had two thoughts on how to fix this
    
    1) Repeat the collection null check after obtaining the module lock in `Module.Read` during `MethodDefinition.HasOverrides`
    
    2) Remove the behavior of `AssemblyReader` removing data from the `MetadataSystem`.
    
    I decided to go with Fix 2 because it was easy to find all of problematic property pairings by searching `MetadataSystem.cs` for `Remove`.  I also feel that this behavior of modifying the metadata system is asking for problems and probably not worth the freed memory is provides.
    
    If you'd prefer Fix 1 instead.  Or both Fix 1 & Fix 2 let me know and I can change around the PR.
    mrvoorhe authored Sep 29, 2022
    Copy the full SHA
    c4cfe16 View commit details
  7. Add MethodImplAttributes.AggressiveOptimization (#855)

    In .NET Core 3.0 and later, `MethodImplOptions` got a new flag value,
    `AggressiveOptimization` (512).
    
    This adds that same flag to `MethodImplAttributes` (and a corresponding
    property to `MethodDefinition`). It also updates the comments for the
    flags to match the `MethodImplOptions` documentation better.
    Zastai authored Sep 29, 2022
    Copy the full SHA
    92f32da View commit details
  8. Address issue #873 (#874)

    * Address issue #873
    
    * Be explicit as to what we support writing
    
    * Use normal test infrastructure
    
    * Restore writing primitives
    
    * Restore style
    
    * Can't verify .net core assembly
    
    Co-authored-by: Jb Evain <jb@evain.net>
    SteveGilham and jbevain authored Sep 29, 2022
    Copy the full SHA
    e052ab5 View commit details

Commits on Nov 15, 2022

  1. Treat instance and static methods as different methods during resolut…

    …ion (#882)
    
    When all other comparisons (return type, parameters, etc.) are the same,
    treat instance and static methods with the same name as different
    methods.
    
    This corrects a problem we noticed in IL2CPP in Unity.
    Joshua Peterson authored Nov 15, 2022
    Copy the full SHA
    341fb14 View commit details

Commits on Nov 16, 2022

  1. Fix a StackOverflowException reading windows runtime assemblies. (#879)

    During `AssemblyReader.ReadCustomAttributes` there is a call to `WindowsRuntimeProjections.Project`
    
    ```
    if (module.IsWindowsMetadata ())
    	foreach (var custom_attribute in custom_attributes)
    		WindowsRuntimeProjections.Project (owner, custom_attributes, custom_attribute);
    ```
    
    `WindowsRuntimeProjections.Project` would call `WindowsRuntimeProjections.HasAttribute`, which would then call `type.CustomAttributes`, which would end up back in `AssemblyReader.ReadCustomAttributes`. This would lead to a StackOverflowException.
    
    This wasn't an issue previously.  My PR #843 caused this sequence of calls to start resulting in a StackOverflowException.
    
    Prior to my PR, there was a call to `metadata.RemoveCustomAttributeRange (owner);` before the call to `WindowsRuntimeProjections.Project`.  This meant that when `WindowsRuntimeProjections.HasAttribute` would call `type.CustomAttributes`, we'd still end up in `AssemblyReader.ReadCustomAttributes`, however, no attributes would be found because the following if would be true and lead to returning an empty collection.
    ```
    if (!metadata.TryGetCustomAttributeRanges (owner, out ranges))
        return new Collection<CustomAttribute> ();
    ```
    
    The old behavior was probably the wrong.  Although I'm not certain what the tangible impact was.
    
    The fix was pretty easy.  `AssemblyReader.ReadCustomAttributes` will now pass in the custom attributes to `WindowsRuntimeProjections.Project` avoiding the need to call `type.CustomAttributes`
    mrvoorhe authored Nov 16, 2022
    Copy the full SHA
    cc48622 View commit details

Commits on Nov 28, 2022

  1. Copy the full SHA
    4ad9c0f View commit details

Commits on Jan 18, 2023

  1. Fix RVA field alignment (#888)

    * Fix RVA field alignment
    
    * Use non-latest images to run tests
    
    * Revert windows-2019 change
    
    * Don't align code segment
    
    This wasn't aligned to begin with. Attempting to align it causes problems
    due to the way CodeWriter gets the code RVA - it computes the RVA
    from the CLI Header length, which doesn't take into account the code's
    alignment requirements.
    
    * Revert "Don't align code segment"
    
    This reverts commit 9410f1e.
    
    * Keep alignment values for code
    sbomer authored Jan 18, 2023
    Copy the full SHA
    870ce3e View commit details

Commits on Apr 19, 2023

  1. Bump to 0.11.5

    jbevain committed Apr 19, 2023
    Copy the full SHA
    8c123e1 View commit details
Showing with 1,961 additions and 520 deletions.
  1. +13 −1 .editorconfig
  2. +1 −1 .github/workflows/main.yml
  3. +2 −2 Directory.Build.props
  4. +1 −1 Mono.Cecil.Cil/CodeWriter.cs
  5. +2 −1 Mono.Cecil.Cil/Instruction.cs
  6. +148 −80 Mono.Cecil.Cil/MethodBody.cs
  7. +138 −51 Mono.Cecil.Cil/PortablePdb.cs
  8. +7 −0 Mono.Cecil.Cil/Symbols.cs
  9. +17 −1 Mono.Cecil.Metadata/Buffers.cs
  10. +4 −1 Mono.Cecil.PE/ImageReader.cs
  11. +10 −6 Mono.Cecil.PE/ImageWriter.cs
  12. +23 −2 Mono.Cecil.PE/TextMap.cs
  13. +1 −1 Mono.Cecil.nuspec
  14. +15 −23 Mono.Cecil/AssemblyReader.cs
  15. +62 −24 Mono.Cecil/AssemblyWriter.cs
  16. +32 −0 Mono.Cecil/MetadataResolver.cs
  17. +0 −50 Mono.Cecil/MetadataSystem.cs
  18. +1 −0 Mono.Cecil/MethodCallingConvention.cs
  19. +5 −0 Mono.Cecil/MethodDefinition.cs
  20. +17 −16 Mono.Cecil/MethodImplAttributes.cs
  21. +5 −5 Mono.Cecil/WindowsRuntimeProjections.cs
  22. +1 −1 Mono.Security.Cryptography/CryptoService.cs
  23. +3 −3 ProjectInfo.cs
  24. +2 −2 Test/Mono.Cecil.Tests.csproj
  25. +1 −1 Test/Mono.Cecil.Tests/CompilationService.cs
  26. +174 −1 Test/Mono.Cecil.Tests/CustomAttributesTests.cs
  27. +45 −0 Test/Mono.Cecil.Tests/FieldTests.cs
  28. +199 −42 Test/Mono.Cecil.Tests/ILProcessorTests.cs
  29. +17 −3 Test/Mono.Cecil.Tests/ImageReadTests.cs
  30. +61 −1 Test/Mono.Cecil.Tests/MethodTests.cs
  31. +361 −5 Test/Mono.Cecil.Tests/PortablePdbTests.cs
  32. +20 −9 Test/Mono.Cecil.Tests/WindowsRuntimeAssemblyResolver.cs
  33. +60 −27 Test/Mono.Cecil.Tests/WindowsRuntimeProjectionsTests.cs
  34. BIN Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll
  35. BIN Test/Resources/assemblies/GenericAttributes.dll
  36. BIN Test/Resources/assemblies/NullConst.dll
  37. BIN Test/Resources/assemblies/NullConst.pdb
  38. BIN Test/Resources/assemblies/PdbChecksumLib.dll
  39. BIN Test/Resources/assemblies/PdbChecksumLib.pdb
  40. BIN Test/Resources/assemblies/empty-str-const.exe
  41. BIN Test/Resources/assemblies/empty-str-const.pdb
  42. +37 −0 Test/Resources/cs/CustomAttributes.cs
  43. +85 −0 Test/Resources/il/FieldRVAAlignment.il
  44. +25 −0 Test/Resources/il/others.il
  45. +152 −68 rocks/Mono.Cecil.Rocks/DocCommentId.cs
  46. +1 −1 rocks/Test/Mono.Cecil.Rocks.Tests.csproj
  47. +169 −73 rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs
  48. +6 −0 symbols/mdb/Mono.Cecil.Mdb/MdbWriter.cs
  49. +1 −1 symbols/mdb/Test/Mono.Cecil.Mdb.Tests.csproj
  50. +3 −3 symbols/pdb/Microsoft.Cci.Pdb/PdbFile.cs
  51. +12 −5 symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs
  52. +1 −1 symbols/pdb/Test/Mono.Cecil.Pdb.Tests.csproj
  53. +16 −7 symbols/pdb/Test/Mono.Cecil.Tests/PdbTests.cs
  54. BIN symbols/pdb/Test/Resources/assemblies/MixedNativeCLI.exe
  55. +5 −0 symbols/pdb/Test/Resources/assemblies/MixedNativeCLI.exe.metagen
  56. BIN symbols/pdb/Test/Resources/assemblies/MixedNativeCLI.pdb
14 changes: 13 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -8,4 +8,16 @@ csharp_space_between_method_call_name_and_opening_parenthesis = true
csharp_space_before_open_square_brackets = true
csharp_new_line_before_open_brace = methods
csharp_new_line_before_else = false
csharp_indent_switch_labels = false
csharp_indent_switch_labels = false

trim_trailing_whitespace = false

dotnet_naming_symbols.fields_locals_and_parameters.applicable_kinds = field, local, parameter
dotnet_naming_symbols.fields_locals_and_parameters.applicable_accessibilities = *

dotnet_naming_style.snake_case.capitalization = all_lower
dotnet_naming_style.snake_case.word_separator = _

dotnet_naming_rule.fields_locals_and_parameters_use_snake_case.symbols = fields_locals_and_parameters
dotnet_naming_rule.fields_locals_and_parameters_use_snake_case.style = snake_case
dotnet_naming_rule.fields_locals_and_parameters_use_snake_case.severity = suggestion
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ jobs:
- name: Test
run: dotnet test --no-build -c Debug Mono.Cecil.sln
linux:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v1
- name: Build
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -7,14 +7,14 @@
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\cecil.snk</AssemblyOriginatorKeyFile>
<DefineConstants Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'netcoreapp2.1' ">$(DefineConstants);NET_CORE</DefineConstants>
<DefineConstants Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'netcoreapp3.1' ">$(DefineConstants);NET_CORE</DefineConstants>
<RootNamespace></RootNamespace>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net40" Version="1.0.0" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net40" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
2 changes: 1 addition & 1 deletion Mono.Cecil.Cil/CodeWriter.cs
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ sealed class CodeWriter : ByteBuffer {
public CodeWriter (MetadataBuilder metadata)
: base (0)
{
this.code_base = metadata.text_map.GetNextRVA (TextSegment.CLIHeader);
this.code_base = metadata.text_map.GetRVA (TextSegment.Code);
this.metadata = metadata;
this.standalone_signatures = new Dictionary<uint, MetadataToken> ();
this.tiny_method_bodies = new Dictionary<ByteBuffer, RVA> (new ByteBufferEqualityComparer ());
3 changes: 2 additions & 1 deletion Mono.Cecil.Cil/Instruction.cs
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
//

using System;
using System.Globalization;
using System.Text;

namespace Mono.Cecil.Cil {
@@ -126,7 +127,7 @@ public override string ToString ()
instruction.Append ('\"');
break;
default:
instruction.Append (operand);
instruction.Append (Convert.ToString(operand, CultureInfo.InvariantCulture));
break;
}

228 changes: 148 additions & 80 deletions Mono.Cecil.Cil/MethodBody.cs
Original file line number Diff line number Diff line change
@@ -258,7 +258,7 @@ protected override void OnInsert (Instruction item, int index)
item.next = current;
}

UpdateLocalScopes (null, null);
UpdateDebugInformation (null, null);
}

protected override void OnSet (Instruction item, int index)
@@ -271,7 +271,7 @@ protected override void OnSet (Instruction item, int index)
current.previous = null;
current.next = null;

UpdateLocalScopes (item, current);
UpdateDebugInformation (item, current);
}

protected override void OnRemove (Instruction item, int index)
@@ -285,7 +285,7 @@ protected override void OnRemove (Instruction item, int index)
next.previous = item.previous;

RemoveSequencePoint (item);
UpdateLocalScopes (item, next ?? previous);
UpdateDebugInformation (item, next ?? previous);

item.previous = null;
item.next = null;
@@ -306,122 +306,190 @@ void RemoveSequencePoint (Instruction instruction)
}
}

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;

case AsyncMethodBodyDebugInformation async_method_body:
UpdateAsyncMethodBody (async_method_body, ref resolver);
break;

UpdateLocalScope (debug_info.Scope, removedInstruction, existingInstruction, ref cache);
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);
}

resolver.Restart ();
for (int i = 0; i < debugInfo.Yields.Count; i++) {
debugInfo.Yields [i] = resolver.Resolve (debugInfo.Yields [i]);
}

resolver.Restart ();
for (int i = 0; i < debugInfo.Resumes.Count; i++) {
debugInfo.Resumes [i] = resolver.Resolve (debugInfo.Resumes [i]);
}
}

int offset = inputOffset.Offset;
struct InstructionOffsetResolver {
readonly Instruction [] items;
readonly Instruction removed_instruction;
readonly Instruction existing_instruction;

if (cache.Offset == offset)
return new InstructionOffset (cache.Instruction);
int cache_offset;
int cache_index;
Instruction 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]);
public int LastOffset { get => cache_offset; }

if (size > offset)
return new InstructionOffset (items [i - 1]);
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];
}

size += items [i].GetSize ();
}
public void Restart ()
{
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 InstructionOffset Resolve (InstructionOffset inputOffset)
{
var result = ResolveInstructionOffset (inputOffset);
if (!result.IsEndOfMethod && result.ResolvedInstruction == removed_instruction)
result = new InstructionOffset (existing_instruction);

var item = items [i];
return result;
}

// Allow for trailing null values in the case of
// instructions.Size < instructions.Capacity
if (item == null)
break;
InstructionOffset ResolveInstructionOffset (InstructionOffset inputOffset)
{
if (inputOffset.IsResolved)
return inputOffset;

cache.Instruction = item;
int offset = inputOffset.Offset;

if (cache.Offset == offset)
return new InstructionOffset (cache.Instruction);
if (cache_offset == offset)
return new InstructionOffset (cache_instruction);

if (cache.Offset > offset)
return new InstructionOffset (items [i - 1]);
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]);

size += item.GetSize ();
}
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;

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;

return new InstructionOffset ();
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 ();
}
}
}
}
}
}
Loading