new file mode 100644
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 SUSE LLC <rpalethorpe@suse.com>
+ *
+ * Entry point for the LTP static analyser.
+ *
+ * Scans the AST generated by Clang twice. First pass we just collect
+ * info about the TU (Translation Unit). Second pass performs the
+ * checks.
+ *
+ * This program takes the same arguments the Clang compiler does.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <clang-c/Index.h>
+
+#define attr_unused __attribute__((unused))
+
+enum ltp_tu_kind {
+ LTP_TEST,
+ LTP_OTHER,
+};
+
+static struct {
+ enum ltp_tu_kind tu_kind;
+} tu_info;
+
+static const char *const ansi_red = "\033[1;31m";
+static const char *const ansi_reset = "\033[0m";
+static const char *const ansi_bold = "\033[1m";
+
+static unsigned error_flag;
+
+static int color_enabled(const int fd)
+{
+ static int color;
+
+ if (color)
+ return color - 1;
+
+ const char *const env = getenv("LTP_COLORIZE_OUTPUT");
+
+ if (env) {
+ if (!strcmp(env, "n") || !strcmp(env, "0"))
+ color = 1;
+
+ if (!strcmp(env, "y") || !strcmp(env, "1"))
+ color = 2;
+
+ return color - 1;
+ }
+
+ if (isatty(fd) == 0)
+ color = 1;
+ else
+ color = 2;
+
+ return color - 1;
+}
+
+static void emit_error(CXCursor offending_cursor, const char *const error_msg)
+{
+ CXSourceLocation loc = clang_getCursorLocation(offending_cursor);
+ CXFile loc_file;
+ unsigned loc_line, loc_column;
+ CXString file_name;
+
+ error_flag = 1;
+
+ clang_getFileLocation(loc, &loc_file, &loc_line, &loc_column,
+ /*offset=*/NULL);
+ file_name = clang_getFileName(loc_file);
+
+ if (color_enabled(STDERR_FILENO)) {
+ dprintf(STDERR_FILENO,
+ "%s:%u:%u: %sCHECK ERROR%s: %s%s%s\n",
+ clang_getCString(file_name), loc_line, loc_column,
+ ansi_red, ansi_reset,
+ ansi_bold, error_msg, ansi_reset);
+ } else {
+ dprintf(STDERR_FILENO,
+ "%s:%u:%u: CHECK ERROR: %s\n",
+ clang_getCString(file_name), loc_line, loc_column,
+ error_msg);
+ }
+
+ clang_disposeString(file_name);
+}
+
+static int cursor_cmp_spelling(const char *const spelling, CXCursor cursor)
+{
+ CXString cursor_spelling = clang_getCursorSpelling(cursor);
+ const int ret = strcmp(spelling, clang_getCString(cursor_spelling));
+
+ clang_disposeString(cursor_spelling);
+
+ return ret;
+}
+
+static int cursor_type_cmp_spelling(const char *const spelling, CXCursor cursor)
+{
+ CXType ctype = clang_getCursorType(cursor);
+ CXString ctype_spelling = clang_getTypeSpelling(ctype);
+ const int ret = strcmp(spelling, clang_getCString(ctype_spelling));
+
+ clang_disposeString(ctype_spelling);
+
+ return ret;
+}
+
+/* Check if the TEST() macro is used inside the library */
+static void check_TEST_macro(CXCursor macro_cursor)
+{
+ if (tu_info.tu_kind == LTP_TEST)
+ return;
+
+ if (!cursor_cmp_spelling("TEST", macro_cursor)) {
+ emit_error(macro_cursor,
+ "TEST() macro should not be used in library");
+ }
+}
+
+/* Second pass where we run the checks */
+static enum CXChildVisitResult check_visitor(CXCursor cursor,
+ attr_unused CXCursor parent,
+ attr_unused CXClientData client_data)
+{
+ CXSourceLocation loc = clang_getCursorLocation(cursor);
+
+ if (clang_Location_isInSystemHeader(loc))
+ return CXChildVisit_Continue;
+
+ switch (clang_getCursorKind(cursor)) {
+ case CXCursor_MacroExpansion:
+ check_TEST_macro(cursor);
+ break;
+ default:
+ break;
+ }
+
+ return CXChildVisit_Recurse;
+}
+
+/* If we find `struct tst_test = {...}` then record that this TU is a test */
+static void info_ltp_tu_kind(CXCursor cursor)
+{
+ CXCursor initializer;
+
+ if (clang_Cursor_hasVarDeclGlobalStorage(cursor) != 1)
+ return;
+
+ if (cursor_cmp_spelling("test", cursor))
+ return;
+
+ if (cursor_type_cmp_spelling("struct tst_test", cursor))
+ return;
+
+ initializer = clang_Cursor_getVarDeclInitializer(cursor);
+
+ if (!clang_Cursor_isNull(initializer))
+ tu_info.tu_kind = LTP_TEST;
+}
+
+/* First pass to collect info */
+static enum CXChildVisitResult info_visitor(CXCursor cursor,
+ attr_unused CXCursor parent,
+ attr_unused CXClientData client_data)
+{
+ CXSourceLocation loc = clang_getCursorLocation(cursor);
+
+ if (clang_Location_isInSystemHeader(loc))
+ return CXChildVisit_Continue;
+
+ switch (clang_getCursorKind(cursor)) {
+ case CXCursor_VarDecl:
+ info_ltp_tu_kind(cursor);
+ break;
+ default:
+ break;
+ }
+
+ return CXChildVisit_Continue;
+}
+
+int main(const int argc, const char *const *const argv)
+{
+ CXIndex cindex = clang_createIndex(0, 1);
+ CXTranslationUnit tu;
+ CXCursor tuc;
+
+ enum CXErrorCode ret = clang_parseTranslationUnit2(
+ cindex,
+ /*source_filename=*/NULL,
+ argv + 1, argc - 1,
+ /*unsaved_files=*/NULL, /*num_unsaved_files=*/0,
+ CXTranslationUnit_DetailedPreprocessingRecord,
+ &tu);
+
+ if (ret != CXError_Success) {
+ printf("Failed to load translation unit: %d\n", ret);
+ return 1;
+ }
+
+ tuc = clang_getTranslationUnitCursor(tu);
+
+ tu_info.tu_kind = LTP_OTHER;
+ clang_visitChildren(tuc, info_visitor, NULL);
+
+ clang_visitChildren(tuc, check_visitor, NULL);
+
+ /* Stop leak sanitizer from complaining */
+ clang_disposeTranslationUnit(tu);
+ clang_disposeIndex(cindex);
+
+ return error_flag;
+}