Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3d206dd
feat: add generate_checked_functions script to dynamically create che…
lum1n0us Oct 11, 2025
14c3c26
refactor: based on current file location to adjust header file paths
lum1n0us Oct 11, 2025
5b7d3d0
refactor: Make Result struct definition locally and remove dynamic ty…
lum1n0us Oct 11, 2025
ba4e909
feat: include original wasm_export.h and necessary headers in generat…
lum1n0us Oct 11, 2025
0c26dc1
refactor: streamline Result handling by consolidating return type ass…
lum1n0us Oct 14, 2025
003cb7d
feat: enhance generate_checked_function to support variadic arguments…
lum1n0us Oct 16, 2025
818481b
fix: update generate_checked_function to include static inline in fun…
lum1n0us Oct 16, 2025
9be8c51
WIP. fix bugs about returning a pointer
lum1n0us Oct 20, 2025
3827519
Revert "WIP. fix bugs about returning a pointer"
lum1n0us Oct 20, 2025
b891914
fix: enhance return type handling in generate_checked_function to sup…
lum1n0us Oct 20, 2025
33c31ef
fix: update generate_checked_function to resolve typedefs and enhance…
lum1n0us Oct 20, 2025
080b1fe
fix: add duplicate typedef check and improve pointer handling in gene…
lum1n0us Oct 28, 2025
6a0e38d
fix: correct error code assignment for boolean return type in generat…
lum1n0us Oct 28, 2025
bb4044d
feat: add checked API sample with Fibonacci example and CMake configu…
lum1n0us Oct 28, 2025
e04a4e7
Add command-line options to accept paths of headers as a list of mult…
lum1n0us Oct 28, 2025
dbca782
Add docstring to introduce script usage, arguments, and output
lum1n0us Oct 28, 2025
3c45109
refactor: rename and break process_headers into small ones
lum1n0us Oct 28, 2025
49bc065
fix: update copyright notice and improve header file generation comments
lum1n0us Oct 28, 2025
3e1dfce
refactor: remove unused resolve_typedef function and simplify type re…
lum1n0us Oct 29, 2025
01f14ca
fix: correct indentation in RESULT_STRUCT_TEMPLATE for clarity
lum1n0us Oct 29, 2025
0eabb2e
refactor: update CMakeLists.txt for testing support and modify demo o…
lum1n0us Oct 29, 2025
4aa42a6
refactor: enhance header generation script with default headers and f…
lum1n0us Oct 29, 2025
cf0a7b6
fix: include stdbool.h for boolean type support in aot_comp_option.h
lum1n0us Oct 29, 2025
b837e20
feat: A new job in CI to check if checked APIs are up to date
lum1n0us Oct 29, 2025
2c82850
add generated checked APIs
lum1n0us Oct 29, 2025
cb20ddf
Use sorted lists to maintain consistency between CI and local.
lum1n0us Oct 29, 2025
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
59 changes: 59 additions & 0 deletions .github/workflows/verify_checked_apis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

name: Verify core/iwasm/include checked APIs to see if they are up to date

on:
# will be triggered on PR events
pull_request:
types:
- opened
- synchronize
paths:
- "core/iwasm/include/**"
- ".github/workflows/verify_checked_apis.yml"
- "ci/generate_checked_functions.py"
push:
paths:
- "core/iwasm/include/**"
- ".github/workflows/verify_checked_apis.yml"
- "ci/generate_checked_functions.py"

# Cancel any in-flight jobs for the same PR/branch so there's only one active
# at a time
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
verify_checked_apis:
name: Verify checked APIs
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"

- name: Install dependencies
run: |
pip install pycparser
sudo apt-get update
sudo apt-get install -y clang-format-14

- name: Generate checked APIs
id: generate_checked_apis
run: |
python3 ci/generate_checked_functions.py

