summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Gamari <ben@smart-cactus.org>2022-04-05 23:09:47 -0400
committerBen Gamari <ben@smart-cactus.org>2022-04-06 15:30:56 -0400
commita59a66a8163cbbfa49a14f3c91af322f906084ea (patch)
treeebf3cc1d632b866f0f6f956c6a936997e34b4c70
parent5ad143fd8c448d7af3950f71f53feac6cd33f956 (diff)
downloadhaskell-wip/lint-rts-includes.tar.gz
testsuite: Lint RTS #includeswip/lint-rts-includes
Verifies two important properties of #includes in the RTS: * That system headers don't appear inside of a `<BeginPrivate.h>` block as this can hide system library symbols, resulting in very hard-to-diagnose linker errors * That no headers precede `Rts.h`, ensuring that __USE_MINGW_ANSI_STDIO is set correctly before system headers are included.
-rw-r--r--testsuite/tests/linters/Makefile3
-rw-r--r--testsuite/tests/linters/all.T6
-rwxr-xr-xtestsuite/tests/linters/regex-linters/check-rts-includes.py91
3 files changed, 99 insertions, 1 deletions
diff --git a/testsuite/tests/linters/Makefile b/testsuite/tests/linters/Makefile
index 54ef4db132..2b4c2ad2c3 100644
--- a/testsuite/tests/linters/Makefile
+++ b/testsuite/tests/linters/Makefile
@@ -20,6 +20,9 @@ version-number:
cpp:
(cd $(TOP)/tests/linters/ && python3 regex-linters/check-cpp.py tracked)
+rts-includes:
+ (cd $(TOP)/tests/linters/ && python3 regex-linters/check-rts-includes.py tracked)
+
changelogs:
regex-linters/check-changelogs.sh $(TOP)/..
diff --git a/testsuite/tests/linters/all.T b/testsuite/tests/linters/all.T
index 16700869a4..0e06df6d50 100644
--- a/testsuite/tests/linters/all.T
+++ b/testsuite/tests/linters/all.T
@@ -23,7 +23,11 @@ test('changelogs', [ no_deps if has_ls_files() else skip
test('cpp', [ no_deps if has_ls_files() else skip
, extra_files(["regex-linters"]) ]
- , makefile_test, ['cpp'])
+ , makefile_test, ['cpp'])
+
+test('rts-includes', [ no_deps if has_ls_files() else skip
+ , extra_files(["regex-linters"]) ]
+ , makefile_test, ['rts-includes'])
test('version-number', [ no_deps if has_ls_files() else skip
, extra_files(["regex-linters"]) ]
diff --git a/testsuite/tests/linters/regex-linters/check-rts-includes.py b/testsuite/tests/linters/regex-linters/check-rts-includes.py
new file mode 100755
index 0000000000..14f22995b6
--- /dev/null
+++ b/testsuite/tests/linters/regex-linters/check-rts-includes.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+
+# A linter to warn for ASSERT macros which are separated from their argument
+# list by a space, which Clang's CPP barfs on
+
+from pathlib import Path
+from linter import run_linters, Linter, Warning
+
+from typing import List, Tuple
+import re
+
+INCLUDE_RE = re.compile('# *include ([<"][^">]+[>"])')
+
+def get_includes(file: Path) -> List[Tuple[int, str]]:
+ txt = file.read_text()
+ return [ (line_no+1, m.group(1) )
+ for (line_no, line) in enumerate(txt.split('\n'))
+ for m in [INCLUDE_RE.match(line)]
+ if m is not None
+ if m.group(1) != "rts/PosixSource.h"]
+
+def in_rts_dir(path: Path) -> bool:
+ return len(path.parts) > 0 and path.parts[0] == 'rts'
+
+class RtsHIncludeOrderLinter(Linter):
+ """
+ Verify that "PosixSource.h" is always the first #include in source files to
+ ensure __USE_MINGW_ANSI_STDIO is defined before system headers are
+ #include'd.
+ """
+ def __init__(self):
+ Linter.__init__(self)
+ self.add_path_filter(in_rts_dir)
+ self.add_path_filter(lambda path: path.suffix == '.c')
+
+ def lint(self, path: Path):
+ # We do allow a few small headers to precede Rts.h
+ ALLOWED_HEADERS = {
+ '"ghcconfig.h"',
+ '"ghcplatform.h"',
+ }
+
+ includes = get_includes(path)
+ headers = [x[1] for x in includes]
+ lines = path.read_text().split('\n')
+
+ if '"PosixSource.h"' in headers:
+ for line_no, header in includes:
+ if header == '"PosixSource.h"':
+ break
+ elif header in ALLOWED_HEADERS:
+ continue
+
+ self.add_warning(Warning(
+ path=path,
+ line_no=line_no,
+ line_content=lines[line_no-1],
+ message="PosixSource.h must be first header included in each file"))
+
+class PrivateIncludeLinter(Linter):
+ """
+ Verify that system headers are not #include'd in <BeginPrivate.h> blocks as this
+ can result in very hard-to-diagnose linking errors due to hidden library functions.
+ """
+ def __init__(self):
+ Linter.__init__(self)
+ self.add_path_filter(in_rts_dir)
+ self.add_path_filter(lambda path: path.suffix == '.h')
+
+ def lint(self, path: Path):
+ private = False
+ lines = path.read_text().split('\n')
+ for line_no, include in get_includes(path):
+ if include == '"BeginPrivate.h"':
+ private = True
+ elif include == '"EndPrivate.h"':
+ private = False
+ elif private:
+ self.add_warning(Warning(
+ path=path,
+ line_no=line_no,
+ line_content=lines[line_no-1],
+ message='System header %s found inside of <BeginPrivate.h> block' % include))
+
+linters = [
+ RtsHIncludeOrderLinter(),
+ PrivateIncludeLinter(),
+]
+
+if __name__ == '__main__':
+ run_linters(linters)