1#!/usr/bin/env python3
2#
3# Simple DirectMedia Layer
4# Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
5#
6# This software is provided 'as-is', without any express or implied
7# warranty. In no event will the authors be held liable for any damages
8# arising from the use of this software.
9#
10# Permission is granted to anyone to use this software for any purpose,
11# including commercial applications, and to alter it and redistribute it
12# freely, subject to the following restrictions:
13#
14# 1. The origin of this software must not be misrepresented; you must not
15# claim that you wrote the original software. If you use this software
16# in a product, an acknowledgment in the product documentation would be
17# appreciated but is not required.
18# 2. Altered source versions must be plainly marked as such, and must not be
19# misrepresented as being the original software.
20# 3. This notice may not be removed or altered from any source distribution.
21#
22# This script detects use of stdlib function in SDL code
23
24import argparse
25import os
26import pathlib
27import re
28import sys
29
30SDL_ROOT = pathlib.Path(__file__).resolve().parents[1]
31
32STDLIB_SYMBOLS = [
33 'abs',
34 'acos',
35 'acosf',
36 'asin',
37 'asinf',
38 'asprintf',
39 'atan',
40 'atan2',
41 'atan2f',
42 'atanf',
43 'atof',
44 'atoi',
45 'bsearch',
46 'calloc',
47 'ceil',
48 'ceilf',
49 'copysign',
50 'copysignf',
51 'cos',
52 'cosf',
53 'crc32',
54 'exp',
55 'expf',
56 'fabs',
57 'fabsf',
58 'floor',
59 'floorf',
60 'fmod',
61 'fmodf',
62 'free',
63 'getenv',
64 'isalnum',
65 'isalpha',
66 'isblank',
67 'iscntrl',
68 'isdigit',
69 'isgraph',
70 'islower',
71 'isprint',
72 'ispunct',
73 'isspace',
74 'isupper',
75 'isxdigit',
76 'itoa',
77 'lltoa',
78 'log10',
79 'log10f',
80 'logf',
81 'lround',
82 'lroundf',
83 'ltoa',
84 'malloc',
85 'memalign',
86 'memcmp',
87 'memcpy',
88 'memcpy4',
89 'memmove',
90 'memset',
91 'pow',
92 'powf',
93 'qsort',
94 'qsort_r',
95 'qsort_s',
96 'realloc',
97 'round',
98 'roundf',
99 'scalbn',
100 'scalbnf',
101 'setenv',
102 'sin',
103 'sinf',
104 'snprintf',
105 'sqrt',
106 'sqrtf',
107 'sscanf',
108 'strcasecmp',
109 'strchr',
110 'strcmp',
111 'strdup',
112 'strlcat',
113 'strlcpy',
114 'strlen',
115 'strlwr',
116 'strncasecmp',
117 'strncmp',
118 'strrchr',
119 'strrev',
120 'strstr',
121 'strtod',
122 'strtokr',
123 'strtol',
124 'strtoll',
125 'strtoul',
126 'strupr',
127 'tan',
128 'tanf',
129 'tolower',
130 'toupper',
131 'trunc',
132 'truncf',
133 'uitoa',
134 'ulltoa',
135 'ultoa',
136 'utf8strlcpy',
137 'utf8strlen',
138 'vasprintf',
139 'vsnprintf',
140 'vsscanf',
141 'wcscasecmp',
142 'wcscmp',
143 'wcsdup',
144 'wcslcat',
145 'wcslcpy',
146 'wcslen',
147 'wcsncasecmp',
148 'wcsncmp',
149 'wcsstr',
150]
151RE_STDLIB_SYMBOL = re.compile(rf"\b(?P<symbol>{'|'.join(STDLIB_SYMBOLS)})\b\(")
152
153
154def find_symbols_in_file(file: pathlib.Path) -> int:
155 match_count = 0
156
157 allowed_extensions = [ ".c", ".cpp", ".m", ".h", ".hpp", ".cc" ]
158
159 excluded_paths = [
160 "src/stdlib",
161 "src/libm",
162 "src/hidapi",
163 "src/video/khronos",
164 "include/SDL3",
165 "build-scripts/gen_audio_resampler_filter.c",
166 "build-scripts/gen_audio_channel_conversion.c",
167 "test/win32/sdlprocdump.c",
168 ]
169
170 filename = pathlib.Path(file)
171
172 for ep in excluded_paths:
173 if ep in filename.as_posix():
174 # skip
175 return 0
176
177 if filename.suffix not in allowed_extensions:
178 # skip
179 return 0
180
181 # print("Parse %s" % file)
182
183 try:
184 with file.open("r", encoding="UTF-8", newline="") as rfp:
185 parsing_comment = False
186 for line_i, original_line in enumerate(rfp, start=1):
187 line = original_line.strip()
188
189 line_comment = ""
190
191 # Get the comment block /* ... */ across several lines
192 while True:
193 if parsing_comment:
194 pos_end_comment = line.find("*/")
195 if pos_end_comment >= 0:
196 line = line[pos_end_comment+2:]
197 parsing_comment = False
198 else:
199 break
200 else:
201 pos_start_comment = line.find("/*")
202 if pos_start_comment >= 0:
203 pos_end_comment = line.find("*/", pos_start_comment+2)
204 if pos_end_comment >= 0:
205 line_comment += line[pos_start_comment:pos_end_comment+2]
206 line = line[:pos_start_comment] + line[pos_end_comment+2:]
207 else:
208 line_comment += line[pos_start_comment:]
209 line = line[:pos_start_comment]
210 parsing_comment = True
211 break
212 else:
213 break
214 if parsing_comment:
215 continue
216 pos_line_comment = line.find("//")
217 if pos_line_comment >= 0:
218 line_comment += line[pos_line_comment:]
219 line = line[:pos_line_comment]
220
221 if m := RE_STDLIB_SYMBOL.match(line):
222 override_string = f"This should NOT be SDL_{m['symbol']}()"
223 if override_string not in line_comment:
224 print(f"{filename}:{line_i}")
225 print(f" {line}")
226 print(f"")
227 match_count += 1
228
229 except UnicodeDecodeError:
230 print(f"{file} is not text, skipping", file=sys.stderr)
231
232 return match_count
233
234def find_symbols_in_dir(path: pathlib.Path) -> int:
235 match_count = 0
236 for entry in path.glob("*"):
237 if entry.is_dir():
238 match_count += find_symbols_in_dir(entry)
239 else:
240 match_count += find_symbols_in_file(entry)
241 return match_count
242
243def main():
244 parser = argparse.ArgumentParser(fromfile_prefix_chars="@")
245 parser.add_argument("path", default=SDL_ROOT, nargs="?", type=pathlib.Path, help="Path to look for stdlib symbols")
246 args = parser.parse_args()
247
248 print(f"Looking for stdlib usage in {args.path}...")
249
250 match_count = find_symbols_in_dir(args.path)
251
252 if match_count:
253 print("If the stdlib usage is intentional, add a '// This should NOT be SDL_<symbol>()' line comment.")
254 print("")
255 print("NOT OK")
256 else:
257 print("OK")
258 return 1 if match_count else 0
259
260if __name__ == "__main__":
261 raise SystemExit(main())