Repository navigation
fix: checksum opcodes which include constant pool - #393
Merged
Merged
Conversation
The checksum used to be computed over the sorted characters of the UTF8 constant-pool entries, after renaming every class reference. Opcodes, numeric constants and referenced classes were therefore ignored. The checksum now covers every instruction with its symbolic operands. Only the class's own name is replaced, and fields and methods are sorted. For proxy classes, the Method fields are named after their initializer and the initializer blocks are sorted, because the JDK numbers and orders them non-deterministically. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
algomaster99
force-pushed
the
fix/hash-whole-classfile
branch
from
September 29, 2026 09:10
e32ecdb to
8971a29
Compare
algomaster99
marked this pull request as ready for review
October 18, 2026 12:13
Member
Author
|
All class hashes in the BOMI will change because of this new hash algorithm. But this change was important since we claim this in the paper. |
4 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
HashComputer.computeHashdid not checksum bytecode. It renamed everyCONSTANT_Classentry toBar, concatenated the UTF8 entries of the constant pool, sorted the characters, and hashed the result. Opcodes, numeric constants and referenced classes were therefore not part of the checksum. For example, replacingiaddwithisubornew NoSuchMethodErrorwithnew NoSuchFieldErrordid not change the hash.This PR computes the checksum over a canonical listing of the class instead:
<this>); references to other classes are kept;java.lang.reflect.Proxy, eachMethodfield is named after the block in<clinit>that initializes it, and those blocks are sorted. This covers the JDK numbering and ordering proxy members non-deterministically across runs.All callers (JDK, supply-chain and runtime indexers, and the watchdog) go through
computeHash, so no call sites change.asm-commonsis added forClassRemapper; it is relocated into the agent jars together with the rest of ASM.Examples
Hashes below are the first 12 hex characters, computed with
main(old) and this PR (new).Plain classes
The canonical listing that gets hashed:
Calc4f8635728b085d9c13db4a42Calca + b→a - b4f8635728b08❌ samec90fb32edcee✅ differsCalcLIMIT = 10→LIMIT = 994f8635728b08❌ same89e7bf652e4e✅ differsCalc4f8635728b08✅ same5d9c13db4a42✅ sameIoreturn new java.io.File("x")b3a7eee4e8cd5348696fa0e4IoFile→java.lang.Stringb3a7eee4e8cd❌ samebcc3ccadca37✅ differsRenamedIo, different class nameb3a7eee4e8cd✅ same5348696fa0e4✅ sameProxy classes
$Proxy9and$Proxy13(test fixtures) are JDK proxies forpicocli.CommandLine$Commandfrom two runs. They are identical except for the numbering of their 48Methodfields:The instructions before
putstaticare the same in both, som32andm29are both renamed tom+ the first 16 hex characters of their SHA-256:m2144dd7554349c1d. After renaming and sorting the<clinit>blocks, the two canonical listings (~1,400 lines each) are identical.$Proxy98378f33de012144bbc0de4dc$Proxy13(different field numbering)8378f33de012✅ same144bbc0de4dc✅ same$Proxy9withhelpCommand()andmixinStandardHelpOptions()reading each other'sMethodfield8378f33de012❌ samefc8e50516ed2✅ differsIn the last row, each call reaches the
InvocationHandleras the other method. The old hash cannot see this because it only counts which bytes are in the constant pool, and the namesm0–m47are the same. (The swap was made with ASM usingnew ClassWriter(reader, 0); an unmodified round trip through ASM leaves both hashes unchanged.)Tests
HashComputerTestadds 5 tests:$Proxy9vs$Proxy13fixtures) have the same hash;<clinit>blocks has the same hash;The existing GCA equality tests go through
HashComputerand still pass.mvn -pl terminator-commons test: 25 tests, 0 failures.Hashing the whole JDK works on 21.0.2 (25,309 classes) and 25.0.2 (24,716 classes), and two runs give identical output.
Verification steps (manual, before marking ready)
mvn install -DskipTests(version is now0.14.2-SNAPSHOT)experiments/rq3_rq4/{pdfbox,ttorrent,graphhopper}, updategenerate-index.shandsbom.exe.sh, which still point to0.13.1-SNAPSHOTjarsbomi.jsonl. All hashes change, so old BOMIs report every class as[MODIFIED].sbom.exe.sh; expect no[NOT ALLOWLISTED]or[MODIFIED]sbom.exe.sh; expect no[NOT ALLOWLISTED]or[MODIFIED]sbom.exe.sh; expect only the UUID-namedDropwizardResourceConfig$SpecificBinder…classes[MODIFIED]on$Proxy*orGeneratedConstructorAccessor*classes. That would mean a source of non-determinism the fixtures don't cover.Out of scope (follow-ups)
MethodParametersis dropped along with the rest of the debug information.computeHashthrows insidetransform(), the JVM ignores the exception and loads the class unchanged.🤖 Generated with Claude Code