aboutsummaryrefslogtreecommitdiff
path: root/scripts/coccinelle
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/coccinelle')
-rwxr-xr-xscripts/coccinelle/apply.sh9
-rwxr-xr-xscripts/coccinelle/check_cocci_parse.sh98
-rw-r--r--scripts/coccinelle/ctrl-reply-cleanup.cocci43
-rw-r--r--scripts/coccinelle/ctrl-reply.cocci87
-rw-r--r--scripts/coccinelle/debugmm.cocci29
-rw-r--r--scripts/coccinelle/exceptions.txt24
-rwxr-xr-xscripts/coccinelle/test-operator-cleanup13
-rw-r--r--scripts/coccinelle/tor-coccinelle.h63
-rwxr-xr-xscripts/coccinelle/try_parse.sh46
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"