unplugged-system/external/ltp/tools/sparse/sparse-ltp.c

325 lines
7.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 SUSE LLC <rpalethorpe@suse.com>
*
* Sparse allows us to perform checks on the AST (struct symbol) or on
* a linearized representation. In the latter case we are given a set
* of entry points (functions) containing basic blocks of
* instructions.
*
* The basic blocks contain byte code in SSA form. This is similar to
* the intermediate representation most compilers use during
* optimisation.
*/
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include "lib.h"
#include "allocate.h"
#include "opcode.h"
#include "token.h"
#include "parse.h"
#include "symbol.h"
#include "expression.h"
#include "linearize.h"
/* The rules for test, library and tool code are different */
enum ltp_tu_kind {
LTP_LIB,
LTP_OTHER,
};
static enum ltp_tu_kind tu_kind = LTP_OTHER;
/* Check for LTP-002
*
* Inspects the destination symbol of each store instruction. If it is
* TST_RET or TST_ERR then emit a warning.
*/
static void check_lib_sets_TEST_vars(const struct instruction *insn)
{
static struct ident *TST_RES_id, *TST_ERR_id;
if (!TST_RES_id) {
TST_RES_id = built_in_ident("TST_RET");
TST_ERR_id = built_in_ident("TST_ERR");
}
if (insn->opcode != OP_STORE)
return;
if (insn->src->ident != TST_RES_id &&
insn->src->ident != TST_ERR_id)
return;
warning(insn->pos,
"LTP-002: Library should not write to TST_RET or TST_ERR");
}
static void do_basicblock_checks(struct basic_block *bb)
{
struct instruction *insn;
FOR_EACH_PTR(bb->insns, insn) {
if (!bb_reachable(insn->bb))
continue;
if (tu_kind == LTP_LIB)
check_lib_sets_TEST_vars(insn);
} END_FOR_EACH_PTR(insn);
}
static void do_entrypoint_checks(struct entrypoint *ep)
{
struct basic_block *bb;
FOR_EACH_PTR(ep->bbs, bb) {
do_basicblock_checks(bb);
} END_FOR_EACH_PTR(bb);
}
/* The old API can not comply with the rules. So when we see one of
* these symbols we know that it will result in further
* warnings. Probably these will suggest inappropriate things. Usually
* these symbols should be removed and the new API used
* instead. Otherwise they can be ignored until all tests have been
* converted to the new API.
*/
static bool check_symbol_deprecated(const struct symbol *const sym)
{
static struct ident *TCID_id, *TST_TOTAL_id;
const struct ident *id = sym->ident;
if (!TCID_id) {
TCID_id = built_in_ident("TCID");
TST_TOTAL_id = built_in_ident("TST_TOTAL");
}
if (id != TCID_id && id != TST_TOTAL_id)
return false;
warning(sym->pos,
"Ignoring deprecated API symbol: '%s'. Should this code be converted to the new API?",
show_ident(id));
return true;
}
/* Check for LTP-003 and LTP-004
*
* Try to find cases where the static keyword was forgotten.
*/
static void check_symbol_visibility(const struct symbol *const sym)
{
const unsigned long mod = sym->ctype.modifiers;
const char *const name = show_ident(sym->ident);
const int has_lib_prefix = !strncmp("tst_", name, 4) ||
!strncmp("TST_", name, 4) ||
!strncmp("ltp_", name, 4) ||
!strncmp("safe_", name, 5);
if (!(mod & MOD_TOPLEVEL))
return;
if (has_lib_prefix && (mod & MOD_STATIC) && !(mod & MOD_INLINE)) {
warning(sym->pos,
"LTP-003: Symbol '%s' has the LTP public library prefix, but is static (private).",
name);
return;
}
if ((mod & MOD_STATIC))
return;
if (tu_kind == LTP_LIB && !has_lib_prefix) {
warning(sym->pos,
"LTP-003: Symbol '%s' is a public library function, but is missing the 'tst_' prefix",
name);
return;
}
if (sym->same_symbol)
return;
if (sym->ident == &main_ident)
return;
warning(sym->pos,
"Symbol '%s' has no prototype or library ('tst_') prefix. Should it be static?",
name);
}
/* See base_type() in dissect.c */
static struct symbol *unwrap_base_type(const struct symbol *sym)
{
switch (sym->ctype.base_type->type) {
case SYM_ARRAY:
case SYM_NODE:
case SYM_PTR:
return unwrap_base_type(sym->ctype.base_type);
default:
return sym->ctype.base_type;
}
}
/* Checks if some struct array initializer is terminated with a blank
* (zeroed) item i.e. {}
*/
static bool is_terminated_with_null_struct(const struct symbol *const sym)
{
const struct expression *const arr_init = sym->initializer;
const struct expression *item_init =
last_ptr_list((struct ptr_list *)arr_init->expr_list);
if (item_init->type == EXPR_POS)
item_init = item_init->init_expr;
if (item_init->type != EXPR_INITIALIZER)
return false;
return ptr_list_empty((struct ptr_list *)item_init->expr_list);
}
/* LTP-005: Check array sentinel value
*
* This is most important for the tags array. It is only accessed when
* the test fails. So we perform a static check to ensure it ends with
* {}.
*/
static void check_struct_array_initializer(const struct symbol *const sym)
{
if (is_terminated_with_null_struct(sym))
return;
warning(sym->pos,
"LTP-005: Struct array doesn't appear to be null-terminated; did you forget to add '{}' as the final entry?");
}
/* Find struct tst_test test = { ... } and perform tests on its initializer */
static void check_test_struct(const struct symbol *const sym)
{
static struct ident *tst_test, *tst_test_test;
struct ident *ctype_name = NULL;
struct expression *init = sym->initializer;
struct expression *entry;
if (!sym->ctype.base_type)
return;
ctype_name = sym->ctype.base_type->ident;
if (!init)
return;
if (!tst_test_test) {
tst_test = built_in_ident("tst_test");
tst_test_test = built_in_ident("test");
}
if (sym->ident != tst_test_test)
return;
if (ctype_name != tst_test)
return;
FOR_EACH_PTR(init->expr_list, entry) {
if (entry->init_expr->type != EXPR_SYMBOL)
continue;
switch (entry->ctype->ctype.base_type->type) {
case SYM_PTR:
case SYM_ARRAY:
break;
default:
return;
}
const struct symbol *entry_init = entry->init_expr->symbol;
const struct symbol *entry_ctype = unwrap_base_type(entry_init);
if (entry_ctype->type == SYM_STRUCT)
check_struct_array_initializer(entry_init);
} END_FOR_EACH_PTR(entry);
}
/* AST level checks */
static void do_symbol_checks(struct symbol *sym)
{
if (check_symbol_deprecated(sym))
return;
check_symbol_visibility(sym);
check_test_struct(sym);
}
/* Compile the AST into a graph of basicblocks */
static void process_symbols(struct symbol_list *list)
{
struct symbol *sym;
FOR_EACH_PTR(list, sym) {
struct entrypoint *ep;
do_symbol_checks(sym);
expand_symbol(sym);
ep = linearize_symbol(sym);
if (!ep || !ep->entry)
continue;
do_entrypoint_checks(ep);
if (dbg_entry)
show_entry(ep);
} END_FOR_EACH_PTR(sym);
}
static void collect_info_from_args(const int argc, char *const *const argv)
{
int i;
for (i = 0; i < argc; i++) {
if (!strcmp("-DLTPLIB", argv[i]))
tu_kind = LTP_LIB;
}
}
int main(int argc, char **argv)
{
struct string_list *filelist = NULL;
char *file;
Waddress_space = 0;
Wbitwise = 0;
Wcast_truncate = 0;
Wcontext = 0;
Wdecl = 0;
Wexternal_function_has_definition = 0;
Wflexible_array_array = 0;
Wimplicit_int = 0;
Wint_to_pointer_cast = 0;
Wmemcpy_max_count = 0;
Wnon_pointer_null = 0;
Wone_bit_signed_bitfield = 0;
Woverride_init = 0;
Wpointer_to_int_cast = 0;
Wvla = 0;
do_output = 0;
collect_info_from_args(argc, argv);
process_symbols(sparse_initialize(argc, argv, &filelist));
FOR_EACH_PTR(filelist, file) {
process_symbols(sparse(file));
} END_FOR_EACH_PTR(file);
report_stats();
return 0;
}