Skip to content

Analyzer.sbud() returns a truncated tree unless the caller materializes the matches first #3584

Description

@ESultanik

Summary

Analyzer.sbud() called without pre-materialized matches returns a tree containing only the
top-level matches, with every subEls empty. The same analyzer returns the full tree when passed
matches_so_far. A Python API caller therefore gets a silently truncated result — no error, no
warning, just a fraction of the structure.

The CLI is unaffected, because polyfile/__main__.py materializes the matches before calling
sbud.

Reproducer

import zipfile
zipfile.ZipFile("w.zip", "w").writestr("a.txt", b"hello\n")

from polyfile.polyfile import Analyzer

def count(nodes):
    return sum(1 + count(n.get("subEls") or []) for n in nodes)

a = Analyzer("w.zip")
print(count(a.sbud()["struc"]))                        # 1

b = Analyzer("w.zip")
list(b.matches())                                      # materialize first
print(count(b.sbud(matches=b.matches_so_far)["struc"])) # 55

One node versus fifty-five, for the same file through the same class.

Cause

Match.to_obj runs on each top-level match as the generator yields it, before that match's children
have been attached. Materializing the matches first — which is what matches_so_far implies — gives
every match its children before serialization begins.

Why this matters beyond the bug

Analyzer and sbud are the documented way to use PolyFile as a library rather than a command.
A caller has no way to discover that an extra step is required: the method returns successfully, the
shape of the result is correct, and only the contents are wrong. Nothing in the signature or the
docstring says the matches must be materialized first.

That makes it a good example of what #3577 is about. Whatever the fix, the contract needs stating:
either sbud() materializes on the caller's behalf, or it refuses to run until the caller has, but
it should not quietly return less than it promises.

Suggested fix

Have sbud materialize the matches itself when none are supplied, so the parameter becomes an
optimization rather than a precondition. Confirm there is no case where a caller deliberately wants
the partial tree; if there is, that case needs a name and a docstring rather than being the default.

A test should assert that sbud() and sbud(matches=...) produce identical output for the same
input — the two paths agreeing is the property worth pinning, not either count.

Found while implementing #3581. Related: #3577, the public API contract.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions