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