SDL: Script to detect use of stdlib function

From 70ccf3458701d8563f4b213b1713eafcc052e34e Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Tue, 27 Jun 2023 12:19:02 +0200
Subject: [PATCH] Script to detect use of stdlib function

---
 build-scripts/stdlib_checks.py | 259 +++++++++++++++++++++++++++++++++
 1 file changed, 259 insertions(+)
 create mode 100755 build-scripts/stdlib_checks.py

diff --git a/build-scripts/stdlib_checks.py b/build-scripts/stdlib_checks.py
new file mode 100755
index 000000000000..d710c9096797
--- /dev/null
+++ b/build-scripts/stdlib_checks.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+#
+#  Simple DirectMedia Layer
+#  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+#
+#  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]
+
+words = [
+    'abs',
+    'acos',
+    'acosf',
+    'asin',
+    'asinf',
+    'asprintf',
+    'atan',
+    'atan2',
+    'atan2f',
+    'atanf',
+    'atof',
+    'atoi',
+    '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',
+    'memcmp',
+    'memcpy',
+    'memcpy4',
+    'memmove',
+    'memset',
+    'pow',
+    'powf',
+    'qsort',
+    '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' ]
+
+
+reg_comment_remove_content = re.compile('\/\*.*\*/')
+reg_comment_remove_content2 = re.compile('".*"')
+reg_comment_remove_content3 = re.compile(':strlen')
+reg_comment_remove_content4 = re.compile('->free')
+
+def find_symbols_in_file(file, regex):
+
+    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" ]
+
+    filename = pathlib.Path(file)
+
+    for ep in excluded_paths:
+        if ep in filename.as_posix():
+            # skip
+            return
+
+    if filename.suffix not in allowed_extensions:
+        # skip
+        return
+
+    # print("Parse %s" % file)
+
+    try:
+        with file.open("r", encoding="UTF-8", newline="") as rfp:
+            parsing_comment = False
+            for l in rfp:
+                l = l.strip()
+
+                # Get the comment block /* ... */ across several lines
+                match_start = "/*" in l
+                match_end = "*/" in l
+                if match_start and match_end:
+                    continue
+                if match_start:
+                    parsing_comment = True
+                    continue
+                if match_end:
+                    parsing_comment = False
+                    continue
+                if parsing_comment:
+                    continue
+
+                if regex.match(l):
+
+                    # free() allowed here
+                    if "This should NOT be SDL_free" in l:
+                        continue
+
+                    # double check
+                    # Remove one line comment /* ... */
+                    # eg: extern DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path, int bExclusive /* = false */);
+                    l = reg_comment_remove_content.sub('', l)
+
+                    # Remove strings " ... "
+                    l = reg_comment_remove_content2.sub('', l)
+
+                    # :strlen
+                    l = reg_comment_remove_content3.sub('', l)
+
+                    # ->free
+                    l = reg_comment_remove_content4.sub('', l)
+
+                    if regex.match(l):
+                        print("File %s" % filename)
+                        print("        %s" % l)
+                        print("")
+
+    except UnicodeDecodeError:
+        print("%s is not text, skipping" % file)
+    except Exception as err:
+        print("%s" % err)
+
+def find_symbols_in_dir(path, regex):
+
+    for entry in path.glob("*"):
+        if entry.is_dir():
+            find_symbols_in_dir(entry, regex)
+        else:
+            find_symbols_in_file(entry, regex)
+
+def main():
+    str = ".*\\b("
+    for w in words:
+        str += w + "|"
+    str = str[:-1]
+    str += ")\("
+    regex = re.compile(str)
+    find_symbols_in_dir(SDL_ROOT, regex)
+
+if __name__ == "__main__":
+
+    parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
+    args = parser.parse_args()
+
+    try:
+        main()
+    except Exception as e:
+        print(e)
+        exit(-1)
+
+    exit(0)
+
+