From patchwork Thu Jun 3 15:48:25 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Richard Palethorpe X-Patchwork-Id: 1487291 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=213.254.12.146; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=suse.com header.i=@suse.com header.a=rsa-sha256 header.s=susede1 header.b=erUauZEO; dkim-atps=neutral Received: from picard.linux.it (picard.linux.it [213.254.12.146]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Fwr0k1TcZz9sPf for ; Fri, 4 Jun 2021 01:48:54 +1000 (AEST) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id DE0AC3C8F20 for ; Thu, 3 Jun 2021 17:48:51 +0200 (CEST) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-3.smtp.seeweb.it (in-3.smtp.seeweb.it [IPv6:2001:4b78:1:20::3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by picard.linux.it (Postfix) with ESMTPS id CCCD03C53D9 for ; Thu, 3 Jun 2021 17:48:37 +0200 (CEST) Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by in-3.smtp.seeweb.it (Postfix) with ESMTPS id B67011A0114D for ; Thu, 3 Jun 2021 17:48:36 +0200 (CEST) Received: from relay2.suse.de (unknown [149.44.160.134]) by smtp-out2.suse.de (Postfix) with ESMTP id CC0D71FD4E; Thu, 3 Jun 2021 15:48:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1622735315; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=S0f86rCgRpPImVP63H2Z/E6k/EkUjed1+uQ8Te13ZUs=; b=erUauZEORbtBebskm2wHYc8xVZSF15uOVdudCB6QCD7+iKflGB6m2JgQiYofk1AjhTfXtC Yr6lpkd5B8MdftctNr7rCLdtuli/9voTp5GPynwOKCZoVPniOk2BpTtRHs8ujwpf+RIrxD 32EgOVwFfta2BC/b/MenOE1olMsg5nk= Received: from g78.suse.de (unknown [10.163.24.38]) by relay2.suse.de (Postfix) with ESMTP id 9A8CBA3B81; Thu, 3 Jun 2021 15:48:35 +0000 (UTC) To: ltp@lists.linux.it Date: Thu, 3 Jun 2021 16:48:25 +0100 Message-Id: <20210603154825.30165-3-rpalethorpe@suse.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210603154825.30165-1-rpalethorpe@suse.com> References: <20210603154825.30165-1-rpalethorpe@suse.com> MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.102.4 at in-3.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.1 required=7.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_NONE,SPF_PASS autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on in-3.smtp.seeweb.it Subject: [LTP] [RFC PATCH 2/2] Start libclang based analyzer and TEST() check X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Richard Palethorpe via ltp From: Richard Palethorpe Reply-To: Richard Palethorpe Cc: Richard Palethorpe Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" 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. --- tools/clang-checks/main.c | 218 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 tools/clang-checks/main.c diff --git a/tools/clang-checks/main.c b/tools/clang-checks/main.c new file mode 100644 index 000000000..22df30b35 --- /dev/null +++ b/tools/clang-checks/main.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021 SUSE LLC + * + * 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 +#include +#include +#include +#include + +#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; +}