diff options
Diffstat (limited to 'scripts/coccinelle')
-rwxr-xr-x | scripts/coccinelle/apply.sh | 9 | ||||
-rwxr-xr-x | scripts/coccinelle/check_cocci_parse.sh | 98 | ||||
-rw-r--r-- | scripts/coccinelle/ctrl-reply-cleanup.cocci | 43 | ||||
-rw-r--r-- | scripts/coccinelle/ctrl-reply.cocci | 87 | ||||
-rw-r--r-- | scripts/coccinelle/debugmm.cocci | 29 | ||||
-rw-r--r-- | scripts/coccinelle/exceptions.txt | 24 | ||||
-rwxr-xr-x | scripts/coccinelle/test-operator-cleanup | 13 | ||||
-rw-r--r-- | scripts/coccinelle/tor-coccinelle.h | 63 | ||||
-rwxr-xr-x | scripts/coccinelle/try_parse.sh | 46 |
9 files changed, 412 insertions, 0 deletions
diff --git a/scripts/coccinelle/apply.sh b/scripts/coccinelle/apply.sh new file mode 100755 index 0000000000..f531d7fa32 --- /dev/null +++ b/scripts/coccinelle/apply.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# apply.sh: +# run spatch with appropriate includes and builtins for the Tor source code + +top="$(dirname "$0")/../.." + +spatch -macro_file_builtins "$top"/scripts/coccinelle/tor-coccinelle.h \ + -I "$top" -I "$top"/src -I "$top"/ext --defined COCCI "$@" diff --git a/scripts/coccinelle/check_cocci_parse.sh b/scripts/coccinelle/check_cocci_parse.sh new file mode 100755 index 0000000000..aaa586c093 --- /dev/null +++ b/scripts/coccinelle/check_cocci_parse.sh @@ -0,0 +1,98 @@ +#!/bin/sh + +# If we have coccinelle installed, run try_parse.sh on every filename passed +# as an argument. If no filenames are supplied, scan a standard Tor 0.3.5 or +# later directory layout. +# +# Uses the default coccinelle exceptions file, or $TOR_COCCI_EXCEPTIONS_FILE, +# if it is set. +# +# Use TOR_COCCI_EXCEPTIONS_FILE=/dev/null check_cocci_parse.sh to disable +# the default exception file. +# +# If spatch is not installed, remind the user to install it, but exit with +# a success error status. + +scripts_cocci="$(dirname "$0")" +top="$scripts_cocci/../.." +try_parse="$scripts_cocci/try_parse.sh" + +exitcode=0 + +export TOR_COCCI_EXCEPTIONS_FILE="${TOR_COCCI_EXCEPTIONS_FILE:-$scripts_cocci/exceptions.txt}" + +PURPOSE="cocci C parsing" + +echo "Checking spatch:" + +if ! command -v spatch ; then + echo "Install coccinelle's spatch to check $PURPOSE." + exit "$exitcode" +fi + +# Returns true if $1 is greater than or equal to $2 +version_ge() +{ + if test "$1" = "$2" ; then + # return true + return 0 + fi + LOWER_VERSION="$(printf '%s\n' "$1" "$2" | $SORT_V | head -n 1)" + # implicit return + test "$LOWER_VERSION" != "$1" +} + +# 'sort -V' is a gnu extension +SORT_V="sort -V" +# Use 'sort -n' if 'sort -V' doesn't work +if ! version_ge "1" "0" ; then + echo "Your 'sort -V' command appears broken. Falling back to 'sort -n'." + echo "Some spatch version checks may give the wrong result." + SORT_V="sort -n" +fi + +# Print the full spatch version, for diagnostics +spatch --version + +MIN_SPATCH_V="1.0.4" +# This pattern needs to handle version strings like: +# spatch version 1.0.0-rc19 +# spatch version 1.0.6 compiled with OCaml version 4.05.0 +SPATCH_V=$(spatch --version | head -1 | \ + sed 's/spatch version \([0-9][^ ]*\).*/\1/') + +if ! version_ge "$SPATCH_V" "$MIN_SPATCH_V" ; then + echo "Tor requires coccinelle spatch >= $MIN_SPATCH_V to check $PURPOSE." + echo "But you have $SPATCH_V. Please install a newer version." + exit "$exitcode" +fi + +if test $# -ge 1 ; then + "$try_parse" "$@" + exitcode=$? +else + cd "$top" || exit 1 + # This is the layout in 0.3.5 + # Keep these lists consistent: + # - OWNED_TOR_C_FILES in Makefile.am + # - CHECK_FILES in pre-commit.git-hook and pre-push.git-hook + # - try_parse in check_cocci_parse.sh + "$try_parse" \ + src/lib/*/*.[ch] \ + src/core/*/*.[ch] \ + src/feature/*/*.[ch] \ + src/app/*/*.[ch] \ + src/test/*.[ch] \ + src/test/*/*.[ch] \ + src/tools/*.[ch] + exitcode=$? +fi + +if test "$exitcode" != 0 ; then + echo "Please fix these $PURPOSE errors in the above files" + echo "Set VERBOSE=1 for more details" + echo "Try running test-operator-cleanup or 'make autostyle-operators'" + echo "As a last resort, you can modify scripts/coccinelle/exceptions.txt" +fi + +exit "$exitcode" diff --git a/scripts/coccinelle/ctrl-reply-cleanup.cocci b/scripts/coccinelle/ctrl-reply-cleanup.cocci new file mode 100644 index 0000000000..f085cd4684 --- /dev/null +++ b/scripts/coccinelle/ctrl-reply-cleanup.cocci @@ -0,0 +1,43 @@ +// Script to clean up after ctrl-reply.cocci -- run as a separate step +// because cleanup_write2 (even when disabled) somehow prevents the +// match rule in ctrl-reply.cocci from matching. + +// If it doesn't have to be a printf, turn it into a write + +@ cleanup_write @ +expression E; +constant code, s; +@@ +-control_printf_endreply(E, code, s) ++control_write_endreply(E, code, s) + +// Use send_control_done() instead of explicitly writing it out +@ cleanup_send_done @ +type T; +identifier f != send_control_done; +expression E; +@@ + T f(...) { +<... +-control_write_endreply(E, 250, "OK") ++send_control_done(E) + ...> + } + +// Clean up more printfs that could be writes +// +// For some reason, including this rule, even disabled, causes the +// match rule in ctrl-reply.cocci to fail to match some code that has +// %s in its format strings + +@ cleanup_write2 @ +expression E1, E2; +constant code; +@@ +( +-control_printf_endreply(E1, code, "%s", E2) ++control_write_endreply(E1, code, E2) +| +-control_printf_midreply(E1, code, "%s", E2) ++control_write_midreply(E1, code, E2) +) diff --git a/scripts/coccinelle/ctrl-reply.cocci b/scripts/coccinelle/ctrl-reply.cocci new file mode 100644 index 0000000000..d6e9aeedd7 --- /dev/null +++ b/scripts/coccinelle/ctrl-reply.cocci @@ -0,0 +1,87 @@ +// Script to edit control_*.c for refactored control reply output functions + +@ initialize:python @ +@@ +import re +from coccilib.report import * + +# reply strings "NNN-foo", "NNN+foo", "NNN foo", etc. +r = re.compile(r'^"(\d+)([ +-])(.*)\\r\\n"$') + +# Generate name of function to call based on which separator character +# comes between the numeric code and the text +def idname(sep, base): + if sep == '+': + return base + "datareply" + elif sep == '-': + return base + "midreply" + else: + return base + "endreply" + +# Generate the actual replacements used by the rules +def gen(s, base, p): + pos = p[0] + print_report(pos, "%s %s" % (base, s)) + m = r.match(s) + if m is None: + # String not correct format, so fail match + cocci.include_match(False) + print_report(pos, "BAD STRING %s" % s) + return + + code, sep, s1 = m.groups() + + if r'\r\n' in s1: + # Extra CRLF in string, so fail match + cocci.include_match(False) + print_report(pos, "extra CRLF in string %s" % s) + return + + coccinelle.code = code + # Need a string that is a single C token, because Coccinelle only allows + # "identifiers" to be output from Python scripts? + coccinelle.body = '"%s"' % s1 + coccinelle.id = idname(sep, base) + return + +@ match @ +identifier f; +position p; +expression E; +constant s; +@@ +( + connection_printf_to_buf@f@p(E, s, ...) +| + connection_write_str_to_buf@f@p(s, E) +) + +@ script:python sc1 @ +s << match.s; +p << match.p; +f << match.f; +id; +body; +code; +@@ +if f == 'connection_printf_to_buf': + gen(s, 'control_printf_', p) +elif f == 'connection_write_str_to_buf': + gen(s, 'control_write_', p) +else: + raise(ValueError("%s: %s" % (f, s))) + +@ replace @ +constant match.s; +expression match.E; +identifier match.f; +identifier sc1.body, sc1.id, sc1.code; +@@ +( +-connection_write_str_to_buf@f(s, E) ++id(E, code, body) +| +-connection_printf_to_buf@f(E, s ++id(E, code, body + , ...) +) diff --git a/scripts/coccinelle/debugmm.cocci b/scripts/coccinelle/debugmm.cocci new file mode 100644 index 0000000000..dbd308df33 --- /dev/null +++ b/scripts/coccinelle/debugmm.cocci @@ -0,0 +1,29 @@ +// Look for use of expressions with side-effects inside of debug logs. +// +// This script detects expressions like ++E, --E, E++, and E-- inside of +// calls to log_debug(). +// +// The log_debug() macro exits early if debug logging is not enabled, +// potentially causing problems if its arguments have side-effects. + +@@ +expression E; +@@ +*log_debug(... , <+... --E ...+>, ... ); + + +@@ +expression E; +@@ +*log_debug(... , <+... ++E ...+>, ... ); + +@@ +expression E; +@@ +*log_debug(... , <+... E-- ...+>, ... ); + + +@@ +expression E; +@@ +*log_debug(... , <+... E++ ...+>, ... ); diff --git a/scripts/coccinelle/exceptions.txt b/scripts/coccinelle/exceptions.txt new file mode 100644 index 0000000000..473f4b22c5 --- /dev/null +++ b/scripts/coccinelle/exceptions.txt @@ -0,0 +1,24 @@ +# A list of exception patterns for check_cocci_parse.sh +# Passed to 'grep -f' +src/lib/cc/compat_compiler.h +src/lib/container/handles.h +src/lib/container/map.c +src/lib/container/map.h +src/lib/container/order.c +src/lib/crypt_ops/crypto_rand.c +src/lib/fs/files.h +src/lib/log/util_bug.c +src/lib/pubsub/pubsub_macros.h +src/lib/smartlist_core/smartlist_foreach.h +src/lib/testsupport/testsupport.h +src/lib/tls/tortls.h +src/lib/tls/tortls_openssl.c +src/lib/tls/x509.h +src/lib/version/version.c +src/core/mainloop/connection.c +src/core/or/reasons.c +src/feature/dirclient/dirclient.c +src/feature/nodelist/networkstatus.c +src/test/test_address.c +src/test/test_hs_cache.c +src/test/test_hs_descriptor.c diff --git a/scripts/coccinelle/test-operator-cleanup b/scripts/coccinelle/test-operator-cleanup index e7822542a4..28b4d4f588 100755 --- a/scripts/coccinelle/test-operator-cleanup +++ b/scripts/coccinelle/test-operator-cleanup @@ -1,4 +1,17 @@ #!/usr/bin/perl -w -p -i +# +# Copyright (c) 2001 Matej Pfajfar. +# Copyright (c) 2001-2004, Roger Dingledine. +# Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. +# Copyright (c) 2007-2019, The Tor Project, Inc. +# See LICENSE for licensing information + +# This script looks for instances of C comparison operators as macro arguments, +# and replaces them with our OP_* equivalents. +# +# Some macros that take operators are our tt_int_op() testing macro, and the +# standard timercmp() macro. Coccinelle can't handle their syntax, however, +# unless we give them their operators as a macro too. next if m#^ */\*# or m#^ *\* #; diff --git a/scripts/coccinelle/tor-coccinelle.h b/scripts/coccinelle/tor-coccinelle.h new file mode 100644 index 0000000000..44d79325eb --- /dev/null +++ b/scripts/coccinelle/tor-coccinelle.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/* + * This file looks like a C header, but its purpose is a bit different. + * + * We never include it from our real C files; we only tell Coccinelle + * about it in apply.sh. + * + * It tells the Coccinelle semantic patching tool how to understand + * things that would otherwise not be good C syntax, or which would + * otherwise not make sense to it as C. It doesn't need to produce + * semantically equivalent C, or even correct C: it only has to produce + * syntactically valid C. + */ + +#define MOCK_DECL(a, b, c) a b c +#define MOCK_IMPL(a, b, c) a b c +#define CHECK_PRINTF(a, b) +#define CHECK_SCANF(a, b) +#define STATIC static +#define EXTERN(a,b) extern a b; + +#define STMT_BEGIN do { +#define STMT_END } while (0) + +#define BUG(x) (x) +#define IF_BUG_ONCE(x) if (x) + +#define ATTR_NORETURN +#define ATTR_UNUSED +#define ATTR_CONST +#define ATTR_MALLOC +#define ATTR_WUR +#define DISABLE_GCC_WARNING(x) +#define ENABLE_GCC_WARNING(x) + +#define HANDLE_DECL(a,b,c) +#define HANDLE_IMPL(a,b,c) +#define HT_ENTRY(x) void * +#define HT_HEAD(a,b) struct ht_head +#define HT_INITIALIZER() { } +#define X509 struct x509_st +#define STACK_OF(x) struct foo_stack_t +#define TOR_TAILQ_HEAD(a,b) struct tailq_head +#define TOR_TAILQ_ENTRY(a) struct tailq_entry +#define TOR_SIMPLEQ_HEAD(a,b) struct simpleq_entry +#define TOR_SIMPLEQ_ENTRY(a) struct simpleq_entry +#define TOR_LIST_HEAD(a,b) struct list_head +#define TOR_LIST_ENTRY(a) struct list_entry +#define TOR_SLIST_HEAD(a,b) struct slist_head +#define TOR_SLIST_ENTRY(a) struct slist_entry + +#define NS_DECL(a, b, c) a b c +#define NS(a) a + +#define CONF_TEST_MEMBERS(a,b,c) +#define DUMMY_CONF_TEST_MEMBERS + +#define EAT_SEMICOLON extern int dummy__; diff --git a/scripts/coccinelle/try_parse.sh b/scripts/coccinelle/try_parse.sh new file mode 100755 index 0000000000..a90e51b4aa --- /dev/null +++ b/scripts/coccinelle/try_parse.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +# Echo the name of every argument of this script that is not "perfect" +# according to coccinelle's --parse-c. +# +# If $TOR_COCCI_EXCEPTIONS_FILE is non-empty, skip any files that match the +# patterns in the exception file, according to "grep -f" +# +# If VERBOSE is non-empty, log spatch errors and skipped files. + +top="$(dirname "$0")/../.." + +exitcode=0 + +for fn in "$@"; do + + if test "${TOR_COCCI_EXCEPTIONS_FILE}" ; then + skip_fn=$(echo "$fn" | grep -f "${TOR_COCCI_EXCEPTIONS_FILE}") + if test "${skip_fn}" ; then + if test "${VERBOSE}" != ""; then + echo "Skipping '${skip_fn}'" + fi + continue + fi + fi + + if spatch --macro-file-builtins \ + "$top"/scripts/coccinelle/tor-coccinelle.h \ + --defined COCCI \ + --parse-c "$fn" \ + 2>/dev/null | grep "perfect = 1" > /dev/null; then + : # it's perfect + else + echo "$fn" + if test "${VERBOSE}" != ""; then + spatch --macro-file-builtins \ + "$top"/scripts/coccinelle/tor-coccinelle.h \ + --defined COCCI \ + --parse-c "$fn" + fi + exitcode=1 + fi + +done + +exit "$exitcode" |