Repository navigation
fix: make the summary fixpoints converge - #268
Merged
Merged
Conversation
…nal calls The cycles of #261 that never ended, three and four functions and ansible's shape with exception handlers, are checked end to end with their finding, in a subprocess with a timeout. A summary lists its external calls in source order, and a caller's summary does not depend on the order of its callee's.
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.
ruff check . reports I001 three times in tests/test_summary_external_call_order.py, added in #265. This is ruff's own fix, a blank line and one imported name per line; the tests are unchanged.
Starting from empty summaries, the iteration stopped at MAX_PROJECT_ITERATIONS published summaries that missed what later iterations add, so a dependency still on its way read as no taint: a parameter returned at the end of a 22-module cycle was lost, and a sink at the end of a 30-module cycle was missed, on main too. The loop now runs until the index settles and the bound is gone; a comment at the loop states why it settles. The cross-module test runs --check in a subprocess with a timeout instead of asserting the bound, and two 30-module cycles are checked.
Contributor
Author
Measurements of the merged codeThese are the measurements of the code as merged, not the experimental ones in the description.
Suites against the merged engine
Findings
Projects 0.23.0 does not complete
Time on the projects 0.23.0 completes (seed 0)
On the other projects, the times are within a few percent of each other: mitmproxy 34.5 s and 32.1 s, luigi 26.6 s and 27.3 s, celery 25.0 s and 25.1 s. |
Contributor
Author
Measurements of the merged codeThese are the measurements of the code as merged, not the experimental ones in the description.
Suites against the merged engine
Findings
Projects 0.23.0 does not complete
Time on the projects 0.23.0 completes (seed 0)
On the other projects, the times are within a few percent of each other: mitmproxy 34.5 s and 32.1 s, luigi 26.6 s and 27.3 s, celery 25.0 s and 25.1 s. |
Merged
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
Closes #261. Two mechanisms kept the summary fixpoints from converging. This PR carries @sebas8866's fix from #265 for the first, unchanged, and adds the second.
717c074fix: canonicalize external calls in summary fixpointssummarizesorts the external calls of a summary on location, call site, invocation site and symbol;CACHE_FORMAT16 → 17; regression tests.SummaryAnalysisstarts from.ruff check .reportedI001three times in the #265 test file; ruff's own fix, no change to the tests.SummaryAnalysisnever held, and--checknever returned (ansible, fastapi, sentry, home-assistant).MAX_PROJECT_ITERATIONS: ansible's 68-module component, sentry's 1,330.SummaryAnalysisstarts from, shared asstarting_summaries. The iteration goes on until the index of the project's summaries settles, andMAX_PROJECT_ITERATIONSis gone.Why the bound had to go (review)
Starting from empty summaries, an iteration stopped before the index settles holds summaries that miss what later iterations add: a dependency still on its way reads as no taint.
mainos.system(m0.f(input()))os.system(x),m0.f(input())On
main, a member not yet summarized was an unknown symbol, whose result depends on its arguments: the first case was reported, but an unknown symbol is never a sink, so the second was missed.Why the iteration settles
The argument holds for the project loop, not for the rounds of a single module, which can lose dependencies.
A nested function starts with no nonlocal record, which reads as "never written", so its caller keeps the previous value until the nested function is summarized. The module's fixpoint is then not always the least one. For example, a function that returns
os.getenv(...)through a nonlocal overwritten with a constant, and calls itself, keepsos.getenvin its return externals.What holds:
kept, reachability,unsupported,static.This is a property of the current rules, written down in a comment at the loop: a rule reading the index without joining would break it, and the loop's termination with it.
Compatibility
CACHE_FORMAT16 → 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.coretrace_python.interprocedural.starting_summaries.coretrace_python.engine.MAX_PROJECT_ITERATIONS.Tests
tests/test_summary_external_call_order.py, 5 tests:--checkcompletes;tests/test_summary_fixpoint.py, 8 tests: each run of--checkhappens in a subprocess with a timeout, so a loop that does not end fails instead of hanging the suite.No existing test or snapshot is changed. The new tests failed before their change for the expected reasons:
Mutations caught:
_canonical_external_calls(all 4 order tests);Verification of this branch
Local runs only (macOS, Python 3.14), with the CI's steps:
ruff check .andmypy: clean.test_packaging, which also fails onmainin this environment.full-stack-fastapi-templatediffers, as it does onmainunder Python 3.14: its source uses a PEP 758 syntax that only 3.14 accepts, while the CI runs 3.11 to 3.13.These local runs keep these two environment differences and are not a green CI. The merge waits for the GitHub CI that this PR triggers.
Measurements
This branch's code with the bound lifted (identical in behaviour to the last commit), local. At every iteration, I checked that every member's new summary includes the previous one.
These measurements illustrate the argument above; they do not prove it.
On these projects, the artificial 22-module cycle aside, every component settles before 20 iterations, so removing the bound changes no finding there.
Experimental, not this branch. Copies of 0.23.0 with the canonical order and the starting summaries, implemented independently of #265 with the same sort key and still bounded at 20.
-j 16, Enterprise plugins, 38 projects without their test files, fixedPYTHONHASHSEED, on a shared machine (times indicative). Sentry's 1,330-module component settled there in 12 iterations.This branch on the full corpus: to be measured on the same corpus and configuration, and added in a comment.