From 44e9050a58d7fe1aa69065aaee1471649a22e880 Mon Sep 17 00:00:00 2001 From: Lorky Date: Mon, 12 Oct 2026 09:30:54 +0200 Subject: [PATCH] fix(import-audit): parse export aliases as C++ tokens (#4196) Co-authored-by: lorkydey --- tools/import_audit.py | 3 +-- tools/nid_names.py | 5 ++-- tools/tests/test_import_audit.py | 42 ++++++++++++++++++++++++++++++++ tools/tests/test_nid_names.py | 5 ++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/tools/import_audit.py b/tools/import_audit.py index a6c0f6b0d..276ce39f8 100644 --- a/tools/import_audit.py +++ b/tools/import_audit.py @@ -14,7 +14,6 @@ import progress ROOT = Path(__file__).resolve().parent.parent PRX = ROOT / "core" / "libs" / "prx" NID = re.compile(r"[A-Za-z0-9+\-]{11}") -EXPORT_ALIAS = re.compile(r'APS5_EXPORT\("([A-Za-z0-9+\-]{11})",\s*(\w+)\)') NID_POSTFIX = "_nid_postfix" CLASSES = ("implemented", "stub", "absent", "module") PE_DIRECTORY_OFFSET = {0x20b: 112, 0x10b: 96} @@ -208,7 +207,7 @@ def source_stubs(source): for library in sorted(item for item in Path(source).iterdir() if item.is_dir()): aliases = {} for file in library.rglob("*.cpp"): - for nid, name in EXPORT_ALIAS.findall(file.read_text(errors="ignore")): + for nid, name in nid_names.source_aliases(file.read_text(errors="ignore")): aliases[name] = nid group = progress.scan_library(library) done = {nid_of(name, aliases) for name in group["done_names"]} diff --git a/tools/nid_names.py b/tools/nid_names.py index 8dd9382e7..a633f94dd 100644 --- a/tools/nid_names.py +++ b/tools/nid_names.py @@ -81,7 +81,7 @@ def source_aliases(text): continue if re.fullmatch(r'"[A-Za-z0-9+\-]{11}"', literal) is None: continue - if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name) and "unknown" in name.lower(): + if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name): yield literal[1:-1], name @@ -97,7 +97,8 @@ def collect_unknowns(): for path in source_files(PRX): text = path.read_text(encoding="utf-8") for nid, name in source_aliases(text): - found.setdefault(nid, "%s:%s" % (path.relative_to(ROOT).as_posix(), name)) + if "unknown" in name.lower(): + found.setdefault(nid, "%s:%s" % (path.relative_to(ROOT).as_posix(), name)) return found diff --git a/tools/tests/test_import_audit.py b/tools/tests/test_import_audit.py index 98084bf52..5534297e0 100644 --- a/tools/tests/test_import_audit.py +++ b/tools/tests/test_import_audit.py @@ -191,6 +191,48 @@ class ImportAuditTests(unittest.TestCase): records, _ = import_audit.audit([(nid, "libB.prx")], exports, stubs, {"libA.prx", "libB.prx"}, set()) self.assertEqual(records[0]["class"], "implemented") + def test_formatted_export_aliases_remain_stubs(self): + work = self.work + nid = "AAAAAAAAAAA" + (work.libs / "libSceAlias.prx").write_bytes(exporting(nid)) + registry = work.root / "game.json" + result = work.root / "audit.json" + write_registry(registry, [(nid, "libSceAlias.prx")]) + aliases = [ + 'APS5_EXPORT (\n "AAAAAAAAAAA",\n NAME\n);\n', + 'APS5_EXPORT(/* nid */ "AAAAAAAAAAA", /* name */ NAME);\n', + 'APS5_EX\\\nPORT("AAAAAAAAAAA", NAME);\n', + ] + for name in ("sceAliasUnknown", "sceAliasedCall"): + for alias in aliases: + with self.subTest(name=name, alias=alias): + write_source(work.source, "libSceAlias", alias.replace("NAME", name) + STUB_SOURCE.format(name=name)) + code, _, err = work.run(registry, "--json", str(result)) + self.assertEqual((code, err), (0, "")) + record, = json.loads(result.read_text())["imports"] + self.assertEqual((record["class"], record["name"]), ("stub", name)) + + def test_comments_and_literals_do_not_override_export_aliases(self): + work = self.work + nid, name = "AAAAAAAAAAA", "sceAliasUnknown" + (work.libs / "libSceAlias.prx").write_bytes(exporting(nid)) + registry = work.root / "game.json" + result = work.root / "audit.json" + write_registry(registry, [(nid, "libSceAlias.prx")]) + source = 'APS5_EXPORT("AAAAAAAAAAA", sceAliasUnknown);\n' + STUB_SOURCE.format(name=name) + decoys = [ + '// APS5_EXPORT("BBBBBBBBBBB", sceAliasUnknown);\n', + '/* APS5_EXPORT("BBBBBBBBBBB", sceAliasUnknown); */\n', + 'auto example = R"text(APS5_EXPORT("BBBBBBBBBBB", sceAliasUnknown);)text";\n', + ] + for decoy in decoys: + with self.subTest(decoy=decoy): + write_source(work.source, "libSceAlias", source + decoy) + code, _, err = work.run(registry, "--json", str(result)) + self.assertEqual((code, err), (0, "")) + record, = json.loads(result.read_text())["imports"] + self.assertEqual((record["class"], record["name"]), ("stub", name)) + @unittest.skipUnless((import_audit.ROOT / ".git").exists(), "needs a git checkout") def test_json_is_stamped_with_the_source_commit(self): work = self.work diff --git a/tools/tests/test_nid_names.py b/tools/tests/test_nid_names.py index 0ac509da5..66b18f5df 100644 --- a/tools/tests/test_nid_names.py +++ b/tools/tests/test_nid_names.py @@ -161,6 +161,11 @@ int APS5_VABI sceReal() { return 0; } self.write_source('APS5_EXPORT (\n "AAAAAAAAAAA" ,\n exampleUnknown00\n );\n') self.assertEqual(nid_names.collect_unknowns(), {"AAAAAAAAAAA": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown00"}) + def test_known_aliases_are_not_unknown_exports(self): + self.write_source('APS5_EXPORT("AAAAAAAAAAA", sceKnownCall);\n' + 'APS5_EXPORT("BBBBBBBBBBB", exampleUnknown00);\n') + self.assertEqual(nid_names.collect_unknowns(), {"BBBBBBBBBBB": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown00"}) + def test_windows_source_paths_use_portable_separators(self): self.write_source('APS5_EXPORT("AAAAAAAAAAA", exampleUnknown00);\n') relative = PureWindowsPath("core/libs/prx/libSceExample/Export.cpp")