Skip to content

Commit

Permalink
Add function sync check between symbols and headers
Browse files Browse the repository at this point in the history
Add a script to catch when the C headers define functions that don't
exist in the symbol tables. This might be the result of typos or from
renaming symbols. The script can be invoked with the symbol-check make
target from the `headers/` directory, and will be run automatically in
GitHub Actions.

Also fix the "TaskProcBoot" function signature that was caught by the
sync check script.
  • Loading branch information
UsernameFodder committed Feb 19, 2022
1 parent 2598931 commit 0aa9185
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 1 deletion.
19 changes: 19 additions & 0 deletions .github/workflows/check-function-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Check Function Synchronization

on:
pull_request:
paths:
- 'symbols/**.yml'
- 'headers/functions/**.h'
branches:
- master
workflow_call:

jobs:
sync-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Function sync check
run: make -C headers symbol-check
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@ jobs:
uses: UsernameFodder/pmdsky-debug/.github/workflows/check-headers.yml@master
with:
no-format-on-check-fail: true
check-function-sync:
needs:
- check-symbols
- check-headers
uses: UsernameFodder/pmdsky-debug/.github/workflows/check-function-sync.yml@master
generate-and-deploy:
runs-on: ubuntu-latest
needs:
- check-resymgen
- check-symbols
- check-headers
- check-function-sync
env:
RELEASE_VERSION: '0.1.0'
OUTPUT_DIR: out
Expand Down
4 changes: 4 additions & 0 deletions headers/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ format:
.PHONY: format-check
format-check:
find . -iname '*.h' | xargs clang-format --dry-run -Werror

.PHONY: symbol-check
symbol-check:
./symbol_check.py
1 change: 1 addition & 0 deletions headers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,4 @@ Naming conventions are as follows:
- [GNU Make](https://www.gnu.org/software/make/) will allow you to run `make` commands. If you have the other tools but not `make`, you can just copy commands from the [Makefile](Makefile) and run them yourself.
- Either [`clang`](https://clang.llvm.org/) or [`gcc`](https://gcc.gnu.org/) will allow you to run compiler checks (syntax and size assertions) via `make` or `make headers`.
- [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) (often comes included when you install [`clang`](https://clang.llvm.org/)) will allow you to run the formatter via `make format` (it also requires the `find` and `xargs` Unix utilities). With `clang-format` version 10+ you can also run the formatter in check mode via `make format-check`.
- [Python 3](https://www.python.org/) (invokable with the `python3` command) with [PyYAML](https://pyyaml.org/) installed (`pip3 install pyyaml`) will allow you to run synchronization checks between functions defined in the C headers and those defined in the corresponding [symbol](../symbols) files, via `make symbol-check`.
2 changes: 1 addition & 1 deletion headers/functions/arm9.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ int RoundUpDiv256(int x);
int MultiplyByFixedPoint(int x, int mult_fp);
uint32_t UMultiplyByFixedPoint(uint32_t x, uint32_t mult_fp);
void MemZero(void* ptr, uint32_t len);
void TaksProcBoot(void);
void TaskProcBoot(void);
bool SoundResume(void);
void CardPullOutWithStatus(int status);
void CardPullOut(void);
Expand Down
101 changes: 101 additions & 0 deletions headers/symbol_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python3

# Script to check that all function names present in the C headers are also
# present in the symbol tables.

import os
import re
from typing import Dict, List, Optional
import yaml

ROOT_DIR = os.path.relpath(os.path.join(os.path.dirname(__file__), ".."))
HEADERS_FUNCTIONS_DIR = os.path.join(ROOT_DIR, "headers", "functions")
SYMBOLS_DIR = os.path.join(ROOT_DIR, "symbols")

def get_function_headers() -> List[str]:
return [
os.path.join(HEADERS_FUNCTIONS_DIR, f)
for f in os.listdir(HEADERS_FUNCTIONS_DIR)
if f.endswith(".h")
]

class FunctionList:
"""
A list of all functions defined for a specific binary file, including the
C headers and the symbol tables.
"""
header_file: str
symbol_file: str

def __init__(self, header_file: str):
self.header_file = header_file
symbol_file = self.get_symbol_file(self.header_file)
if symbol_file is not None:
self.symbol_file = symbol_file
else:
raise ValueError(
f"Header file '{header_file}' has no corresponding symbol file"
)

def __str__(self) -> str:
return self.file_stem(self.header_file)

@staticmethod
def file_stem(header_file: str) -> str:
return os.path.splitext(os.path.basename(header_file))[0]

@staticmethod
def get_symbol_file(header_file: str) -> Optional[str]:
"""
Get the symbol file name that corresponds to the given header file.
"""
fname = os.path.join(
SYMBOLS_DIR,
FunctionList.file_stem(header_file) + ".yml"
)
return fname if os.path.isfile(fname) else None

def functions_from_header_file(self) -> List[str]:
with open(self.header_file, "r") as f:
return re.findall(r"\b(\w+)\s*\(", f.read())

def functions_from_symbol_file(self) -> List[str]:
with open(self.symbol_file, "r") as f:
return [
symbol["name"]
for block in yaml.safe_load(f).values()
for symbol in block["functions"]
]

def missing_functions(self) -> List[str]:
"""
Find functions that are in the C headers but not in the symbol tables.
"""
return list(
set(self.functions_from_header_file())
- set(self.functions_from_symbol_file())
)

def run_symbol_check() -> bool:
passed = True
for header_file in get_function_headers():
try:
flist = FunctionList(header_file)
missing = flist.missing_functions()
if missing:
passed = False
print(f"{flist}: found {len(missing)} discrepancies between"
+ " C headers and symbol tables.")
print(f"The following functions are present in"
+ f" {flist.header_file} but missing from"
+ f" {flist.symbol_file}:")
for symbol_name in missing:
print(f" - {symbol_name}")
except ValueError:
# File doesn't correspond to a symbol file; skip
pass
return passed

if __name__ == "__main__":
if not run_symbol_check():
raise SystemExit(1)

0 comments on commit 0aa9185

Please sign in to comment.