Skip to content

Do not swallow errors raised by the caller of _parse_elf - #1428

Open
feiiiiii5 wants to merge 2 commits into
pypa:mainfrom
feiiiiii5:fix/parse-elf-propagate-caller-error
Open

feiiiiii5 wants to merge 2 commits into
pypa:mainfrom
feiiiiii5:fix/parse-elf-propagate-caller-error

Conversation

@feiiiiii5

@feiiiiii5 feiiiiii5 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

_parse_elf catches OSError, TypeError and ValueError across its yield, so the same errors raised by a caller inside the with block are replaced by RuntimeError: generator didn't stop after throw().

Narrow the guard to opening and parsing the executable. ExitStack keeps the stream open while the caller uses the ELF object and closes it on either normal exit or caller failure. Missing or invalid executables still yield None.

Regression tests read a real ELF fixture and a missing path, then raise each of OSError, TypeError, ValueError and RuntimeError. They check that the original exception object propagates, including after receiving None, and that opened streams close. A separate test covers normal-exit closure.

On base ea25b7d, the new exception cases produce 3 failures and 5 passes: valid-ELF caller OSError, TypeError and ValueError become the unrelated context-manager error. The missing-file cases already pass on base.

Validation on Python 3.11.15:

  • Full pytest suite: 62,444 passed, 1 Linux-only test skipped, 427 property tests deselected by the repository defaults.
  • Statement and branch coverage: 100%, including _manylinux.py.
  • Ruff 0.16.4 check/format and mypy 2.3.1: passed on both changed files.

The current in-tree callers only read cached ELF attributes, so this error path is latent there. The change preserves their behavior while keeping errors from the context-manager body intact. Other Python versions, Linux and the property-test suite were not run locally.

_parse_elf is a context manager whose try/except wrapped the yield, so
the guard also covered the caller's own code inside the with block. A
ValueError, TypeError or OSError raised there was caught, the generator
yielded a second time, and contextlib replaced the real exception with
RuntimeError("generator didn't stop after throw()"), discarding the
original traceback.

Only a failure to open or parse the executable is reported as None, so
narrow the guard to open() plus ELFFile() and yield outside it. The file
stays open across the yield via contextlib.ExitStack, which also keeps
ef.interpreter readable inside the block.

Co-Authored-By: opencode <opencode@users.noreply.github.com>
@feiiiiii5
feiiiiii5 force-pushed the fix/parse-elf-propagate-caller-error branch from 768d8f1 to 549fae0 Compare October 8, 2026 06:34
@feiiiiii5

Copy link
Copy Markdown
Contributor Author

Hi — this is 2 files (+35/−5) at 549fae0, unchanged since I pushed it.

_parse_elf wraps its yield in the same try that guards opening and parsing the file. An OSError, TypeError or ValueError raised by the caller inside the with block is therefore thrown into the generator at the yield, caught by that handler, and ends with the generator producing a second value — so the caller's own error is replaced by the RuntimeError contextlib raises for a generator that does not stop, and the thing they were trying to inspect is lost.

The fix narrows the try to the open-and-parse step so only failures of parsing are turned into None, and the test raises ValueError inside the with block and asserts that exact error reaches the caller.

I considered keeping the broad guard and re-raising the caller's error instead, which looked noisier than narrowing the scope. Is there anything you would like changed before this can go in?

@Wkhanex Wkhanex left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ten context-manager checks passed using a real local ELF executable and a nonexistent-path fixture. Caller OSError, TypeError, ValueError and RuntimeError propagated as the same exception objects on both setup paths. Opened ELF streams were closed after normal exit and caller failure; missing-file setup yielded None normally. I used Python 3.12.14 against commit 549fae0.

The unsuccessful-setup path is worth a regression too: an exception raised by the with-body after receiving None should propagate once, just like the successful parse path, rather than be mistaken for a new parsing failure or trigger a second yield. I checked both paths against fetched PR source with standalone assertions; I did not run the full pytest suite.

@feiiiiii5

Copy link
Copy Markdown
Contributor Author

Thanks @Wkhanex for checking both setup paths. Added the missing-path regression in 576fdac, alongside the valid ELF path, for OSError, TypeError, ValueError and RuntimeError. Each checks that the exact exception object propagates; opened streams must also close after normal exit or caller failure. Production code is unchanged from the head you checked.

I independently ran the new cases on base ea25b7d: 3 failed and 5 passed, with only the valid-ELF OSError/TypeError/ValueError cases losing the original error. On this revision the full suite passes: 62,444 passed, 1 skipped, 427 property tests deselected. Statement and branch coverage are 100%; pinned Ruff 0.16.4 and mypy 2.3.1 also pass. This was on Python 3.11.15; the other platform/interpreter matrices were not run locally.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants