commit 60e21469f7c368c2780b0c62b6927e8aed873447
parent 9eb9358a1ef702c07b6b7f5df44501a6163f3038
Author: serge-sans-paille <sguelton@mozilla.com>
Date: Wed, 7 Jan 2026 06:31:45 +0000
Bug 2007348 - Minimal C api linter r=ahal
Make sure stdio.h and stdlib.h are not included when they are not
actually used.
Differential Revision: https://phabricator.services.mozilla.com/D277318
Diffstat:
7 files changed, 226 insertions(+), 0 deletions(-)
diff --git a/tools/lint/includes/__init__.py b/tools/lint/includes/__init__.py
@@ -11,6 +11,7 @@ from mozlint import result
from mozlint.pathutils import expand_exclusions
from .std import api as std_api
+from .std import capi as std_capi
here = os.path.dirname(__file__)
with open(os.path.join(here, "..", "..", "..", "mfbt", "api.yml")) as fd:
@@ -133,6 +134,41 @@ def lint_std_headers(results, path, raw_content, config, fix):
)
+def lint_cstd_headers(results, path, raw_content, config, fix):
+ symbol_pattern = r"\b((std)?::)?{}\b"
+
+ for header, symbols in std_capi.items():
+ headerline = rf"#\s*include <({header}|c{header[:-2]})>"
+ if not (match := re.search(headerline, raw_content)):
+ continue
+ if re.search(
+ "|".join(symbol_pattern.format(symbol) for symbol in symbols), raw_content
+ ):
+ continue
+
+ msg = (
+ f"{path} includes <{match.group(1)}> but does not reference any of its API"
+ )
+ lineno = 1 + raw_content.count("\n", 0, match.start())
+
+ if fix:
+ fix_includes(path, raw_content, lineno)
+ results["fixed"] += 1
+ else:
+ diff = generate_diff(path, raw_content, lineno)
+
+ results["results"].append(
+ result.from_config(
+ config,
+ path=path,
+ message=msg,
+ level="error",
+ lineno=lineno,
+ diff=diff,
+ )
+ )
+
+
def lint(paths, config, **lintargs):
results = {"results": [], "fixed": 0}
paths = list(expand_exclusions(paths, config, lintargs["root"]))
@@ -147,5 +183,6 @@ def lint(paths, config, **lintargs):
lint_mfbt_headers(results, path, raw_content, config, fix)
lint_std_headers(results, path, raw_content, config, fix)
+ lint_cstd_headers(results, path, raw_content, config, fix)
return results
diff --git a/tools/lint/includes/std.py b/tools/lint/includes/std.py
@@ -422,3 +422,152 @@ api = {
api["type_traits"].extend(
[f"{k}_v" for k in api["type_traits"]] + [f"{k}_t" for k in api["type_traits"]]
)
+
+capi = {
+ "stdio.h": [
+ # macros
+ "BUFSIZ",
+ "EOF",
+ "FILENAME_MAX",
+ "FOPEN_MAX",
+ "L_ctermid",
+ "L_cuserid",
+ "L_tmpnam",
+ "NULL",
+ "SEEK_CUR",
+ "SEEK_END",
+ "SEEK_SET",
+ "TMP_MAX",
+ "clearerr",
+ "feof",
+ "ferror",
+ "fileno",
+ "getc",
+ "getchar",
+ "putc",
+ "putchar",
+ "stderr",
+ "stdin",
+ "stdout",
+ # typedef
+ "FILE",
+ # functions
+ "clearerr",
+ "fclose",
+ "fdopen",
+ "feof",
+ "ferror",
+ "fflush",
+ "fgetc",
+ "fgetpos",
+ "fgets",
+ "fileno",
+ "fmemopen",
+ "fopen",
+ "fopencookie",
+ "fprintf",
+ "fpurge",
+ "fputc",
+ "fputs",
+ "fread",
+ "freopen",
+ "fscanf",
+ "fseek",
+ "fseeko",
+ "fsetpos",
+ "ftell",
+ "ftello",
+ "fwrite",
+ "getc",
+ "getchar",
+ "gets",
+ "getw",
+ "mktemp",
+ "open_memstream",
+ "open_wmemstream",
+ "perror",
+ "printf",
+ "putc",
+ "putchar",
+ "puts",
+ "putw",
+ "remove",
+ "rewind",
+ "scanf",
+ "setbuf",
+ "setbuffer",
+ "setlinebuf",
+ "setvbuf",
+ "snprintf",
+ "snwprintf",
+ "sprintf",
+ "sscanf",
+ "strerror",
+ "sys_errlist",
+ "sys_nerr",
+ "tempnam",
+ "tmpfile",
+ "tmpnam",
+ "ungetc",
+ "vfprintf",
+ "vfscanf",
+ "vprintf",
+ "vscanf",
+ "vsnprintf",
+ "vsprintf",
+ "vsscanf",
+ ],
+ "stdlib.h": [
+ "_Exit",
+ "_exit",
+ "_wtoi",
+ "_wtoi_l",
+ "abort",
+ "abs",
+ "aligned_alloc",
+ "at_quick_exit",
+ "atexit",
+ "atof",
+ "atoi",
+ "atol",
+ "atoll",
+ "bsearch",
+ "call_once",
+ "calloc",
+ "div",
+ "div_t",
+ "exit",
+ "free",
+ "free_aligned_sized",
+ "free_sized",
+ "getenv",
+ "labs",
+ "ldiv",
+ "llabs",
+ "lldiv",
+ "malloc",
+ "mblen",
+ "mbstowcs",
+ "mbtowc",
+ "memalignment",
+ "putenv",
+ "qsort",
+ "quick_exit",
+ "rand",
+ "realloc",
+ "srand",
+ "strfromd",
+ "strfromf",
+ "strfroml",
+ "strtod",
+ "strtof",
+ "strtol",
+ "strtold",
+ "strtoll",
+ "strtoul",
+ "strtoull",
+ "system",
+ "wcstombs",
+ "wctomb",
+ ],
+}
diff --git a/tools/lint/test/files/includes/correct_cstdio.h b/tools/lint/test/files/includes/correct_cstdio.h
@@ -0,0 +1,5 @@
+#include <cstdio>
+
+auto foo() {
+ fputs("yeah");
+}
diff --git a/tools/lint/test/files/includes/correct_stdio.h b/tools/lint/test/files/includes/correct_stdio.h
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+auto foo() {
+ fputs("yeah");
+}
diff --git a/tools/lint/test/files/includes/incorrect_cstdio.h b/tools/lint/test/files/includes/incorrect_cstdio.h
@@ -0,0 +1,5 @@
+#include <cstdio>
+
+auto foo() {
+ return 1;
+}
diff --git a/tools/lint/test/files/includes/incorrect_stdio.h b/tools/lint/test/files/includes/incorrect_stdio.h
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+auto foo() {
+ return 0;
+}
diff --git a/tools/lint/test/test_includes.py b/tools/lint/test/test_includes.py
@@ -91,5 +91,25 @@ def test_lint_std_includes(lint, paths):
)
+def test_lint_c_std_includes(lint, paths):
+ results = lint(paths("correct_stdio.h"))
+ assert not results
+
+ results = lint(paths("correct_cstdio.h"))
+ assert not results
+
+ results = lint(paths("incorrect_stdio.h"))
+ assert len(results) == 1
+ assert results[0].message.endswith(
+ "incorrect_stdio.h includes <stdio.h> but does not reference any of its API"
+ )
+
+ results = lint(paths("incorrect_cstdio.h"))
+ assert len(results) == 1
+ assert results[0].message.endswith(
+ "incorrect_cstdio.h includes <cstdio> but does not reference any of its API"
+ )
+
+
if __name__ == "__main__":
mozunit.main()