Skip to content

v2.0.0#4

Merged
ebrocas merged 90 commits into
mainfrom
dev
Jul 7, 2026
Merged

v2.0.0#4
ebrocas merged 90 commits into
mainfrom
dev

Conversation

@ebrocas

@ebrocas ebrocas commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

ebrocas and others added 30 commits June 16, 2026 12:18
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
ebrocas added 29 commits June 16, 2026 12:18
pytest 9 deprecates class-scoped fixtures defined as instance methods (they
will be removed in pytest 10): the instance a fixture runs on is not the one
test methods see. Convert the class-scoped fixtures (pyrrha_exec, export_res,
export_dump across the fs, fs-cg and decomp test classes, plus the _host_path
helper they call) to classmethods using cls instead of self. The abstract
export_res stub stays an instance method since it is always overridden and
never run as a fixture.
NamedTemporaryFile(delete_on_close=...) only exists on Python >= 3.12, but the
project supports 3.11 (the Ghidra CI image runs 3.11), so record_source raised
TypeError on the first non-imported function, aborting every Ghidra
decompilation run. Use delete=False with explicit cleanup in a finally block;
this also fixes the Windows case, where record_file cannot reopen a still-open
NamedTemporaryFile.
record_reference_location expects the reference id returned by record_ref_call,
but index_call_graph discarded it and passed the callee symbol id instead, so
call-site highlights were associated with the wrong token. Capture the ref id
and use it, skipping location recording when no reference was created.
numbat derives a source file's node id from the name passed to record_file and
returns the existing id when that name was already seen, but then always
inserts a new file row with it, raising "UNIQUE constraint failed: file.id"
whenever two functions share a name. Ghidra (and IDA) can emit several
functions with the same mangled name (tool-generated or versioned symbols), so
record the file under "<name>@<addr>", which is unique because the address is
the key under which the function is stored.
Add tests/test_decomp_mapper.py covering record_function, record_source,
index_decompiled (declaration/call-site location, suffix shadowing, missing
declaration) and index_call_graph (reference recording, imported caller/callee
skipping, unregistered callees, missing source) plus to_export and
normalize_name. The mapper is driven through a fake DB and injected FuncData,
so no IDA/Ghidra backend is needed; this raises decomp_mapper coverage from
~31% to ~82% in the backend-free CI job. Add a test_decomp_mapper CI job to
collect that coverage. The remaining uncovered lines are __init__,
index_function and map(), which require a real backend and are exercised by
the IDA/Ghidra jobs.
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
The _collect_export_artifacts teardown copied each artifact into
PYTEST_ARTIFACTS_DIR with shutil.copy2 and no error handling. When a job
downloads another job's artifacts into that same directory (test_fs-cg_backend
needs test_fs), GitLab restores them read-only, so the copy raised
PermissionError and failed the test at teardown. Drop a pre-existing
destination before copying and swallow any OSError with a warning: collecting
artifacts is a convenience and must never fail a test.
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Each test job measured only its own module and emitted a separate coverage
percentage, so there was no single number for the whole suite. Run coverage in
parallel mode (parallel=true in [tool.coverage.run]) so each job writes a
uniquely suffixed data file, save that file as an artifact, and add a coverage
job that downloads every test job's data file, combines them and produces one
global cobertura report and percentage. A [tool.coverage.paths] alias maps the
source tree and the installed wheel to the same logical path so the data files
merge instead of being double-counted. coverage combine is added before
xml/report in the per-job script, which parallel mode now requires.
Each test job runs `coverage combine`, which consumes the suffixed .coverage.*
files and writes a single plain .coverage; the artifact glob ".coverage.*" then
matched nothing, so the coverage job had no data to combine ("No data to
combine"). Rename the combined file to .coverage.$CI_JOB_NAME_SLUG (unique per
job, including matrix children) and save that as the artifact; the coverage job
then finds every job's file and merges them.
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
When several filesystem components match an imported name and the
INTERACTIVE strategy is used, _select_fs_component scans the resolve
cache for an already-chosen entry. On a cache hit it set selected_bin
but left selected_index as None, so the prompt loop guard stayed
satisfied and the user was asked to choose again, ignoring the cached
decision.

Record the matching index and break out of the scan on a cache hit so
the cached choice is returned without prompting.
Add unit tests that run without a disassembler backend, raising
backend-free coverage of the affected modules:

- decomp_mapper: drive __init__, index_function and map() via a
  DecompilMapper subclass with canned backend accessors (82% -> 99%).
- __main__: exercise the fs, fs-cg and decomp CLI commands with the
  mappers, setup_db and setup_logs faked, plus setup_logs and the
  unsupported-backend guards (0% -> 99%).
- imports_mapper: cover the DB-recording helpers, _select_fs_component
  strategies, _resolve_lib_import, resolve_symbol_import, the
  non-resolved import recording and the result validators (17% -> 45%).

The imports tests use a recording FakeDB double and real FileSystem /
Binary / Symbol objects so the resolution heuristics run against genuine
data. Includes a regression test for the INTERACTIVE duplicate-resolution
cache reuse.
Add unit tests that run without a disassembler backend for the
inter-image call-graph modules:

- intercg_mapper: cover the overridden _correct_map_result, map_binary
  bookkeeping, _treat_bin_parsing_result dispatch (incl. the lief
  fallback), the static _merge_parser_functions_into_cached_binary
  helper, _record_call_ref / _record_unindexed_call, the
  export-to-binaries map and the _record_one_call resolution branches
  (local, ignore-listed, ghidra-synthetic, template/version stripping,
  single/multiple exporters, unindexed, .ko handling) (15% -> 58%).
- intercg_bin_loader: cover FuncData accessors, the
  _count_leading_underscores helper, the synthetic-name regex,
  _build_calls_list and _disambiguate_export, the latter two exercised
  on a parser built without launching a disassembler (20% -> 32%).

As elsewhere, a recording FakeDB double and real FileSystem / Binary /
Symbol objects are used so resolution logic runs against genuine data.
The backend-free unit tests (test_main.py, test_imports_mapper.py,
test_intercg_mapper.py, test_intercg_bin_loader.py) were added as files
but no pipeline job executed them, so their coverage never reached the
global report: the intercg modules stayed at their import-only floor,
imports_mapper reflected only the backend job, and __main__ was absent
from the report entirely.

Add four backend-free jobs in the 'test' stage, each extending
test_data_structures so they reuse the parallel-mode coverage + combine
+ per-job .coverage.<slug> artifact pattern:

- test_cli                -> __main__ (tests/test_main.py)
- test_imports_mapper     -> imports_mapper unit paths
- test_intercg_mapper     -> intercg_mapper
- test_intercg_bin_loader -> intercg_bin_loader

Add all four to the global 'coverage' job's needs so their data files
are combined with the backend jobs. Modules measured by both a unit job
and a backend job (imports_mapper, intercg_*) are reported as the union
of lines any test exercised.
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
Signed-off-by: Eloïse Brocas <ebrocas@quarkslab.com>
@ebrocas ebrocas merged commit 73cd79e into main Jul 7, 2026
1 check passed
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.

1 participant