#!/usr/bin/env python3 # # Simple DirectMedia Layer # Copyright (C) 1997-2024 Sam Lantinga # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. # # This script detects use of stdlib function in SDL code import argparse import os import pathlib import re import sys SDL_ROOT = pathlib.Path(__file__).resolve().parents[1] STDLIB_SYMBOLS = [ 'abs', 'acos', 'acosf', 'asin', 'asinf', 'asprintf', 'atan', 'atan2', 'atan2f', 'atanf', 'atof', 'atoi', 'bsearch', 'calloc', 'ceil', 'ceilf', 'copysign', 'copysignf', 'cos', 'cosf', 'crc32', 'exp', 'expf', 'fabs', 'fabsf', 'floor', 'floorf', 'fmod', 'fmodf', 'free', 'getenv', 'isalnum', 'isalpha', 'isblank', 'iscntrl', 'isdigit', 'isgraph', 'islower', 'isprint', 'ispunct', 'isspace', 'isupper', 'isxdigit', 'itoa', 'lltoa', 'log10', 'log10f', 'logf', 'lround', 'lroundf', 'ltoa', 'malloc', 'memalign', 'memcmp', 'memcpy', 'memcpy4', 'memmove', 'memset', 'pow', 'powf', 'qsort', 'qsort_r', 'qsort_s', 'realloc', 'round', 'roundf', 'scalbn', 'scalbnf', 'setenv', 'sin', 'sinf', 'snprintf', 'sqrt', 'sqrtf', 'sscanf', 'strcasecmp', 'strchr', 'strcmp', 'strdup', 'strlcat', 'strlcpy', 'strlen', 'strlwr', 'strncasecmp', 'strncmp', 'strrchr', 'strrev', 'strstr', 'strtod', 'strtokr', 'strtol', 'strtoll', 'strtoul', 'strupr', 'tan', 'tanf', 'tolower', 'toupper', 'trunc', 'truncf', 'uitoa', 'ulltoa', 'ultoa', 'utf8strlcpy', 'utf8strlen', 'vasprintf', 'vsnprintf', 'vsscanf', 'wcscasecmp', 'wcscmp', 'wcsdup', 'wcslcat', 'wcslcpy', 'wcslen', 'wcsncasecmp', 'wcsncmp', 'wcsstr', ] RE_STDLIB_SYMBOL = re.compile(rf"\b(?P{'|'.join(STDLIB_SYMBOLS)})\b\(") def find_symbols_in_file(file: pathlib.Path) -> int: match_count = 0 allowed_extensions = [ ".c", ".cpp", ".m", ".h", ".hpp", ".cc" ] excluded_paths = [ "src/stdlib", "src/libm", "src/hidapi", "src/video/khronos", "include/SDL3", "build-scripts/gen_audio_resampler_filter.c", "build-scripts/gen_audio_channel_conversion.c", "test/win32/sdlprocdump.c", ] filename = pathlib.Path(file) for ep in excluded_paths: if ep in filename.as_posix(): # skip return 0 if filename.suffix not in allowed_extensions: # skip return 0 # print("Parse %s" % file) try: with file.open("r", encoding="UTF-8", newline="") as rfp: parsing_comment = False for line_i, original_line in enumerate(rfp, start=1): line = original_line.strip() line_comment = "" # Get the comment block /* ... */ across several lines while True: if parsing_comment: pos_end_comment = line.find("*/") if pos_end_comment >= 0: line = line[pos_end_comment+2:] parsing_comment = False else: break else: pos_start_comment = line.find("/*") if pos_start_comment >= 0: pos_end_comment = line.find("*/", pos_start_comment+2) if pos_end_comment >= 0: line_comment += line[pos_start_comment:pos_end_comment+2] line = line[:pos_start_comment] + line[pos_end_comment+2:] else: line_comment += line[pos_start_comment:] line = line[:pos_start_comment] parsing_comment = True break else: break if parsing_comment: continue pos_line_comment = line.find("//") if pos_line_comment >= 0: line_comment += line[pos_line_comment:] line = line[:pos_line_comment] if m := RE_STDLIB_SYMBOL.match(line): override_string = f"This should NOT be SDL_{m['symbol']}()" if override_string not in line_comment: print(f"{filename}:{line_i}") print(f" {line}") print(f"") match_count += 1 except UnicodeDecodeError: print(f"{file} is not text, skipping", file=sys.stderr) return match_count def find_symbols_in_dir(path: pathlib.Path) -> int: match_count = 0 for entry in path.glob("*"): if entry.is_dir(): match_count += find_symbols_in_dir(entry) else: match_count += find_symbols_in_file(entry) return match_count def main(): parser = argparse.ArgumentParser(fromfile_prefix_chars="@") parser.add_argument("path", default=SDL_ROOT, nargs="?", type=pathlib.Path, help="Path to look for stdlib symbols") args = parser.parse_args() print(f"Looking for stdlib usage in {args.path}...") match_count = find_symbols_in_dir(args.path) if match_count: print("If the stdlib usage is intentional, add a '// This should NOT be SDL_()' line comment.") print("") print("NOT OK") else: print("OK") return 1 if match_count else 0 if __name__ == "__main__": raise SystemExit(main())