- name: Check for differences
run: |
#it exits with 1 if there were differences and 0 means no differences
git diff --exit-code
297 changes: 297 additions & 0 deletions ci/generate_checked_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
"""
This script generates "checked" versions of functions from the specified header files.

Usage:
python3 generate_checked_functions.py --headers <header1.h> <header2.h> ...

Arguments:
--headers: A list of header file paths to process. Each header file will be parsed, and a corresponding
"_checked.h" file will be generated with additional null pointer checks and error handling.
If not provided, a default list of headers under "core/iwasm/include/" will be used.

Example:
python3 generate_checked_functions.py
# OR
python3 generate_checked_functions.py --headers core/iwasm/include/wasm_export.h

Description:
The script parses the provided header files using `pycparser` to extract function declarations and typedefs.
For each function, it generates a "checked" version that includes:
- Null pointer checks for pointer parameters.
- Error handling using a `Result` struct.
- Support for variadic arguments (e.g., ...).

The generated "_checked.h" files include the original header file and define the `Result` struct, which
encapsulates the return value and error codes. The `Result` struct is dynamically generated based on the
return types of the functions in the header file.

Dependencies:
- pycparser: Install it using `pip install pycparser`.
- clang-format-14: Ensure it is installed for formatting the generated files.

Output:
For each input header file, a corresponding "_checked.h" file is created in the same directory.
The generated files are automatically formatted using clang-format-14.
"""

import argparse
from pathlib import Path
from pycparser import c_ast, parse_file
import subprocess

# Constants for repeated strings
CPP_ARGS = [
"-E",
"-D__attribute__(x)=",
"-D__asm__(x)=",
"-D__asm(x)=",
"-D__builtin_va_list=int",
"-D__extension__=",
"-D__inline__=",
"-D__restrict=",
"-D__restrict__=",
"-D_Static_assert(x, y)=",
"-D__signed=",
"-D__volatile__(x)=",
"-Dstatic_assert(x, y)=",
]

RESULT_STRUCT_TEMPLATE = """
typedef struct {
int error_code; // Error code (0 for success, non-zero for errors)
union {
// Add other types as needed
} value;
} Result;
"""

COPYRIGHT = """
/*
* Copyright (C) 2025 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
"""

INCLUDE_HEADERS = ["<stdbool.h>", "<stdint.h>", "<stdlib.h>"]


def extract_typedefs(ast):
"""Extract all typedefs from the AST."""
return {node.name: node.type for node in ast.ext if isinstance(node, c_ast.Typedef)}


def generate_result_struct(return_types):
"""Generate the Result struct based on return types."""
result_struct = RESULT_STRUCT_TEMPLATE
for return_type in return_types:
if return_type == "void":
continue

result_struct = result_struct.replace(
"// Add other types as needed",
f" {return_type} {return_type}_value;\n // Add other types as needed",
)
return result_struct


def write_checked_header(output_path, result_struct, functions, typedefs):
"""Write the checked header file."""

with open(output_path, "w") as f:
# copyright
f.write(COPYRIGHT)
f.write("\n")

f.write("/*\n")
f.write(" * THIS FILE IS GENERATED AUTOMATICALLY, DO NOT EDIT!\n")
f.write(" */\n")

# include guard
f.write(
f"#ifndef {output_path.stem.upper()}_H\n#define {output_path.stem.upper()}_H\n\n"
)

for header in INCLUDE_HEADERS:
f.write(f"#include {header}\n")
f.write("\n")
# include original header
original_header = output_path.stem.replace("_checked", "") + ".h"
f.write(f'#include "{original_header}"\n')
f.write("\n")

f.write(result_struct + "\n")

for func in functions:
new_func = generate_checked_function(func, typedefs)
f.write(new_func + "\n\n")

f.write(f"#endif // {output_path.stem.upper()}_H\n")


def generate_checked_function(func, typedefs):
"""Generate a checked version of the given function."""
func_name = func.name # Access the name directly from Decl
new_func_name = f"{func_name}_checked"

# Extract parameters
params = func.type.args.params if func.type.args else []

# Determine the return type
return_pointer = False
return_type = "void" # Default to void if no return type is specified
if isinstance(func.type.type, c_ast.TypeDecl):
return_type = " ".join(func.type.type.type.names)

resolved_type = typedefs.get(return_type, return_type)
if isinstance(resolved_type, c_ast.PtrDecl):
return_pointer = True

# Start building the new function
new_func = [f"static inline Result {new_func_name}("]
param_list = []
for param in params:
if isinstance(param, c_ast.EllipsisParam):
# Handle variadic arguments (e.g., ...)
param_list.append("...")
new_func.append(" ...,")
continue

