All of lore.kernel.org
 help / color / mirror / Atom feed
From: Richard Palethorpe <rpalethorpe@suse.com>
To: ltp@lists.linux.it
Subject: [LTP] [RFC PATCH v2 2/2] Start libclang based analyzer and TEST() check
Date: Fri,  4 Jun 2021 12:14:34 +0100	[thread overview]
Message-ID: <20210604111434.21422-3-rpalethorpe@suse.com> (raw)
In-Reply-To: <20210604111434.21422-1-rpalethorpe@suse.com>

This uses the stable Clang C API to find usages of the TEST()
macro. It can also determine if a translation unit is a test
executable by finding the struct tst_test test instantiation.

This Clang API only exposes the AST along with some other utilities
for evaluating constants, indexing, auto completion and source
rewriting. This is somewhat less than what Smatch, Coccinelle and the
unstable Clang C++ APIs expose. However it is a simple, stable and
well supported C API.

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 tools/clang-check/main.c | 239 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 239 insertions(+)
 create mode 100644 tools/clang-check/main.c

diff --git a/tools/clang-check/main.c b/tools/clang-check/main.c
new file mode 100644
index 000000000..26ce898c3
--- /dev/null
+++ b/tools/clang-check/main.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 SUSE LLC <rpalethorpe@suse.com>
+ * Copyright (c) 2017 Petr Vorel <pvorel@suse.cz>
+ *
+ * Entry point for the LTP static analyser.
+ *
+ * Scans the AST (Abstract Syntax Tree) generated by Clang
+ * twice. First pass we just collect info about the TU (Translation
+ * Unit). Second pass performs the checks.
+ *
+ * AST Nodes are called CXCursor by libclang. We use the library's
+ * visitor functions to recurse into the AST.
+ *
+ * The kind of AST node decides which checks to run on it.
+ *
+ * This program takes the same arguments the Clang compiler
+ * frontend does. Although some are ignored by libclang.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <clang-c/Index.h>
+
+#define attr_unused __attribute__((unused))
+
+/* The rules for test, library and tool code are different */
+enum ltp_tu_kind {
+	LTP_TEST,
+	LTP_OTHER,
+};
+
+/* Holds information about the TU which we gathered on the first pass */
+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;
+
+/* Copied from lib/tst_ansi_color.c */
+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.
+ *
+ * This check takes an AST node which should already be known to be a
+ * macro expansion kind.
+ *
+ * If the TU appears to be a test executable then the test does not
+ * apply. So in that case we return.
+ *
+ * If the macro expansion AST node is spelled TEST, then we emit an
+ * error. Otherwise do nothing.
+ */
+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;
+}
-- 
2.31.1


  parent reply	other threads:[~2021-06-04 11:14 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-06-04 11:14 [LTP] [RFC PATCH v2 0/2] Libclang based analyzer Richard Palethorpe
2021-06-04 11:14 ` [LTP] [RFC PATCH v2 1/2] Add 'make check' and clang-check to build system Richard Palethorpe
2021-06-04 14:10   ` Petr Vorel
2021-06-04 14:11     ` Cyril Hrubis
2021-06-04 14:28     ` Richard Palethorpe
2021-06-04 14:33       ` Cyril Hrubis
2021-06-07  9:18   ` Joerg Vehlow
2021-06-07  9:12     ` Cyril Hrubis
2021-06-11 13:49   ` Petr Vorel
2021-06-11 14:17     ` Petr Vorel
2021-06-14 11:09       ` Richard Palethorpe
2021-06-04 11:14 ` Richard Palethorpe [this message]
2021-06-04 12:52 ` [LTP] [RFC PATCH v2 0/2] Libclang based analyzer Cyril Hrubis
2021-06-04 13:50   ` Richard Palethorpe
2021-06-07  8:37 ` Joerg Vehlow
2021-06-07  9:14   ` Cyril Hrubis
2021-06-07 10:20   ` Richard Palethorpe
2021-06-07 11:34     ` Joerg Vehlow
2021-06-07 13:42       ` Cyril Hrubis
2021-06-07 13:49       ` Richard Palethorpe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210604111434.21422-3-rpalethorpe@suse.com \
    --to=rpalethorpe@suse.com \
    --cc=ltp@lists.linux.it \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.