From 38f5fba310de567b43f43df093fe43783d1a27c2 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Wed, 19 Aug 2026 11:53:42 +0900 Subject: [PATCH] Fix UnboundLocalError in newick read_props when a required value is missing In read_props (ete4/parser/newick.pyx), the second try block only binds p1_str inside its if branch (when a :-separated value is present). When a strict parser requires that value but it is absent, the elif check_req and p1_req branch raises AssertionError('missing required value') while p1_str is still unbound. The except handler then references p1_str, so instead of the intended NewickError the caller gets an UnboundLocalError. This fires for every strict parser with a required second field (parsers 2, 3, 5, 6, 7). Initialize p1_str = '' before the try, mirroring how p0_str is always defined earlier in the same function. Parsing behavior is unchanged - parser=3 still rejects (A); - only the exception type on the required-value-missing path changes, so the parser raises the intended, catchable NewickError instead of crashing. Fixes #799. --- ete4/parser/newick.pyx | 1 + tests/test_tree.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ete4/parser/newick.pyx b/ete4/parser/newick.pyx index 85281b4d1..a61c79e2c 100644 --- a/ete4/parser/newick.pyx +++ b/ete4/parser/newick.pyx @@ -239,6 +239,7 @@ def read_props(str text, long pos, is_leaf, dict parser, check_req=False): except (AssertionError, ValueError) as e: raise NewickError('parsing %r: %s' % (p0_str, e)) + p1_str = '' # always defined (like p0_str) so error messages don't fail try: if pos < len(text) and text[pos] == ':': pos = skip_spaces_and_comments(text, pos+1) diff --git a/tests/test_tree.py b/tests/test_tree.py index 74d45c68b..078a9c4be 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -345,6 +345,12 @@ def test_newick_formats(self): # unsupported newick stream self.assertRaises(Exception, Tree, [1,2,3]) + def test_newick_missing_required_value(self): + # A strict parser with a required second field (dist/support) must + # raise a clean NewickError when that value is missing, not crash with + # UnboundLocalError from the error handler (issue #799). + self.assertRaises(NewickError, Tree, '(A);', parser=3) + def test_newick_multisupport(self): nw = '((a,b)2/3:4,(c,d)5/6:7);' t = Tree(nw, parser='multisupport')