Repository navigation
fix: Calling igraph from a warning handler no longer corrupts the call that warned - #2999
Merged
krlmlr merged 2 commits intoOct 18, 2026
Merged
Conversation
A warning from the C core is signalled from `IGRAPH_R_CHECK()` while the generated wrapper still holds its output vectors on the finally stack and keeps the attributes of a result graph alive through the preserve list. A calling handler that calls igraph again ran `R_igraph_finalizer()`, whose `IGRAPH_FINALLY_FREE()` destroyed the outer vectors, and cleared the preserve list, so the outer call failed with "Assertion failed: v->stor_begin != NULL" or "Corrupt finally stack". `Rx_igraph_warning()` now signals the warning in a new finally stack level, with the preserve list and a pending error saved, and restores them through `R_UnwindProtect()`, also when a handler jumps out. Warnings keep their text, their source suffix and their timing. Fixes #2983. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ajt8pqcYoxFsEW8KhcTSY
Signalled inside `R_UnwindProtect()`, `Rf_warning()` took its call from the context that `R_UnwindProtect()` opens. A byte-compiled caller still got its call through the bytecode interpreter, but under `pkgload::load_all()`, covr, or with the JIT disabled, a C warning lost "In `triad_census_impl()`", and six snapshots in `test-aaa-auto.R` failed. `Rx_igraph_warning_signal()` now signals through `base::warning()`, which names the closure below it, as `Rf_warning()` did before the frame existed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnA76zWTu4ZD8BQCSYRpk
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.
A warning from the C core can now be handled by R code that calls igraph again, without corrupting the call that warned. It is stacked on #2998.
Closes #2983.
The problem
A C warning is signalled from
IGRAPH_R_CHECK()while the generated wrapper still holds its output vectors on the igraph finally stack, and keeps the attributes of a graph it is about to return alive only through the attribute preserve list. A calling handler that called igraph again ranR_igraph_finalizer(), whoseIGRAPH_FINALLY_FREE()destroyed the outer vectors, and cleared the preserve list. The outer call then failed with "Assertion failed: v->stor_begin != NULL" or "Corrupt finally stack", or returned a graph whose attributes could be garbage-collected.What it changes
Rx_igraph_warning()insrc/rinterface_extra.cnow signals the warning in a new finally stack level, with the preserve list and a pending error saved, and restores them throughR_UnwindProtect(), also when a handler jumps out (tryCatch(),options(warn = 2)). Nested calls only ever free their own entries. Nothing changes at the hundreds of.Call()sites.The second commit keeps the call of the warning. Inside
R_UnwindProtect(),Rf_warning()took its call from the context thatR_UnwindProtect()opens: a byte-compiled caller still got its call through the bytecode interpreter, but underpkgload::load_all(), covr, or with the JIT disabled, a C warning lost "Intriad_census_impl()", and six snapshots intest-aaa-auto.Rfailed. The warning is now signalled throughbase::warning(), which names the closure below it, asRf_warning()did before. Warnings keep their text, their "Source: file:line" suffix, their call and their timing: the output underoptions(warn = 1)andoptions(warn = 2)is identical tomain's.tests/testthat/test-warning-reentrancy.Radds six tests: a handler that calls igraph, a logging handler that looks at the graph that warned, a returned graph that keeps its attributes when the handler collects garbage, igraph staying usable after a handler jumps out, and C warnings keeping their text, source, count and call.Checks
With a handler that calls
degree(make_ring(3))and muffles the warning, a build of the base of this PR fails with the assertion fortriad_census(), foreigen_centrality()andhits_scores()with a negative weight, and with "Corrupt finally stack" forconnect(); a build of this branch returns all four normally.hits_scores(make_ring(4))does not warn with the current C core, so it only triggers once the C core 1.0 warns for undirected graphs. Three of the first five new tests error on the base build. The whole suite passes against an installed build of this branch (1912 blocks), exceptgraph_from_graphdb(), which could not download its data in the sandbox.devtools::document()andair formatchange nothing.R callbacks that run during a C call, such as a
bfs()callback that callsdegree(), share the finally stack problem; they are #2548 and are not addressed here.LLM use: this PR was prepared with Claude Code (Claude Opus 5.5), at the maintainer's request. The fix comes from the subsession https://claude.ai/code/session_017Ajt8pqcYoxFsEW8KhcTSY; the second commit was added in this session.
Generated by Claude Code