Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions tools/import_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,23 +535,39 @@ def branch_variants(body):
raise AuditError("#if without a matching #endif")
root.append(body[position:])

def choices(item):
if isinstance(item, str):
return iter((item,))
return (choice for alternative in item["alternatives"] for choice in expand(alternative))

def expand(items):
variants = [""]
for item in items:
if isinstance(item, str):
variants = [variant + item for variant in variants]
continue
choices = [choice for alternative in item["alternatives"] for choice in expand(alternative)]
variants = [variant + choice for variant in variants for choice in choices]
if len(variants) > BRANCH_VARIANT_LIMIT:
iterators = [choices(item) for item in items]
values = [next(iterator) for iterator in iterators]
while True:
yield "".join(values)
index = len(items) - 1
while index >= 0:
try:
values[index] = next(iterators[index])
break
except StopIteration:
iterators[index] = choices(items[index])
values[index] = next(iterators[index])
index -= 1
if index < 0:
return

def limited(variants):
for count, variant in enumerate(variants, 1):
if count > BRANCH_VARIANT_LIMIT:
raise AuditError(f"more than {BRANCH_VARIANT_LIMIT} preprocessor branch combinations in one function")
return variants
yield variant

return expand(root)
return limited(expand(root))


def variant_calls(body):
return [unconditional_calls(variant) for variant in branch_variants(body)]
return (unconditional_calls(variant) for variant in branch_variants(body))


def unconditional_calls(body):
Expand Down
22 changes: 22 additions & 0 deletions tools/tests/test_import_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,28 @@ def test_guarded_exports_are_partial_and_unconditional_ones_are_stubs(self):
self.assertEqual(partial, {(library, nid_names.compute_nid(name)) for library in ("libGuard.prx", "libShared.prx")
for name, kind in kinds.items() if kind == "partial"})

def test_many_host_branches_only_fail_when_every_combination_must_be_read(self):
blocks = "".join(f"#ifdef HOST_{index}\n value += {index};\n#endif\n" for index in range(9))
source = self.work.source
write_source(source, "libHosts",
"void Fail() { NotImplemented_nid_no_patch(__func__); }\n"
"int APS5_VABI sceStub(int value) { NotImplemented_nid_no_patch(__func__); return 0; }\n"
f"int APS5_VABI sceHosts(int value) {{\n{blocks} return value;\n}}\n"
f"int APS5_VABI sceLastHost(int value) {{\n{blocks}#ifdef HOST_LAST\n Fail();\n#endif\n return value;\n}}\n"
f"int APS5_VABI sceNestedHosts(int value) {{\n#ifdef HOST_OUTER\n{blocks}#else\n Fail();\n#endif\n return value;\n}}\n")
stubs, partial = import_audit.source_stub_classes(source)
stub, hosts, last, nested = (("libHosts.prx", nid_names.compute_nid(name))
for name in ("sceStub", "sceHosts", "sceLastHost", "sceNestedHosts"))
self.assertEqual(set(stubs), {stub, last, nested})
self.assertEqual(partial, {last, nested})
for every in (f"{blocks} Fail();\n", f"#ifdef HOST_OUTER\n{blocks}#endif\n Fail();\n"):
with self.subTest(every=every):
write_source(source, "libHosts",
"void Fail() { NotImplemented_nid_no_patch(__func__); }\n"
f"int APS5_VABI sceEveryHost(int value) {{\n{every} return value;\n}}\n")
with self.assertRaisesRegex(import_audit.AuditError, "more than 256 preprocessor branch combinations"):
import_audit.source_stub_classes(source)

def test_partial_import_is_its_own_class(self):
direct, guarded = nid_names.compute_nid("sceDirect"), nid_names.compute_nid("sceGuarded")
stubs = {("libA.prx", direct): "sceDirect", ("libA.prx", guarded): "sceGuarded", ("libB.prx", guarded): "sceGuarded"}
Expand Down
Loading