xml.sax expatreader swallows KeyboardInterrupt during external entity parsing
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 77.2k
- フォーク
- 35.9k
- PR マージ指標
- PR 指標を取得中
説明
When parsing XML with external entity resolution enabled, pressing Ctrl+C (or raising KeyboardInterrupt / SystemExit inside a content handler) is silently swallowed and converted into a generic SAXParseException.
The root cause is a bare except: in ExpatParser.external_entity_ref() at Lib/xml/sax/expatreader.py line 427:
try:
xmlreader.IncrementalParser.parse(self, source)
except:
return 0 # FIXME: save error info here?
The bare except: catches everything — KeyboardInterrupt, SystemExit, MemoryError — and returns 0 to expat, which then raises a generic "error in processing external entity reference". The inline FIXME notes the error info is lost, but the broader issue is that BaseException subclasses like KeyboardInterrupt should never be caught here at all.
There's also a secondary issue: the _entity_stack cleanup (lines 430–431) only runs on success, so the parser's internal state is corrupted after any error during entity parsing.
Reproduction
import xml.sax
from xml.sax.handler import feature_external_ges
from xml.sax import ContentHandler
from xml.sax.xmlreader import InputSource
from io import BytesIO
class KBHandler(ContentHandler):
def startElement(self, name, attrs):
if name == 'entity':
raise KeyboardInterrupt('simulated Ctrl+C')
class Resolver:
def resolveEntity(self, pubId, sysId):
src = InputSource()
src.setByteStream(BytesIO(b'<entity/>'))
return src
parser = xml.sax.make_parser()
parser.setFeature(feature_external_ges, True)
parser.setEntityResolver(Resolver())
parser.setContentHandler(KBHandler())
try:
parser.feed('<!DOCTYPE d [<!ENTITY e SYSTEM "x">]><d>&e;</d>')
parser.close()
except KeyboardInterrupt:
print('GOOD: KeyboardInterrupt propagated')
except xml.sax.SAXParseException as e:
print(f'BUG: KeyboardInterrupt became SAXParseException: {e}')
Output: BUG: KeyboardInterrupt became SAXParseException: <unknown>:1:6: error in processing external entity reference
Suggested fix
Change except: to except Exception: and move the _entity_stack cleanup into a finally block. I checked pyexpat.c — the C layer handles Python exception propagation correctly through call_with_frame() / XML_StopParser() / get_parse_result(), so letting KeyboardInterrupt pass through is safe.
The other bare except: in the same file (line 104 in parse()) correctly re-raises after cleanup, so this is not a deliberate pattern.
Linked PRs
- gh-148435
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
Lib/xml/sax/expatreader.py の ExpatParser.external_entity_ref() を読み、その後、例外の伝播を確認するために、提供された外部エンティティの再現コードを実行します。KeyboardInterrupt およびその他の BaseException サブクラスが SAXParseException に変換されず、エラー後に _entity_stack の状態がクリーンアップされれば完了です。PR gh-148435 はすでにリンクされています。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- backend
- issue の種類
- バグ
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 活発さ
- 停滞
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 25/100