aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml6
-rw-r--r--Makefile.am73
-rw-r--r--changes/ticket3191913
-rwxr-xr-xscripts/coccinelle/check_cocci_parse.sh52
-rw-r--r--scripts/coccinelle/exceptions.txt24
-rwxr-xr-xscripts/coccinelle/try_parse.sh25
-rwxr-xr-xscripts/git/pre-commit.git-hook78
-rwxr-xr-xscripts/git/pre-push.git-hook153
8 files changed, 301 insertions, 123 deletions
diff --git a/.travis.yml b/.travis.yml
index eda8d17bc5..ea06d21db5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -117,6 +117,8 @@ addons:
- libseccomp-dev
## zstd doesn't exist in Ubuntu Trusty
#- libzstd
+ ## Optional build dependencies
+ - coccinelle
- shellcheck
## Conditional build dependencies
## Always installed, so we don't need sudo
@@ -146,6 +148,7 @@ addons:
- pkg-config
## Optional build dependencies
- ccache
+ - coccinelle
- shellcheck
## Conditional build dependencies
## Always installed, because manual brew installs are hard to get right
@@ -202,6 +205,9 @@ install:
- if [[ "$CHUTNEY" != "" ]]; then pushd "$CHUTNEY_PATH"; git log -1 ; popd ; fi
## If we're running stem, show the stem version and commit
- if [[ "$TEST_STEM" != "" ]]; then pushd stem; python -c "from stem import stem; print(stem.__version__);"; git log -1; popd; fi
+ ## Get the coccinelle version
+ ## Installs are unreliable on macOS, so we just rely on brew list --versions
+ - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then spatch --version; fi
## We don't want Tor tests to depend on default configuration file at
## ~/.torrc. So we put some random bytes in there, to make sure we get build
## failures in case Tor is reading it during CI jobs.
diff --git a/Makefile.am b/Makefile.am
index 32cb21f38c..9866f87f8c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -158,37 +158,43 @@ include doc/include.am
include contrib/include.am
EXTRA_DIST+= \
- ChangeLog \
- CONTRIBUTING \
- CODE_OF_CONDUCT \
- INSTALL \
- LICENSE \
- Makefile.nmake \
- README \
- ReleaseNotes \
- scripts/maint/checkIncludes.py \
- scripts/maint/checkSpace.pl \
- scripts/maint/checkShellScripts.sh \
- scripts/maint/practracker/README \
- scripts/maint/practracker/exceptions.txt \
- scripts/maint/practracker/includes.py \
- scripts/maint/practracker/metrics.py \
- scripts/maint/practracker/practracker.py \
- scripts/maint/practracker/practracker_tests.py \
- scripts/maint/practracker/problem.py \
- scripts/maint/practracker/testdata/.may_include \
- scripts/maint/practracker/testdata/a.c \
- scripts/maint/practracker/testdata/b.c \
- scripts/maint/practracker/testdata/ex0-expected.txt \
- scripts/maint/practracker/testdata/ex0.txt \
- scripts/maint/practracker/testdata/ex1-expected.txt \
- scripts/maint/practracker/testdata/ex1.txt \
- scripts/maint/practracker/testdata/ex1-overbroad-expected.txt \
- scripts/maint/practracker/testdata/ex.txt \
- scripts/maint/practracker/testdata/header.h \
- scripts/maint/practracker/testdata/not_c_file \
- scripts/maint/practracker/test_practracker.sh \
- scripts/maint/practracker/util.py
+ ChangeLog \
+ CONTRIBUTING \
+ CODE_OF_CONDUCT \
+ INSTALL \
+ LICENSE \
+ Makefile.nmake \
+ README \
+ ReleaseNotes \
+ scripts/maint/checkIncludes.py \
+ scripts/maint/checkSpace.pl \
+ scripts/maint/checkShellScripts.sh \
+ scripts/maint/practracker/README \
+ scripts/maint/practracker/exceptions.txt \
+ scripts/maint/practracker/includes.py \
+ scripts/maint/practracker/metrics.py \
+ scripts/maint/practracker/practracker.py \
+ scripts/maint/practracker/practracker_tests.py \
+ scripts/maint/practracker/problem.py \
+ scripts/maint/practracker/testdata/.may_include \
+ scripts/maint/practracker/testdata/a.c \
+ scripts/maint/practracker/testdata/b.c \
+ scripts/maint/practracker/testdata/ex0-expected.txt \
+ scripts/maint/practracker/testdata/ex0.txt \
+ scripts/maint/practracker/testdata/ex1-expected.txt \
+ scripts/maint/practracker/testdata/ex1.txt \
+ scripts/maint/practracker/testdata/ex1-overbroad-expected.txt \
+ scripts/maint/practracker/testdata/ex.txt \
+ scripts/maint/practracker/testdata/header.h \
+ scripts/maint/practracker/testdata/not_c_file \
+ scripts/maint/practracker/test_practracker.sh \
+ scripts/maint/practracker/util.py \
+ scripts/coccinelle/apply.sh \
+ scripts/coccinelle/check_cocci_parse.sh \
+ scripts/coccinelle/exceptions.txt \
+ scripts/coccinelle/test-operator-cleanup \
+ scripts/coccinelle/tor-coccinelle.h \
+ scripts/coccinelle/try_parse.sh
## This tells etags how to find mockable function definitions.
AM_ETAGSFLAGS=--regex='{c}/MOCK_IMPL([^,]+,\W*\([a-zA-Z0-9_]+\)\W*,/\1/s'
@@ -246,7 +252,7 @@ test: all
shellcheck:
$(top_srcdir)/scripts/maint/checkShellScripts.sh
-check-local: check-spaces check-changes check-includes check-best-practices shellcheck
+check-local: check-spaces check-changes check-includes check-best-practices shellcheck check-cocci
need-chutney-path:
@if test ! -d "$$CHUTNEY_PATH"; then \
@@ -379,6 +385,9 @@ if USEPYTHON
@$(PYTHON) $(top_srcdir)/scripts/maint/practracker/practracker.py $(top_srcdir) $(TOR_PRACTRACKER_OPTIONS)
endif
+check-cocci:
+ VERBOSE=1 $(top_srcdir)/scripts/coccinelle/check_cocci_parse.sh $(OWNED_TOR_C_FILES)
+
practracker-regen:
$(PYTHON) $(top_srcdir)/scripts/maint/practracker/practracker.py --regen $(top_srcdir)
diff --git a/changes/ticket31919 b/changes/ticket31919
new file mode 100644
index 0000000000..5344db956b
--- /dev/null
+++ b/changes/ticket31919
@@ -0,0 +1,13 @@
+ o Minor features (developer tools):
+ - Add a check_cocci_parse.sh script that checks that new code
+ is parseable by Coccinelle. Add an exceptions file for unparseable
+ files. Closes ticket 31919.
+ - Call the check_cocci_parse.sh script from a 'check-cocci' Makefile
+ target. Closes ticket 31919.
+ o Minor features (git scripts):
+ - Call the check_cocci_parse.sh script from the git commit and push hooks.
+ Closes ticket 31919.
+ - Skip unmodified source files when doing some existing git hook checks.
+ Related to ticket 31919.
+ o Minor features (continuous integration):
+ - Call the check_cocci_parse.sh script from Travis CI. Closes ticket 31919.
diff --git a/scripts/coccinelle/check_cocci_parse.sh b/scripts/coccinelle/check_cocci_parse.sh
new file mode 100755
index 0000000000..5c27c7bc88
--- /dev/null
+++ b/scripts/coccinelle/check_cocci_parse.sh
@@ -0,0 +1,52 @@
+#!/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}"
+
+if ! command -v spatch; then
+ echo "Install coccinelle's spatch to check cocci C parsing!"
+ exit "$exitcode"
+fi
+
+if test $# -ge 1 ; then
+ "$try_parse" "$@"
+ exitcode=$?
+else
+ # This is the layout in 0.3.5
+ "$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 cocci parsing 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/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/try_parse.sh b/scripts/coccinelle/try_parse.sh
index 0f91e31702..a90e51b4aa 100755
--- a/scripts/coccinelle/try_parse.sh
+++ b/scripts/coccinelle/try_parse.sh
@@ -2,6 +2,11 @@
# 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")/../.."
@@ -9,16 +14,28 @@ exitcode=0
for fn in "$@"; do
- if spatch -macro_file_builtins "$top"/scripts/coccinelle/tor-coccinelle.h \
- -I "$top" -I "$top"/src -I "$top"/ext --defined COCCI \
+ 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 \
- -I "$top" -I "$top"/src -I "$top"/ext --defined COCCI \
+ spatch --macro-file-builtins \
+ "$top"/scripts/coccinelle/tor-coccinelle.h \
+ --defined COCCI \
--parse-c "$fn"
fi
exitcode=1
diff --git a/scripts/git/pre-commit.git-hook b/scripts/git/pre-commit.git-hook
index 1c381ec60a..2e476c310e 100755
--- a/scripts/git/pre-commit.git-hook
+++ b/scripts/git/pre-commit.git-hook
@@ -13,30 +13,61 @@ cd "$workdir" || exit 1
set -e
+if [ $# -eq 0 ]; then
+ # When called in pre-commit, check the files modified in this commit
+ CHECK_FILTER="git diff --cached --name-only --diff-filter=ACMR"
+ # Use the appropriate owned tor source list to filter the changed files
+ if [ -d src/lib ]; then
+ # This is the layout in 0.3.5
+ CHECK_FILES="$($CHECK_FILTER \
+ src/lib/*/*.[ch] \
+ src/core/*/*.[ch] \
+ src/feature/*/*.[ch] \
+ src/app/*/*.[ch] \
+ src/test/*.[ch] \
+ src/test/*/*.[ch] \
+ src/tools/*.[ch] \
+ )"
+ elif [ -d src/common ]; then
+ # This was the layout before 0.3.5
+ CHECK_FILES="$($CHECK_FILTER \
+ src/common/*/*.[ch] \
+ src/or/*/*.[ch] \
+ src/test/*.[ch] \
+ src/test/*/*.[ch] \
+ src/tools/*.[ch]
+ )"
+ fi
+else
+ # When called in pre-push, concatenate the argument array
+ # Fails on special characters in file names
+ CHECK_FILES="$*"
+fi
+
+## General File Checks
+
if [ -n "$(ls ./changes/)" ]; then
python scripts/maint/lintChanges.py ./changes/*
fi
-if [ -d src/lib ]; then
- # This is the layout in 0.3.5
- perl scripts/maint/checkSpace.pl -C \
- src/lib/*/*.[ch] \
- src/core/*/*.[ch] \
- src/feature/*/*.[ch] \
- src/app/*/*.[ch] \
- src/test/*.[ch] \
- src/test/*/*.[ch] \
- src/tools/*.[ch]
-elif [ -d src/common ]; then
- # This was the layout before 0.3.5
- perl scripts/maint/checkSpace.pl -C \
- src/common/*/*.[ch] \
- src/or/*/*.[ch] \
- src/test/*.[ch] \
- src/test/*/*.[ch] \
- src/tools/*.[ch]
+if [ -e scripts/maint/checkShellScripts.sh ]; then
+ scripts/maint/checkShellScripts.sh
+fi
+
+if [ ! "$CHECK_FILES" ]; then
+ echo "No modified tor-owned source files, skipping further checks"
+ exit 0
fi
+## Owned Source File Checks
+
+printf "Modified tor-owned source files:\n%s\n" "$CHECK_FILES"
+
+# We want word splitting here, because file names are space separated
+# shellcheck disable=SC2086
+perl scripts/maint/checkSpace.pl -C \
+ $CHECK_FILES
+
if test -e scripts/maint/practracker/includes.py; then
python scripts/maint/practracker/includes.py
fi
@@ -54,6 +85,13 @@ if [ -e "${PT_DIR}/practracker.py" ]; then
fi
fi
-if [ -e scripts/maint/checkShellScripts.sh ]; then
- scripts/maint/checkShellScripts.sh
+if [ -e scripts/coccinelle/check_cocci_parse.sh ]; then
+
+ # Run a verbose cocci parse check on the changed files
+ # (spatch is slow, so we don't want to check all the files.)
+ #
+ # We want word splitting here, because file names are space separated
+ # shellcheck disable=SC2086
+ VERBOSE=1 scripts/coccinelle/check_cocci_parse.sh \
+ $CHECK_FILES
fi
diff --git a/scripts/git/pre-push.git-hook b/scripts/git/pre-push.git-hook
index f4504c4215..2f3608029a 100755
--- a/scripts/git/pre-push.git-hook
+++ b/scripts/git/pre-push.git-hook
@@ -16,91 +16,110 @@
# The following sample script was used as starting point:
# https://github.com/git/git/blob/master/templates/hooks--pre-push.sample
+# Are you adding a new check to the git hooks?
+# - Common checks belong in the pre-commit hook
+# - Push-only checks belong in the pre-push hook
+
echo "Running pre-push hook"
z40=0000000000000000000000000000000000000000
upstream_name=${TOR_UPSTREAM_REMOTE_NAME:-"upstream"}
-# Are you adding a new check to the git hooks?
-# - Common checks belong in the pre-commit hook
-# - Push-only checks belong in the pre-push hook
-#
-# Call the pre-commit hook for the common checks, if it is executable.
workdir=$(git rev-parse --show-toplevel)
-if [ -x "$workdir/.git/hooks/pre-commit" ]; then
- if ! "$workdir"/.git/hooks/pre-commit; then
- exit 1
- fi
-fi
remote="$1"
-
remote_name=$(git remote --verbose | grep "$2" | awk '{print $1}' | head -n 1)
-if [[ "$remote_name" != "$upstream_name" ]]; then
- echo "Not pushing to upstream - refraining from further checks"
- exit 0
-fi
ref_is_upstream_branch() {
- if [ "$1" == "refs/heads/master" ] ||
- [[ "$1" == refs/heads/release-* ]] ||
- [[ "$1" == refs/heads/maint-* ]]
- then
- return 1
- fi
+ if [ "$1" == "refs/heads/master" ] ||
+ [[ "$1" == refs/heads/release-* ]] ||
+ [[ "$1" == refs/heads/maint-* ]]; then
+ return 1
+ fi
}
# shellcheck disable=SC2034
while read -r local_ref local_sha remote_ref remote_sha
do
- if [ "$local_sha" = $z40 ]
- then
- # Handle delete
- :
- else
- if [ "$remote_sha" = $z40 ]
- then
- # New branch, examine all commits
- range="$local_sha"
- else
- # Update to existing branch, examine new commits
- range="$remote_sha..$local_sha"
- fi
-
- if (ref_is_upstream_branch "$local_ref" == 0 ||
- ref_is_upstream_branch "$remote_ref" == 0) &&
- [ "$local_ref" != "$remote_ref" ]
- then
- if [ "$remote" == "origin" ]
- then
- echo >&2 "Not pushing: $local_ref to $remote_ref"
- echo >&2 "If you really want to push this, use --no-verify."
- exit 1
- else
- continue
- fi
- fi
-
- # Check for fixup! commit
- commit=$(git rev-list -n 1 --grep '^fixup!' "$range")
- if [ -n "$commit" ]
- then
- echo >&2 "Found fixup! commit in $local_ref, not pushing"
- echo >&2 "If you really want to push this, use --no-verify."
- exit 1
- fi
-
- # Check for squash! commit
- commit=$(git rev-list -n 1 --grep '^squash!' "$range")
- if [ -n "$commit" ]
- then
- echo >&2 "Found squash! commit in $local_ref, not pushing"
- echo >&2 "If you really want to push this, use --no-verify."
- exit 1
- fi
- fi
+ if [ "$local_sha" = $z40 ]; then
+ # Handle delete
+ :
+ else
+ if [ "$remote_sha" = $z40 ]; then
+ # New branch, examine commits not in master
+ range="master...$local_sha"
+ else
+ # Update to existing branch, examine new commits
+ range="$remote_sha..$local_sha"
+ fi
+
+ # Call the pre-commit hook for the common checks, if it is executable
+ # Only check the files newly modified in this branch
+ CHECK_FILTER="git diff --name-only --diff-filter=ACMR $range"
+ # Use the appropriate owned tor source list to filter the changed files
+ if [ -d src/lib ]; then
+ # This is the layout in 0.3.5
+ CHECK_FILES="$($CHECK_FILTER \
+ src/lib/*/*.[ch] \
+ src/core/*/*.[ch] \
+ src/feature/*/*.[ch] \
+ src/app/*/*.[ch] \
+ src/test/*.[ch] \
+ src/test/*/*.[ch] \
+ src/tools/*.[ch] \
+ )"
+ elif [ -d src/common ]; then
+ # This was the layout before 0.3.5
+ CHECK_FILES="$($CHECK_FILTER \
+ src/common/*/*.[ch] \
+ src/or/*/*.[ch] \
+ src/test/*.[ch] \
+ src/test/*/*.[ch] \
+ src/tools/*.[ch]
+ )"
+ fi
+
+ # We want word splitting here, because file names are space separated
+ # shellcheck disable=SC2086
+ if ! "$workdir/"scripts/git/pre-commit.git-hook $CHECK_FILES; then
+ exit 1
+ fi
+
+ if [[ "$remote_name" != "$upstream_name" ]]; then
+ echo "Not pushing to upstream - refraining from further checks"
+ continue
+ fi
+
+ if (ref_is_upstream_branch "$local_ref" == 0 ||
+ ref_is_upstream_branch "$remote_ref" == 0) &&
+ [ "$local_ref" != "$remote_ref" ]; then
+ if [ "$remote" == "origin" ]; then
+ echo >&2 "Not pushing: $local_ref to $remote_ref"
+ echo >&2 "If you really want to push this, use --no-verify."
+ exit 1
+ else
+ continue
+ fi
+ fi
+
+ # Check for fixup! commit
+ commit=$(git rev-list -n 1 --grep '^fixup!' "$range")
+ if [ -n "$commit" ]; then
+ echo >&2 "Found fixup! commit in $local_ref, not pushing"
+ echo >&2 "If you really want to push this, use --no-verify."
+ exit 1
+ fi
+
+ # Check for squash! commit
+ commit=$(git rev-list -n 1 --grep '^squash!' "$range")
+ if [ -n "$commit" ]; then
+ echo >&2 "Found squash! commit in $local_ref, not pushing"
+ echo >&2 "If you really want to push this, use --no-verify."
+ exit 1
+ fi
+ fi
done
exit 0