Repository navigation
function_data.calls_out_to is non-deterministic: list(set)[:20] keeps a different subset per PYTHONHASHSEED #3236
Description
Found while verifying that #3200/#3201 moved nothing pre-existing: two scans of the same corpus, from the same engine commit, produce different function_data.calls_out_to values for the same function.
gitgalaxy/core/detector.py:8600:
calls_out = list({c for c in raw_calls if c not in ignore_keywords and c != name})[:20]The set is unordered, and the [:20] truncation then keeps a different subset depending on PYTHONHASHSEED. So this is not only a column whose order drifts — the contents drift, for any function with more than 20 distinct call targets.
Measured on IBM/zopeneditor-sample, two scans at the same engine commit:
id 86 old: ["IF","DATETIME","A","CHAR","RECSIZE","X","ENDFILE","R","BIT","WHEN", ...]
new: ["ENV","PSAM2","INTO","INIT","BIT","EDIT","FILE","LIST","BLKSIZE", ...]
RECSIZE, ENDFILE, WHEN and IF are present in one scan and absent in the other; INIT, EDIT, LIST and BLKSIZE the other way round. Both are 20 items. Reproducible directly:
for SEED in 1 2 3; do PYTHONHASHSEED=$SEED python3 -c "
raw=['IF','DATETIME','A','CHAR','RECSIZE','X','ENDFILE','R','BIT','WHEN','RETURNS','FILE','FORMAT','SKIP','ENV','PSAM2','INTO','OPTIONS','FIXED','DEC','INIT','EDIT','LIST','BLKSIZE','SELECT','ENDPAGE','WHILE','STRING']
print(list({c for c in raw})[:8])"; doneWhy it matters
calls_out_tois read bynetwork_risk_sensor.extract_test_coverage_mapping()to map test functions to the production functions they exercise, so the architectural test-coverage estimate is non-reproducible for any test function with >20 targets.- It breaks the repo's own determinism rule ("sort every set you serialise"; check a new output under two
PYTHONHASHSEEDvalues), which cobol-refractor: deterministic IR dumps + snapshot test of the generated clean room and Spring Boot tree #3212 applied to the refraction IR but not to the engine. - It makes any
function_datadiff between two scans noisy, which is exactly what made this visible: it was the only non-additive difference between a pre-Mainframe call graph: no edges for COBOL CALL, CICS LINK/XCTL or JCL EXEC PGM=, and unresolved CALL targets are recorded nowhere #3200 and a post-Mainframe call graph: no edges for COBOL CALL, CICS LINK/XCTL or JCL EXEC PGM=, and unresolved CALL targets are recorded nowhere #3200 scan, and had to be chased down before the Mainframe call graph: no edges for COBOL CALL, CICS LINK/XCTL or JCL EXEC PGM=, and unresolved CALL targets are recorded nowhere #3200 PR could claim it moved nothing.
Note on scope: this is not #3202. #3202 is about calls_out_to capturing the wrong things for COBOL (subscripts and intrinsics rather than PERFORM/CALL targets). This is about the same column being unstable run-to-run in every language. They are independent — fixing #3202's pattern would still leave the truncation non-deterministic.
Suggested fix: sorted(...)[:20]. That makes the retained subset deterministic, though it also makes it alphabetical rather than source-order — if the first 20 in source order is the wanted semantics, de-duplicate with dict.fromkeys(raw_calls) instead, which preserves first-occurrence order.
It does not affect the golden master (calls_out_to is not in the audit JSON), so the fix needs no bless.
Activity
squid-protocol commentedon Sep 21, 2026
closed by #3261