param_name = param.name if param.name else ""
param_list.append(param_name)
param_type = (
" ".join(param.type.type.names)
if isinstance(param.type, c_ast.TypeDecl)
else "void*"
)
new_func.append(f" {param_type} {param_name},")
if param_list:
new_func[-1] = new_func[-1].rstrip(",") # Remove trailing comma
new_func.append(") {")

# Add null checks for pointer parameters
new_func.append(f" Result res;")
has_variadic = False
for param in params:
if isinstance(param, c_ast.EllipsisParam):
# Restructure to use va_list
new_func.append(" va_list args;")
has_variadic = True
elif isinstance(param.type, c_ast.PtrDecl):
new_func.append(f" // Check for null pointer parameter: {param.name}")
new_func.append(f" if ({param.name} == NULL) {{")
new_func.append(f" res.error_code = -1;")
new_func.append(f" return res;")
new_func.append(f" }}")

# Call the original function
new_func.append(f" // Execute the original function")
if return_type == "void":
new_func.append(f" {func_name}({', '.join(param_list)});")
elif has_variadic:
new_func.append(" va_start(args, " + param_list[-2] + ");")
new_func.append(
f" {return_type} original_result = {func_name}({', '.join(param_list[:-1])}, args);"
)
new_func.append(" va_end(args);")
else:
new_func.append(
f" {return_type} original_result = {func_name}({', '.join(param_list)});"
)

# Handle returned values
new_func.append(f" // Assign return value and error code")
if return_type == "void":
new_func.append(f" res.error_code = 0;")
elif return_type == "_Bool":
new_func.append(f" res.error_code = original_result ? 0 : -2;")
new_func.append(f" res.value._Bool_value = original_result;")
# if return type is a pointer or typedef from pointer
elif return_pointer:
new_func.append(f" if (original_result != NULL) {{")
new_func.append(f" res.error_code = 0;")
new_func.append(f" res.value.{return_type}_value = original_result;")
new_func.append(f" }} else {{")
new_func.append(f" res.error_code = -2;")
new_func.append(f" }}")
else:
new_func.append(f" if (original_result == 0) {{")
new_func.append(f" res.error_code = 0;")
new_func.append(f" res.value.{return_type}_value = original_result;")
new_func.append(f" }} else {{")
new_func.append(f" res.error_code = -2;")
new_func.append(f" }}")

new_func.append(f" return res;")
new_func.append(f"}}")
return "\n".join(new_func)


def parse_arguments():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(
description="Generate checked functions from header files."
)
parser.add_argument(
"--headers",
nargs="+",
required=False,
help="List of header file paths to process. Relative to the project root.",
default=[
"core/iwasm/include/aot_comp_option.h",
"core/iwasm/include/aot_export.h",
"core/iwasm/include/gc_export.h",
"core/iwasm/include/lib_export.h",
"core/iwasm/include/wasm_c_api.h",
"core/iwasm/include/wasm_export.h",
],
)
return parser.parse_args()


def generate_checked_headers(header_paths):
"""Process each header file and generate checked versions."""
output_header = []
for input_header in header_paths:
input_path = Path(input_header)
output_path = input_path.with_name(input_path.stem + "_checked.h")

ast = parse_file(
str(input_path),
use_cpp=True,
cpp_path="gcc",
cpp_args=CPP_ARGS,
)

typedefs = extract_typedefs(ast)
functions = [
node
for node in ast.ext
if isinstance(node, c_ast.Decl) and isinstance(node.type, c_ast.FuncDecl)
]
functions = sorted(functions, key=lambda f: f.name)

return_types = {
" ".join(func.type.type.type.names)
for func in functions
if isinstance(func.type.type, c_ast.TypeDecl)
}
return_types = sorted(return_types)

result_struct = generate_result_struct(return_types)
write_checked_header(output_path, result_struct, functions, typedefs)
output_header.append(output_path)

return output_header


def main():
args = parse_arguments()
generated_headers = generate_checked_headers(args.headers)

# format the generated files using clang-format-14
for header in generated_headers:
subprocess.run(["clang-format-14", "--style=file", "-i", str(header)])


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions core/iwasm/include/aot_comp_option.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define __AOT_COMP_OPTION_H__

#include <stdint.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
Expand Down
Loading
Loading