xref: /dpdk/devtools/checkpatches.sh (revision 3c4898ef762eeb2578b9ae3d7f6e3a0e5cbca8c8)
1#! /bin/sh
2# SPDX-License-Identifier: BSD-3-Clause
3# Copyright 2015 6WIND S.A.
4
5# Load config options:
6# - DPDK_CHECKPATCH_PATH
7# - DPDK_CHECKPATCH_CODESPELL
8# - DPDK_CHECKPATCH_LINE_LENGTH
9# - DPDK_CHECKPATCH_OPTIONS
10. $(dirname $(readlink -f $0))/load-devel-config
11
12VALIDATE_NEW_API=$(dirname $(readlink -f $0))/check-symbol-change.sh
13
14# Enable codespell by default. This can be overwritten from a config file.
15# Codespell can also be enabled by setting DPDK_CHECKPATCH_CODESPELL to a valid path
16# to a dictionary.txt file if dictionary.txt is not in the default location.
17codespell=${DPDK_CHECKPATCH_CODESPELL:-enable}
18length=${DPDK_CHECKPATCH_LINE_LENGTH:-100}
19
20# override default Linux options
21options="--no-tree"
22if [ "$codespell" = "enable" ] ; then
23    options="$options --codespell"
24elif [ -f "$codespell" ] ; then
25    options="$options --codespell"
26    options="$options --codespellfile $codespell"
27fi
28options="$options --max-line-length=$length"
29options="$options --show-types"
30options="$options --ignore=LINUX_VERSION_CODE,ENOSYS,\
31FILE_PATH_CHANGES,MAINTAINERS_STYLE,SPDX_LICENSE_TAG,\
32VOLATILE,PREFER_PACKED,PREFER_ALIGNED,PREFER_PRINTF,STRLCPY,\
33PREFER_KERNEL_TYPES,PREFER_FALLTHROUGH,BIT_MACRO,CONST_STRUCT,\
34SPLIT_STRING,LONG_LINE_STRING,C99_COMMENT_TOLERANCE,\
35LINE_SPACING,PARENTHESIS_ALIGNMENT,NETWORKING_BLOCK_COMMENT_STYLE,\
36NEW_TYPEDEFS,COMPARISON_TO_NULL,AVOID_BUG"
37options="$options $DPDK_CHECKPATCH_OPTIONS"
38
39print_usage () {
40	cat <<- END_OF_HELP
41	usage: $(basename $0) [-h] [-q] [-v] [-nX|-r range|patch1 [patch2] ...]
42
43	Run Linux kernel checkpatch.pl with DPDK options.
44	The environment variable DPDK_CHECKPATCH_PATH can be set, if not we will
45	try to find the script in the sources of the currently running kernel.
46
47	The patches to check can be from stdin, files specified on the command line,
48	latest git commits limited with -n option, or commits in the git range
49	specified with -r option (default: "origin/main..").
50	END_OF_HELP
51}
52
53check_forbidden_additions() { # <patch>
54	res=0
55
56	# refrain from new additions of rte_panic() and rte_exit()
57	# multiple folders and expressions are separated by spaces
58	awk -v FOLDERS="lib drivers" \
59		-v EXPRESSIONS="rte_panic\\\( rte_exit\\\(" \
60		-v RET_ON_FAIL=1 \
61		-v MESSAGE='Using rte_panic/rte_exit' \
62		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
63		"$1" || res=1
64
65	# refrain from using compiler attribute without defining a common macro
66	awk -v FOLDERS="lib drivers app examples" \
67		-v EXPRESSIONS="__attribute__" \
68		-v RET_ON_FAIL=1 \
69		-v MESSAGE='Using compiler attribute directly' \
70		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
71		"$1" || res=1
72
73	# check %l or %ll format specifier
74	awk -v FOLDERS='lib drivers app examples' \
75		-v EXPRESSIONS='%ll*[xud]' \
76		-v RET_ON_FAIL=1 \
77		-v MESSAGE='Using %l format, prefer %PRI*64 if type is [u]int64_t' \
78		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
79		"$1" || res=1
80
81	# refrain from new additions of 16/32/64 bits rte_atomicNN_xxx()
82	awk -v FOLDERS="lib drivers app examples" \
83		-v EXPRESSIONS="rte_atomic[0-9][0-9]_.*\\\(" \
84		-v RET_ON_FAIL=1 \
85		-v MESSAGE='Using rte_atomicNN_xxx' \
86		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
87		"$1" || res=1
88
89	# refrain from new additions of rte_smp_[r/w]mb()
90	awk -v FOLDERS="lib drivers app examples" \
91		-v EXPRESSIONS="rte_smp_(r|w)?mb\\\(" \
92		-v RET_ON_FAIL=1 \
93		-v MESSAGE='Using rte_smp_[r/w]mb' \
94		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
95		"$1" || res=1
96
97	# refrain from using compiler __sync_xxx builtins
98	awk -v FOLDERS="lib drivers app examples" \
99		-v EXPRESSIONS="__sync_.*\\\(" \
100		-v RET_ON_FAIL=1 \
101		-v MESSAGE='Using __sync_xxx builtins' \
102		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
103		"$1" || res=1
104
105	# refrain from using compiler __rte_atomic_thread_fence()
106	# It should be avoided on x86 for SMP case.
107	awk -v FOLDERS="lib drivers app examples" \
108		-v EXPRESSIONS="__rte_atomic_thread_fence\\\(" \
109		-v RET_ON_FAIL=1 \
110		-v MESSAGE='Using __rte_atomic_thread_fence, prefer rte_atomic_thread_fence' \
111		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
112		"$1" || res=1
113
114	# refrain from using compiler __atomic_xxx builtins
115	awk -v FOLDERS="lib drivers app examples" \
116		-v EXPRESSIONS="__atomic_.*\\\( __ATOMIC_(RELAXED|CONSUME|ACQUIRE|RELEASE|ACQ_REL|SEQ_CST)" \
117		-v RET_ON_FAIL=1 \
118		-v MESSAGE='Using __atomic_xxx/__ATOMIC_XXX built-ins, prefer rte_atomic_xxx/rte_memory_order_xxx' \
119		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
120		"$1" || res=1
121
122	# refrain from using some pthread functions
123	awk -v FOLDERS="lib drivers app examples" \
124		-v EXPRESSIONS="pthread_(create|join|detach|set(_?name_np|affinity_np)|attr_set(inheritsched|schedpolicy))\\\(" \
125		-v RET_ON_FAIL=1 \
126		-v MESSAGE='Using pthread functions, prefer rte_thread' \
127		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
128		"$1" || res=1
129
130	# forbid use of __reserved which is a reserved keyword in Windows system headers
131	awk -v FOLDERS="lib drivers app examples" \
132		-v EXPRESSIONS='\\<__reserved\\>' \
133		-v RET_ON_FAIL=1 \
134		-v MESSAGE='Using __reserved' \
135		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
136		"$1" || res=1
137
138	# forbid use of non abstracted bit count operations
139	awk -v FOLDERS="lib drivers app examples" \
140		-v EXPRESSIONS='\\<__builtin_(clz|clzll|ctz|ctzll|popcount|popcountll)\\>' \
141		-v RET_ON_FAIL=1 \
142		-v MESSAGE='Using __builtin helpers for bit count operations' \
143		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
144		"$1" || res=1
145
146	# forbid inclusion of Linux header for PCI constants
147	awk -v FOLDERS="lib drivers app examples" \
148		-v EXPRESSIONS='include.*linux/pci_regs\\.h' \
149		-v RET_ON_FAIL=1 \
150		-v MESSAGE='Using linux/pci_regs.h, prefer rte_pci.h' \
151		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
152		"$1" || res=1
153
154	# forbid use of experimental build flag except in examples
155	awk -v FOLDERS='lib drivers app' \
156		-v EXPRESSIONS='-DALLOW_EXPERIMENTAL_API allow_experimental_apis' \
157		-v RET_ON_FAIL=1 \
158		-v MESSAGE='Using experimental build flag for in-tree compilation' \
159		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
160		"$1" || res=1
161
162	# refrain from using RTE_LOG_REGISTER for drivers and libs
163	awk -v FOLDERS='lib drivers' \
164		-v EXPRESSIONS='\\<RTE_LOG_REGISTER\\>' \
165		-v RET_ON_FAIL=1 \
166		-v MESSAGE='Using RTE_LOG_REGISTER, prefer RTE_LOG_REGISTER_(DEFAULT|SUFFIX)' \
167		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
168		"$1" || res=1
169
170	# forbid non-internal thread in drivers and libs
171	awk -v FOLDERS='lib drivers' \
172		-v EXPRESSIONS="rte_thread_(set_name|create_control)\\\(" \
173		-v RET_ON_FAIL=1 \
174		-v MESSAGE='Prefer rte_thread_(set_prefixed_name|create_internal_control)' \
175		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
176		"$1" || res=1
177
178	# forbid inclusion of driver specific headers in apps and examples
179	awk -v FOLDERS='app examples' \
180		-v EXPRESSIONS='include.*_driver\\.h include.*_pmd\\.h' \
181		-v RET_ON_FAIL=1 \
182		-v MESSAGE='Using driver specific headers in applications' \
183		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
184		"$1" || res=1
185
186	# prevent addition of tests not in one of our test suites
187	awk -v FOLDERS='app/test' \
188		-v EXPRESSIONS='REGISTER_TEST_COMMAND' \
189		-v RET_ON_FAIL=1 \
190		-v MESSAGE='Using REGISTER_TEST_COMMAND instead of REGISTER_<suite_name>_TEST' \
191		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
192		"$1" || res=1
193
194	# SVG must be included with wildcard extension to allow conversion
195	awk -v FOLDERS='doc' \
196		-v EXPRESSIONS='::[[:space:]]*[^[:space:]]*\\.svg' \
197		-v RET_ON_FAIL=1 \
198		-v MESSAGE='Using explicit .svg extension instead of .*' \
199		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
200		"$1" || res=1
201
202	# links must prefer https over http
203	awk -v FOLDERS='doc' \
204		-v EXPRESSIONS='http://.*dpdk.org' \
205		-v RET_ON_FAIL=1 \
206		-v MESSAGE='Using non https link to dpdk.org' \
207		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
208		"$1" || res=1
209
210	# prefer Sphinx references for internal documentation
211	awk -v FOLDERS='doc' \
212		-v EXPRESSIONS='//doc.dpdk.org/guides/' \
213		-v RET_ON_FAIL=1 \
214		-v MESSAGE='Using explicit URL to doc.dpdk.org, prefer :ref: or :doc:' \
215		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
216		"$1" || res=1
217
218	# '// XXX is not set' must be preferred over '#undef XXX'
219	awk -v FOLDERS='config/rte_config.h' \
220		-v EXPRESSIONS='#undef' \
221		-v RET_ON_FAIL=1 \
222		-v MESSAGE='Using "#undef XXX", prefer "// XXX is not set"' \
223		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
224		"$1" || res=1
225
226	return $res
227}
228
229check_experimental_tags() { # <patch>
230	res=0
231
232	cat "$1" |awk '
233	BEGIN {
234		current_file = "";
235		ret = 0;
236	}
237	/^+++ b\// {
238		current_file = $2;
239	}
240	/^+.*__rte_experimental/ {
241		if (current_file ~ ".c$" ) {
242			print "Please only put __rte_experimental tags in " \
243				"headers ("current_file")";
244			ret = 1;
245		}
246		if ($1 != "+__rte_experimental" || $2 != "") {
247			print "__rte_experimental must appear alone on the line" \
248				" immediately preceding the return type of a function."
249			ret = 1;
250		}
251	}
252	END {
253		exit ret;
254	}' || res=1
255
256	return $res
257}
258
259check_internal_tags() { # <patch>
260	res=0
261
262	cat "$1" |awk '
263	BEGIN {
264		current_file = "";
265		ret = 0;
266	}
267	/^+++ b\// {
268		current_file = $2;
269	}
270	/^+.*__rte_internal/ {
271		if (current_file ~ ".c$" ) {
272			print "Please only put __rte_internal tags in " \
273				"headers ("current_file")";
274			ret = 1;
275		}
276		if ($1 != "+__rte_internal" || $2 != "") {
277			print "__rte_internal must appear alone on the line" \
278				" immediately preceding the return type of" \
279				" a function."
280			ret = 1;
281		}
282	}
283	END {
284		exit ret;
285	}' || res=1
286
287	return $res
288}
289
290check_release_notes() { # <patch>
291	rel_notes_prefix=doc/guides/rel_notes/release_
292	IFS=. read year month release < VERSION
293	current_rel_notes=${rel_notes_prefix}${year}_${month}.rst
294
295	! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" |
296		grep -v $current_rel_notes
297}
298
299number=0
300range='origin/main..'
301quiet=false
302verbose=false
303while getopts hn:qr:v ARG ; do
304	case $ARG in
305		n ) number=$OPTARG ;;
306		q ) quiet=true ;;
307		r ) range=$OPTARG ;;
308		v ) verbose=true ;;
309		h ) print_usage ; exit 0 ;;
310		? ) print_usage ; exit 1 ;;
311	esac
312done
313shift $(($OPTIND - 1))
314
315if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
316	default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl"
317	if [ -f "$default_path" ] && [ -x "$default_path" ] ; then
318		DPDK_CHECKPATCH_PATH="$default_path"
319	else
320		print_usage >&2
321		echo
322		echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
323		exit 1
324	fi
325fi
326
327print_headline() { # <title>
328	printf '\n### %s\n\n' "$1"
329	headline_printed=true
330}
331
332total=0
333status=0
334
335check () { # <patch-file> <commit>
336	local ret=0
337	local subject=''
338	headline_printed=false
339
340	total=$(($total + 1))
341	if [ -n "$1" ] ; then
342		tmpinput=$1
343	else
344		tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
345		trap "rm -f '$tmpinput'" INT
346
347		if [ -n "$2" ] ; then
348			git format-patch --find-renames \
349			--no-stat --stdout -1 $commit > "$tmpinput"
350		else
351			cat > "$tmpinput"
352		fi
353	fi
354
355	# Subject can be on 2 lines
356	subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput")
357	! $verbose || print_headline "$subject"
358
359	! $verbose || printf 'Running checkpatch.pl:\n'
360	report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
361	if [ $? -ne 0 ] ; then
362		$headline_printed || print_headline "$subject"
363		printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
364		ret=1
365	fi
366
367	! $verbose || printf '\nChecking API additions/removals:\n'
368	report=$($VALIDATE_NEW_API "$tmpinput")
369	if [ $? -ne 0 ] ; then
370		$headline_printed || print_headline "$subject"
371		printf '%s\n' "$report"
372		ret=1
373	fi
374
375	! $verbose || printf '\nChecking forbidden tokens additions:\n'
376	report=$(check_forbidden_additions "$tmpinput")
377	if [ $? -ne 0 ] ; then
378		$headline_printed || print_headline "$subject"
379		printf '%s\n' "$report"
380		ret=1
381	fi
382
383	! $verbose || printf '\nChecking __rte_experimental tags:\n'
384	report=$(check_experimental_tags "$tmpinput")
385	if [ $? -ne 0 ] ; then
386		$headline_printed || print_headline "$subject"
387		printf '%s\n' "$report"
388		ret=1
389	fi
390
391	! $verbose || printf '\nChecking __rte_internal tags:\n'
392	report=$(check_internal_tags "$tmpinput")
393	if [ $? -ne 0 ] ; then
394		$headline_printed || print_headline "$subject"
395		printf '%s\n' "$report"
396		ret=1
397	fi
398
399	! $verbose || printf '\nChecking release notes updates:\n'
400	report=$(check_release_notes "$tmpinput")
401	if [ $? -ne 0 ] ; then
402		$headline_printed || print_headline "$subject"
403		printf '%s\n' "$report"
404		ret=1
405	fi
406
407	if [ "$tmpinput" != "$1" ]; then
408		rm -f "$tmpinput"
409		trap - INT
410	fi
411	[ $ret -eq 0 ] && return 0
412
413	status=$(($status + 1))
414}
415
416if [ -n "$1" ] ; then
417	for patch in "$@" ; do
418		check "$patch" ''
419	done
420elif [ ! -t 0 ] ; then # stdin
421	check '' ''
422else
423	if [ $number -eq 0 ] ; then
424		commits=$(git rev-list --reverse $range)
425	else
426		commits=$(git rev-list --reverse --max-count=$number HEAD)
427	fi
428	for commit in $commits ; do
429		check '' $commit
430	done
431fi
432pass=$(($total - $status))
433$quiet || printf '\n%d/%d valid patch' $pass $total
434$quiet || [ $pass -le 1 ] || printf 'es'
435$quiet || printf '\n'
436exit $status
437