Skip to content

fix: make the summary fixpoints converge - #266

Closed
SizzleUnrlsd wants to merge 2 commits into
mainfrom
fix/summary-fixpoint-convergence
Closed

SizzleUnrlsd wants to merge 2 commits into
mainfrom
fix/summary-fixpoint-convergence

Conversation

@SizzleUnrlsd

Copy link
Copy Markdown
Contributor

Summary

Closes #261. Two mechanisms kept the summary fixpoints from converging; each has its own commit.

  1. Module fixpoint: external calls in source order.
    • Before: a summary listed its external calls in the order the rounds met them, a caller copying its callee's in the callee's order. Around a call cycle of three functions or more, that order rotated forever while the content had settled. The order-sensitive comparison that ends SummaryAnalysis never held, and --check never returned (ansible, fastapi, sentry, home-assistant).
    • Now: summarize sorts the external calls on the key record merges them on: location, call site, invocation site, symbol.
  2. Project fixpoint: an import cycle starts from empty summaries.
    • Before: a call between two modules of an import cycle was a call to an unknown symbol on the first iteration and a project call on the next. Around a cycle of three modules, the iteration shifted by one module each time and ran to MAX_PROJECT_ITERATIONS: ansible's 68-module component, sentry's 1,330.
    • Now: the members' functions start from the summaries SummaryAnalysis starts from, shared as starting_summaries. The bound stays as a guard; no component of the corpus reaches it any more.

The canonical order of the first commit was proposed independently in #265.

Compatibility

  • CACHE_FORMAT 16 → 17: caches built by an earlier engine are recomputed once.
  • orm-n-plus-one (Enterprise) names the first query of a callee's summary, now the first in source order. On saleor, 4 of 228 findings name another query of the same callee, at the same line.
  • New: coretrace_python.interprocedural.starting_summaries.

Tests

New file tests/test_summary_fixpoint.py. No existing test or snapshot is changed.

  • Three cycles that never ended (three functions, four functions, ansible's shape with exception handlers): --check in a subprocess with a timeout, so the failing case cannot hang the suite. Each must report its one injection.
  • A summary lists its external calls in source order.
  • A caller's summary does not depend on the order of its callee's external calls.
  • A cycle of three modules converges before the bound, with the finding of the one-module version.

The new tests failed before the change for the expected reasons: timeout, order, 21 extractions against a bound of 20.

Mutations caught:

  • no sort;
  • no starting summaries;
  • unsupported functions started empty (caught by existing tests).

Removing the call site from the sort key is not caught. Two records share a location only from two call sites of the caller, met in its block order whatever the callee's order. The key keeps it anyway, so the order is total.

Verification

  • macOS, Python 3.14:
    • unit suite: 2,339 passed, 1 failed (test_packaging, which also fails on main in this environment);
    • regression: 18 of 19 (full-stack-fastapi-template, a PEP 758 syntax only 3.14 accepts, also differs on main);
    • ruff and mypy clean.
  • Linux, Python 3.12: unit suite 2,339 passed and 1 skipped; regression 19 of 19; Enterprise suite 968 passed.

Measurements

-j 16, Enterprise plugins, 38 projects without their test files, fixed PYTHONHASHSEED. Measured on a shared machine, so times are indicative.

Project 0.23.0 This change
fastapi 0.115.6 stopped at 20 min 15 s
home-assistant 2024.12.0 stopped at 20 min 271 s
ansible 2.18.1 stopped at 64 min 281 s
sentry 24.12.0 stopped at 20 min 937 s
sqlalchemy 2.0.36 571 s 296 s

Findings on the projects 0.23.0 completes: unchanged, except the 4 saleor findings above. The suites pass unchanged against these engines.

These figures come from copies of 0.23.0 with the same changes. The measurements of this branch itself are pending, as the remote machine is unreachable; I will add them in a comment.

A summary listed its external calls in the order the rounds met them, a caller copying its callee's in the callee's order. Around a call cycle of three functions or more, that order rotated forever while the content had settled, so the order-sensitive comparison ending the module's fixpoint never held and the analysis never returned. Summaries now sort their external calls on the key record merges them on. The cache format moves to 17.
A call between two modules of an import cycle was a call to an unknown symbol on the first iteration and a project call on the next, so around a cycle of three modules the iteration shifted by one module each time and ran to MAX_PROJECT_ITERATIONS. The members' functions now start from the summaries SummaryAnalysis starts from, shared as starting_summaries.

Closes #261.
@SizzleUnrlsd SizzleUnrlsd added the bug Something isn't working label Oct 19, 2026
@SizzleUnrlsd SizzleUnrlsd self-assigned this Oct 19, 2026
@SizzleUnrlsd

Copy link
Copy Markdown
Contributor Author

Opened by mistake before deciding how to handle #265; closing for now.

@SizzleUnrlsd
SizzleUnrlsd deleted the fix/summary-fixpoint-convergence branch October 19, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

summaries: the fixpoint never terminates when a call cycle reorders external calls

1 participant