From a59a66a8163cbbfa49a14f3c91af322f906084ea Mon Sep 17 00:00:00 2001 From: Ben Gamari <ben@smart-cactus.org> Date: Tue, 5 Apr 2022 23:09:47 -0400 Subject: [PATCH] testsuite: 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. --- testsuite/tests/linters/Makefile | 3 + testsuite/tests/linters/all.T | 6 +- .../regex-linters/check-rts-includes.py | 91 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100755 testsuite/tests/linters/regex-linters/check-rts-includes.py diff --git a/testsuite/tests/linters/Makefile b/testsuite/tests/linters/Makefile index 54ef4db1324d..2b4c2ad2c37c 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 16700869a470..0e06df6d501a 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 000000000000..14f22995b677 --- /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) -- GitLab