xref: /dpdk/devtools/checkpatches.sh (revision 7917b0d38e92e8b9ec5a870415b791420e10f11a)
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 calls to RTE_LOG in libraries
57	awk -v FOLDERS="lib" \
58		-v EXPRESSIONS="RTE_LOG\\\(" \
59		-v RET_ON_FAIL=1 \
60		-v MESSAGE='Prefer RTE_LOG_LINE' \
61		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
62		"$1" || res=1
63
64	# refrain from new calls to RTE_LOG in drivers (but leave some leeway for base drivers)
65	awk -v FOLDERS="drivers" \
66		-v SKIP_FILES='osdep.h$' \
67		-v EXPRESSIONS="RTE_LOG\\\( RTE_LOG_DP\\\( rte_log\\\(" \
68		-v RET_ON_FAIL=1 \
69		-v MESSAGE='Prefer RTE_LOG_LINE/RTE_LOG_DP_LINE' \
70		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
71		"$1" || res=1
72
73	# no output on stdout or stderr
74	awk -v FOLDERS="lib drivers" \
75		-v EXPRESSIONS="\\\<printf\\\> \\\<fprintf\\\(stdout, \\\<fprintf\\\(stderr," \
76		-v RET_ON_FAIL=1 \
77		-v MESSAGE='Writing to stdout or stderr' \
78		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
79		"$1" || res=1
80
81	# refrain from new additions of rte_panic() and rte_exit()
82	# multiple folders and expressions are separated by spaces
83	awk -v FOLDERS="lib drivers" \
84		-v EXPRESSIONS="rte_panic\\\( rte_exit\\\(" \
85		-v RET_ON_FAIL=1 \
86		-v MESSAGE='Using rte_panic/rte_exit' \
87		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
88		"$1" || res=1
89
90	# refrain from using compiler attribute without defining a common macro
91	awk -v FOLDERS="lib drivers app examples" \
92		-v SKIP_FILES='lib/eal/include/rte_common.h' \
93		-v EXPRESSIONS="__attribute__" \
94		-v RET_ON_FAIL=1 \
95		-v MESSAGE='Using compiler attribute directly' \
96		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
97		"$1" || res=1
98
99	# check %l or %ll format specifier
100	awk -v FOLDERS='lib drivers app examples' \
101		-v EXPRESSIONS='%ll*[xud]' \
102		-v RET_ON_FAIL=1 \
103		-v MESSAGE='Using %l format, prefer %PRI*64 if type is [u]int64_t' \
104		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
105		"$1" || res=1
106
107	# refrain from new additions of 16/32/64 bits rte_atomicNN_xxx()
108	awk -v FOLDERS="lib drivers app examples" \
109		-v EXPRESSIONS="rte_atomic[0-9][0-9]_.*\\\(" \
110		-v RET_ON_FAIL=1 \
111		-v MESSAGE='Using rte_atomicNN_xxx' \
112		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
113		"$1" || res=1
114
115	# refrain from new additions of rte_smp_[r/w]mb()
116	awk -v FOLDERS="lib drivers app examples" \
117		-v EXPRESSIONS="rte_smp_(r|w)?mb\\\(" \
118		-v RET_ON_FAIL=1 \
119		-v MESSAGE='Using rte_smp_[r/w]mb' \
120		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
121		"$1" || res=1
122
123	# refrain from using compiler __sync_xxx builtins
124	awk -v FOLDERS="lib drivers app examples" \
125		-v EXPRESSIONS="__sync_.*\\\(" \
126		-v RET_ON_FAIL=1 \
127		-v MESSAGE='Using __sync_xxx builtins' \
128		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
129		"$1" || res=1
130
131	# refrain from using compiler __rte_atomic_thread_fence()
132	# It should be avoided on x86 for SMP case.
133	awk -v FOLDERS="lib drivers app examples" \
134		-v EXPRESSIONS="__rte_atomic_thread_fence\\\(" \
135		-v RET_ON_FAIL=1 \
136		-v MESSAGE='Using __rte_atomic_thread_fence, prefer rte_atomic_thread_fence' \
137		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
138		"$1" || res=1
139
140	# refrain from using compiler __atomic_xxx builtins
141	awk -v FOLDERS="lib drivers app examples" \
142		-v EXPRESSIONS="__atomic_.*\\\( __ATOMIC_(RELAXED|CONSUME|ACQUIRE|RELEASE|ACQ_REL|SEQ_CST)" \
143		-v RET_ON_FAIL=1 \
144		-v MESSAGE='Using __atomic_xxx/__ATOMIC_XXX built-ins, prefer rte_atomic_xxx/rte_memory_order_xxx' \
145		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
146		"$1" || res=1
147
148	# refrain from using some pthread functions
149	awk -v FOLDERS="lib drivers app examples" \
150		-v EXPRESSIONS="pthread_(create|join|detach|set(_?name_np|affinity_np)|attr_set(inheritsched|schedpolicy))\\\(" \
151		-v RET_ON_FAIL=1 \
152		-v MESSAGE='Using pthread functions, prefer rte_thread' \
153		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
154		"$1" || res=1
155
156	# forbid use of __reserved which is a reserved keyword in Windows system headers
157	awk -v FOLDERS="lib drivers app examples" \
158		-v EXPRESSIONS='\\<__reserved\\>' \
159		-v RET_ON_FAIL=1 \
160		-v MESSAGE='Using __reserved' \
161		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
162		"$1" || res=1
163
164	# forbid use of __alignof__
165	awk -v FOLDERS="lib drivers app examples" \
166		-v EXPRESSIONS='\\<__alignof__\\>' \
167		-v RET_ON_FAIL=1 \
168		-v MESSAGE='Using __alignof__, prefer C11 alignof' \
169		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
170		"$1" || res=1
171
172	# forbid use of __typeof__
173	awk -v FOLDERS="lib drivers app examples" \
174		-v EXPRESSIONS='\\<__typeof__\\>' \
175		-v RET_ON_FAIL=1 \
176		-v MESSAGE='Using __typeof__, prefer typeof' \
177		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
178		"$1" || res=1
179
180	# forbid use of non abstracted bit count operations
181	awk -v FOLDERS="lib drivers app examples" \
182		-v EXPRESSIONS='\\<__builtin_(clz|clzll|ctz|ctzll|popcount|popcountll)\\>' \
183		-v RET_ON_FAIL=1 \
184		-v MESSAGE='Using __builtin helpers for bit count operations' \
185		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
186		"$1" || res=1
187
188	# forbid inclusion of Linux header for PCI constants
189	awk -v FOLDERS="lib drivers app examples" \
190		-v EXPRESSIONS='include.*linux/pci_regs\\.h' \
191		-v RET_ON_FAIL=1 \
192		-v MESSAGE='Using linux/pci_regs.h, prefer rte_pci.h' \
193		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
194		"$1" || res=1
195
196	# forbid use of variadic argument pack extension in macros
197	awk -v FOLDERS="lib drivers app examples" \
198		-v EXPRESSIONS='#[[:space:]]*define.*[^(,[:space:]]\\.\\.\\.[[:space:]]*)' \
199		-v RET_ON_FAIL=1 \
200		-v MESSAGE='Do not use variadic argument pack in macros' \
201		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
202		"$1" || res=1
203
204	# forbid use of experimental build flag except in examples
205	awk -v FOLDERS='lib drivers app' \
206		-v EXPRESSIONS='-DALLOW_EXPERIMENTAL_API allow_experimental_apis' \
207		-v RET_ON_FAIL=1 \
208		-v MESSAGE='Using experimental build flag for in-tree compilation' \
209		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
210		"$1" || res=1
211
212	# refrain from using RTE_LOG_REGISTER for drivers and libs
213	awk -v FOLDERS='lib drivers' \
214		-v EXPRESSIONS='\\<RTE_LOG_REGISTER\\>' \
215		-v RET_ON_FAIL=1 \
216		-v MESSAGE='Using RTE_LOG_REGISTER, prefer RTE_LOG_REGISTER_(DEFAULT|SUFFIX)' \
217		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
218		"$1" || res=1
219
220	# forbid non-internal thread in drivers and libs
221	awk -v FOLDERS='lib drivers' \
222		-v EXPRESSIONS="rte_thread_(set_name|create_control)\\\(" \
223		-v RET_ON_FAIL=1 \
224		-v MESSAGE='Prefer rte_thread_(set_prefixed_name|create_internal_control)' \
225		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
226		"$1" || res=1
227
228	# forbid rte_ symbols in cnxk base driver
229	awk -v FOLDERS='drivers/common/cnxk/roc_*' \
230		-v SKIP_FILES='roc_platform*' \
231		-v EXPRESSIONS="rte_ RTE_" \
232		-v RET_ON_FAIL=1 \
233		-v MESSAGE='Use plt_ symbols instead of rte_ API in cnxk base driver' \
234		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
235		"$1" || res=1
236
237	# forbid inclusion of driver specific headers in apps and examples
238	awk -v FOLDERS='app examples' \
239		-v EXPRESSIONS='include.*_driver\\.h include.*_pmd\\.h' \
240		-v RET_ON_FAIL=1 \
241		-v MESSAGE='Using driver specific headers in applications' \
242		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
243		"$1" || res=1
244
245	# prevent addition of tests not in one of our test suites
246	awk -v FOLDERS='app/test' \
247		-v EXPRESSIONS='REGISTER_TEST_COMMAND' \
248		-v RET_ON_FAIL=1 \
249		-v MESSAGE='Using REGISTER_TEST_COMMAND instead of REGISTER_<suite_name>_TEST' \
250		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
251		"$1" || res=1
252
253	# SVG must be included with wildcard extension to allow conversion
254	awk -v FOLDERS='doc' \
255		-v EXPRESSIONS='::[[:space:]]*[^[:space:]]*\\.svg' \
256		-v RET_ON_FAIL=1 \
257		-v MESSAGE='Using explicit .svg extension instead of .*' \
258		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
259		"$1" || res=1
260
261	# links must prefer https over http
262	awk -v FOLDERS='doc' \
263		-v EXPRESSIONS='http://.*dpdk.org' \
264		-v RET_ON_FAIL=1 \
265		-v MESSAGE='Using non https link to dpdk.org' \
266		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
267		"$1" || res=1
268
269	# prefer Sphinx references for internal documentation
270	awk -v FOLDERS='doc' \
271		-v EXPRESSIONS='//doc.dpdk.org/guides/' \
272		-v RET_ON_FAIL=1 \
273		-v MESSAGE='Using explicit URL to doc.dpdk.org, prefer :ref: or :doc:' \
274		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
275		"$1" || res=1
276
277	# '// XXX is not set' must be preferred over '#undef XXX'
278	awk -v FOLDERS='config/rte_config.h' \
279		-v EXPRESSIONS='#undef' \
280		-v RET_ON_FAIL=1 \
281		-v MESSAGE='Using "#undef XXX", prefer "// XXX is not set"' \
282		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
283		"$1" || res=1
284
285	return $res
286}
287
288check_experimental_tags() { # <patch>
289	res=0
290
291	cat "$1" |awk '
292	BEGIN {
293		current_file = "";
294		ret = 0;
295	}
296	/^+++ b\// {
297		current_file = $2;
298	}
299	/^+.*__rte_experimental/ {
300		if (current_file ~ ".c$" ) {
301			print "Please only put __rte_experimental tags in " \
302				"headers ("current_file")";
303			ret = 1;
304		}
305		if ($1 != "+__rte_experimental" || $2 != "") {
306			print "__rte_experimental must appear alone on the line" \
307				" immediately preceding the return type of a function."
308			ret = 1;
309		}
310	}
311	END {
312		exit ret;
313	}' || res=1
314
315	return $res
316}
317
318check_internal_tags() { # <patch>
319	res=0
320
321	cat "$1" |awk '
322	BEGIN {
323		current_file = "";
324		ret = 0;
325	}
326	/^+++ b\// {
327		current_file = $2;
328	}
329	/^+.*__rte_internal/ {
330		if (current_file ~ ".c$" ) {
331			print "Please only put __rte_internal tags in " \
332				"headers ("current_file")";
333			ret = 1;
334		}
335		if ($1 != "+__rte_internal" || $2 != "") {
336			print "__rte_internal must appear alone on the line" \
337				" immediately preceding the return type of" \
338				" a function."
339			ret = 1;
340		}
341	}
342	END {
343		exit ret;
344	}' || res=1
345
346	return $res
347}
348
349check_aligned_attributes() { # <patch>
350	res=0
351
352	for token in __rte_aligned __rte_cache_aligned __rte_cache_min_aligned; do
353		if [ $(grep -E '^\+.*\<'$token'\>' "$1" | \
354				grep -vE '\<(struct|union)[[:space:]]*'$token'\>' | \
355				wc -l) != 0 ]; then
356			echo "Please use $token only for struct or union types alignment."
357			res=1
358		fi
359	done
360
361	return $res
362}
363
364check_release_notes() { # <patch>
365	rel_notes_prefix=doc/guides/rel_notes/release_
366	IFS=. read year month release < VERSION
367	current_rel_notes=${rel_notes_prefix}${year}_${month}.rst
368
369	! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" |
370		grep -v $current_rel_notes
371}
372
373number=0
374range='origin/main..'
375quiet=false
376verbose=false
377while getopts hn:qr:v ARG ; do
378	case $ARG in
379		n ) number=$OPTARG ;;
380		q ) quiet=true ;;
381		r ) range=$OPTARG ;;
382		v ) verbose=true ;;
383		h ) print_usage ; exit 0 ;;
384		? ) print_usage ; exit 1 ;;
385	esac
386done
387shift $(($OPTIND - 1))
388
389if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
390	default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl"
391	if [ -f "$default_path" ] && [ -x "$default_path" ] ; then
392		DPDK_CHECKPATCH_PATH="$default_path"
393	else
394		print_usage >&2
395		echo
396		echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
397		exit 1
398	fi
399fi
400
401print_headline() { # <title>
402	printf '\n### %s\n\n' "$1"
403	headline_printed=true
404}
405
406total=0
407status=0
408
409check () { # <patch-file> <commit>
410	local ret=0
411	local subject=''
412	headline_printed=false
413
414	total=$(($total + 1))
415	if [ -n "$1" ] ; then
416		tmpinput=$1
417	else
418		tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
419		trap "rm -f '$tmpinput'" INT
420
421		if [ -n "$2" ] ; then
422			git format-patch --find-renames \
423			--no-stat --stdout -1 $commit > "$tmpinput"
424		else
425			cat > "$tmpinput"
426		fi
427	fi
428
429	# Subject can be on 2 lines
430	subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput")
431	! $verbose || print_headline "$subject"
432
433	! $verbose || printf 'Running checkpatch.pl:\n'
434	report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
435	if [ $? -ne 0 ] ; then
436		$headline_printed || print_headline "$subject"
437		printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
438		ret=1
439	fi
440
441	! $verbose || printf '\nChecking API additions/removals:\n'
442	report=$($VALIDATE_NEW_API "$tmpinput")
443	if [ $? -ne 0 ] ; then
444		$headline_printed || print_headline "$subject"
445		printf '%s\n' "$report"
446		ret=1
447	fi
448
449	! $verbose || printf '\nChecking forbidden tokens additions:\n'
450	report=$(check_forbidden_additions "$tmpinput")
451	if [ $? -ne 0 ] ; then
452		$headline_printed || print_headline "$subject"
453		printf '%s\n' "$report"
454		ret=1
455	fi
456
457	! $verbose || printf '\nChecking __rte_experimental tags:\n'
458	report=$(check_experimental_tags "$tmpinput")
459	if [ $? -ne 0 ] ; then
460		$headline_printed || print_headline "$subject"
461		printf '%s\n' "$report"
462		ret=1
463	fi
464
465	! $verbose || printf '\nChecking __rte_internal tags:\n'
466	report=$(check_internal_tags "$tmpinput")
467	if [ $? -ne 0 ] ; then
468		$headline_printed || print_headline "$subject"
469		printf '%s\n' "$report"
470		ret=1
471	fi
472
473	! $verbose || printf '\nChecking alignment attributes:\n'
474	report=$(check_aligned_attributes "$tmpinput")
475	if [ $? -ne 0 ] ; then
476		$headline_printed || print_headline "$subject"
477		printf '%s\n' "$report"
478		ret=1
479	fi
480
481	! $verbose || printf '\nChecking release notes updates:\n'
482	report=$(check_release_notes "$tmpinput")
483	if [ $? -ne 0 ] ; then
484		$headline_printed || print_headline "$subject"
485		printf '%s\n' "$report"
486		ret=1
487	fi
488
489	if [ "$tmpinput" != "$1" ]; then
490		rm -f "$tmpinput"
491		trap - INT
492	fi
493	[ $ret -eq 0 ] && return 0
494
495	status=$(($status + 1))
496}
497
498if [ -n "$1" ] ; then
499	for patch in "$@" ; do
500		check "$patch" ''
501	done
502elif [ ! -t 0 ] ; then # stdin
503	check '' ''
504else
505	if [ $number -eq 0 ] ; then
506		commits=$(git rev-list --reverse $range)
507	else
508		commits=$(git rev-list --reverse --max-count=$number HEAD)
509	fi
510	for commit in $commits ; do
511		check '' $commit
512	done
513fi
514pass=$(($total - $status))
515$quiet || printf '\n%d/%d valid patch' $pass $total
516$quiet || [ $pass -le 1 ] || printf 'es'
517$quiet || printf '\n'
518exit $status
519