Skip to content

Make IL block comparison hierarchy- and order-aware - #269

Draft
Widthdom wants to merge 5 commits into
mainfrom
fix/hierarchy-aware-il-block-comparison
Draft

Make IL block comparison hierarchy- and order-aware#269
Widthdom wants to merge 5 commits into
mainfrom
fix/hierarchy-aware-il-block-comparison

Conversation

@Widthdom

Copy link
Copy Markdown
Owner

What

  • Decompose ordinary IL classes into hierarchy-aware class-shell and member blocks so direct method, property, and event reordering within the same class compares equal.
  • Match blocks by complete parent-class identity, member signature, and content hash.
  • Parse nested classes iteratively and use fixed-size incremental hierarchy keys for deeply nested IL.
  • Keep multiline marshal({ ... }) declarations attached to their methods.
  • Preserve declaration order for direct members of interface and import types.
  • Add parser and end-to-end comparison regressions for both dotnet-ildasm and ilspycmd layouts.
  • Update the aligned English/Japanese changelog, user guide, developer guide, and testing guide.

Changed areas:

  • Core parsing and hierarchy identity: FolderDiffIL4DotNet.Core/IL/ILBlockParser.cs
  • Internal comparison-key access: FolderDiffIL4DotNet.Core/FolderDiffIL4DotNet.Core.csproj
  • Application comparison flow: Services/ILOutput/ILBlockParser.cs, Services/ILOutputService.BlockComparison.cs, Services/ILOutputService.Comparison.cs
  • Regression coverage: FolderDiffIL4DotNet.Tests/Services/ILBlockParserTests.cs, FolderDiffIL4DotNet.Tests/Services/ILOutputServiceTests.cs, FolderDiffIL4DotNet.Tests/Services/ILOutputServiceTests.BlockComparison.cs
  • Bilingual documentation: CHANGELOG.md, USER_GUIDE.md, doc/DEVELOPER_GUIDE.md, doc/TESTING_GUIDE.md

Why

Compiler and disassembler output may reorder members inside an ordinary class without changing program behavior. The previous block-aware fallback treated an entire class as one hashed block, so this harmless reordering produced a false mismatch.

The comparison must still report meaningful differences: method-body edits, bodies swapped between signatures, moves between classes, ABI-sensitive interface/import member reordering, and marshal metadata changes.

Root cause

The previous fallback used a flat multiset of top-level (signature, content hash) blocks. It had no class/member hierarchy representation, so member order contributed to the hash of the entire class.

The parser also treated braces inside declaration-header parentheses such as marshal({ ... }) as structural body braces. A hierarchy identity based only on the first .class line could not distinguish multiline declarations whose type names occur on continuation lines, while unconditional order-independent matching was unsafe for interface/import members whose order can define interop ABI layout.

Impact

  • Reordering direct methods, properties, or events within the same ordinary class no longer creates a false mismatch.
  • Method-body changes, body swaps between signatures, and moves between classes remain detectable.
  • Complete multiline class headers distinguish otherwise-colliding container identities.
  • Multiline marshal blobs remain associated with the correct method.
  • Direct members of interface and import types remain order-sensitive, including COM vtable-relevant ordering.
  • Ordinary nested classes retain ordinary-class reordering semantics even when contained by an interface.
  • The explicit-stack parser and compact hierarchy keys support the complete comparison path at 10,000 levels of class nesting.
  • No CLI, configuration, cache-format, or report-schema change is introduced.

Six OSS review fixes

  1. Class-internal member reordering
    Ordinary classes are decomposed into a class shell and separately matched direct-member blocks under the same parent hierarchy.

  2. Non-recursive hierarchy parsing
    A single forward scan with an explicit stack avoids recursive stack growth and repeated subtree rescans or copies.

  3. Bounded hierarchy identities
    Comparison uses incrementally derived fixed-size SHA-256 hierarchy keys instead of materializing every complete ancestor path in the production comparison flow.

  4. Marshal-header block boundaries
    Braces nested inside header parentheses, including multiline marshal({ ... }) blobs, no longer terminate method blocks early.

  5. Complete multiline class identities
    Comparison keys include every class-header line, preventing classes whose names appear on continuation lines from sharing a member bucket while preserving the existing first-line ContainerPath display.

  6. ABI-sensitive member ordering
    Direct members of interface and import types stay in their ordered class shell; ordinary-class and ordinary nested-class members remain reorderable.

Checks

  • dotnet test FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj --configuration Release --nologo
    • Passed: 2,161
    • Failed: 0
    • Skipped: 1
  • git diff --check origin/main...HEAD
    • Passed
  • cdidx status --check
    • Passed after refreshing the index for the rebased commit

The skipped test is RealDisassemblerE2ETests.FilesAreEqualAsync_WhenDotNetIldasmComparesNonDeterministicRebuilds_ReturnsIlMatch. Coverage collection was not run.

Visual assets

No screenshots or GIFs are needed because this change does not alter UI or report layout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant