xref: /dpdk/devtools/checkpatches.sh (revision fac4bc0d06c98e15788f47da4e08a8cfb2ba942b)
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 compiler __builtin_*
181	awk -v FOLDERS="lib drivers app examples" \
182		-v SKIP_FILES='lib/eal/ drivers/.*/base/ drivers/.*osdep.h$' \
183		-v EXPRESSIONS='\\<__builtin_' \
184		-v RET_ON_FAIL=1 \
185		-v MESSAGE='Using __builtin helpers, prefer EAL macros' \
186		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
187		"$1" || res=1
188
189	# forbid inclusion of Linux header for PCI constants
190	awk -v FOLDERS="lib drivers app examples" \
191		-v EXPRESSIONS='include.*linux/pci_regs\\.h' \
192		-v RET_ON_FAIL=1 \
193		-v MESSAGE='Using linux/pci_regs.h, prefer rte_pci.h' \
194		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
195		"$1" || res=1
196
197	# forbid use of variadic argument pack extension in macros
198	awk -v FOLDERS="lib drivers app examples" \
199		-v EXPRESSIONS='#[[:space:]]*define.*[^(,[:space:]]\\.\\.\\.[[:space:]]*)' \
200		-v RET_ON_FAIL=1 \
201		-v MESSAGE='Do not use variadic argument pack in macros' \
202		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
203		"$1" || res=1
204
205	# forbid use of __rte_packed_begin with enums
206	awk -v FOLDERS='lib drivers app examples' \
207		-v EXPRESSIONS='enum.*__rte_packed_begin' \
208		-v RET_ON_FAIL=1 \
209		-v MESSAGE='Using __rte_packed_begin with enum is not allowed' \
210		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
211		"$1" || res=1
212
213	# forbid use of experimental build flag except in examples
214	awk -v FOLDERS='lib drivers app' \
215		-v EXPRESSIONS='-DALLOW_EXPERIMENTAL_API allow_experimental_apis' \
216		-v RET_ON_FAIL=1 \
217		-v MESSAGE='Using experimental build flag for in-tree compilation' \
218		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
219		"$1" || res=1
220
221	# refrain from using RTE_LOG_REGISTER for drivers and libs
222	awk -v FOLDERS='lib drivers' \
223		-v EXPRESSIONS='\\<RTE_LOG_REGISTER\\>' \
224		-v RET_ON_FAIL=1 \
225		-v MESSAGE='Using RTE_LOG_REGISTER, prefer RTE_LOG_REGISTER_(DEFAULT|SUFFIX)' \
226		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
227		"$1" || res=1
228
229	# forbid non-internal thread in drivers and libs
230	awk -v FOLDERS='lib drivers' \
231		-v EXPRESSIONS="rte_thread_(set_name|create_control)\\\(" \
232		-v RET_ON_FAIL=1 \
233		-v MESSAGE='Prefer rte_thread_(set_prefixed_name|create_internal_control)' \
234		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
235		"$1" || res=1
236
237	# forbid rte_ symbols in cnxk base driver
238	awk -v FOLDERS='drivers/common/cnxk/roc_*' \
239		-v SKIP_FILES='roc_platform*' \
240		-v EXPRESSIONS="rte_ RTE_" \
241		-v RET_ON_FAIL=1 \
242		-v MESSAGE='Use plt_ symbols instead of rte_ API in cnxk base driver' \
243		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
244		"$1" || res=1
245
246	# forbid inclusion of driver specific headers in apps and examples
247	awk -v FOLDERS='app examples' \
248		-v EXPRESSIONS='include.*_driver\\.h include.*_pmd\\.h' \
249		-v RET_ON_FAIL=1 \
250		-v MESSAGE='Using driver specific headers in applications' \
251		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
252		"$1" || res=1
253
254	# prevent addition of tests not in one of our test suites
255	awk -v FOLDERS='app/test' \
256		-v EXPRESSIONS='REGISTER_TEST_COMMAND' \
257		-v RET_ON_FAIL=1 \
258		-v MESSAGE='Using REGISTER_TEST_COMMAND instead of REGISTER_<suite_name>_TEST' \
259		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
260		"$1" || res=1
261
262	# SVG must be included with wildcard extension to allow conversion
263	awk -v FOLDERS='doc' \
264		-v EXPRESSIONS='::[[:space:]]*[^[:space:]]*\\.svg' \
265		-v RET_ON_FAIL=1 \
266		-v MESSAGE='Using explicit .svg extension instead of .*' \
267		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
268		"$1" || res=1
269
270	# links must prefer https over http
271	awk -v FOLDERS='doc' \
272		-v EXPRESSIONS='http://.*dpdk.org' \
273		-v RET_ON_FAIL=1 \
274		-v MESSAGE='Using non https link to dpdk.org' \
275		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
276		"$1" || res=1
277
278	# prefer Sphinx references for internal documentation
279	awk -v FOLDERS='doc' \
280		-v EXPRESSIONS='//doc.dpdk.org/guides/' \
281		-v RET_ON_FAIL=1 \
282		-v MESSAGE='Using explicit URL to doc.dpdk.org, prefer :ref: or :doc:' \
283		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
284		"$1" || res=1
285
286	# '// XXX is not set' must be preferred over '#undef XXX'
287	awk -v FOLDERS='config/rte_config.h' \
288		-v EXPRESSIONS='#undef' \
289		-v RET_ON_FAIL=1 \
290		-v MESSAGE='Using "#undef XXX", prefer "// XXX is not set"' \
291		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
292		"$1" || res=1
293
294	return $res
295}
296
297check_experimental_tags() { # <patch>
298	res=0
299
300	cat "$1" |awk '
301	BEGIN {
302		current_file = "";
303		ret = 0;
304	}
305	/^+++ b\// {
306		current_file = $2;
307	}
308	/^+.*__rte_experimental/ {
309		if (current_file ~ ".c$" ) {
310			print "Please only put __rte_experimental tags in " \
311				"headers ("current_file")";
312			ret = 1;
313		}
314		if ($1 != "+__rte_experimental" || $2 != "") {
315			print "__rte_experimental must appear alone on the line" \
316				" immediately preceding the return type of a function."
317			ret = 1;
318		}
319	}
320	END {
321		exit ret;
322	}' || res=1
323
324	return $res
325}
326
327check_internal_tags() { # <patch>
328	res=0
329
330	cat "$1" |awk '
331	BEGIN {
332		current_file = "";
333		ret = 0;
334	}
335	/^+++ b\// {
336		current_file = $2;
337	}
338	/^+.*__rte_internal/ {
339		if (current_file ~ ".c$" ) {
340			print "Please only put __rte_internal tags in " \
341				"headers ("current_file")";
342			ret = 1;
343		}
344		if ($1 != "+__rte_internal" || $2 != "") {
345			print "__rte_internal must appear alone on the line" \
346				" immediately preceding the return type of" \
347				" a function."
348			ret = 1;
349		}
350	}
351	END {
352		exit ret;
353	}' || res=1
354
355	return $res
356}
357
358check_aligned_attributes() { # <patch>
359	res=0
360
361	for token in __rte_aligned __rte_cache_aligned __rte_cache_min_aligned; do
362		if [ $(grep -E '^\+.*\<'$token'\>' "$1" | \
363				grep -vE '\<(struct|union)[[:space:]]*'$token'\>' | \
364				wc -l) != 0 ]; then
365			echo "Please use $token only for struct or union types alignment."
366			res=1
367		fi
368	done
369
370	return $res
371}
372
373check_packed_attributes() { # <patch>
374	res=0
375
376	if [ $(grep -E '^\+.*__rte_packed_begin' "$1" | \
377			grep -vE '\<struct[[:space:]]*__rte_packed_begin\>' | \
378			grep -vE '\<union[[:space:]]*__rte_packed_begin\>' | \
379			grep -vE '\<__rte_cache_aligned[[:space:]]*__rte_packed_begin\>' | \
380			grep -vE '\<__rte_cache_min_aligned[[:space:]]*__rte_packed_begin\>' | \
381			grep -vE '\<__rte_aligned\(.*\)[[:space:]]*__rte_packed_begin\>' | \
382			wc -l) != 0 ]; then
383		echo "Use __rte_packed_begin only after struct, union or alignment attributes."
384		res=1
385	fi
386
387	begin_count=$(grep '__rte_packed_begin' "$1" | wc -l)
388	end_count=$(grep '__rte_packed_end' "$1" | wc -l)
389	if [ $begin_count != $end_count ]; then
390		echo "__rte_packed_begin and __rte_packed_end should always be used in pairs."
391		res=1
392	fi
393
394	return $res
395}
396
397check_release_notes() { # <patch>
398	rel_notes_prefix=doc/guides/rel_notes/release_
399	IFS=. read year month release < VERSION
400	current_rel_notes=${rel_notes_prefix}${year}_${month}.rst
401
402	! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" |
403		grep -v $current_rel_notes
404}
405
406number=0
407range='origin/main..'
408quiet=false
409verbose=false
410while getopts hn:qr:v ARG ; do
411	case $ARG in
412		n ) number=$OPTARG ;;
413		q ) quiet=true ;;
414		r ) range=$OPTARG ;;
415		v ) verbose=true ;;
416		h ) print_usage ; exit 0 ;;
417		? ) print_usage ; exit 1 ;;
418	esac
419done
420shift $(($OPTIND - 1))
421
422if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
423	default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl"
424	if [ -f "$default_path" ] && [ -x "$default_path" ] ; then
425		DPDK_CHECKPATCH_PATH="$default_path"
426	else
427		print_usage >&2
428		echo
429		echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
430		exit 1
431	fi
432fi
433
434print_headline() { # <title>
435	printf '\n### %s\n\n' "$1"
436	headline_printed=true
437}
438
439total=0
440status=0
441
442check () { # <patch-file> <commit>
443	local ret=0
444	local subject=''
445	headline_printed=false
446
447	total=$(($total + 1))
448	if [ -n "$1" ] ; then
449		tmpinput=$1
450	else
451		tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
452		trap "rm -f '$tmpinput'" INT
453
454		if [ -n "$2" ] ; then
455			git format-patch --find-renames \
456			--no-stat --stdout -1 $commit > "$tmpinput"
457		else
458			cat > "$tmpinput"
459		fi
460	fi
461
462	# Subject can be on 2 lines
463	subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput")
464	! $verbose || print_headline "$subject"
465
466	! $verbose || printf 'Running checkpatch.pl:\n'
467	report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
468	if [ $? -ne 0 ] ; then
469		$headline_printed || print_headline "$subject"
470		printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
471		ret=1
472	fi
473
474	! $verbose || printf '\nChecking API additions/removals:\n'
475	report=$($VALIDATE_NEW_API "$tmpinput")
476	if [ $? -ne 0 ] ; then
477		$headline_printed || print_headline "$subject"
478		printf '%s\n' "$report"
479		ret=1
480	fi
481
482	! $verbose || printf '\nChecking forbidden tokens additions:\n'
483	report=$(check_forbidden_additions "$tmpinput")
484	if [ $? -ne 0 ] ; then
485		$headline_printed || print_headline "$subject"
486		printf '%s\n' "$report"
487		ret=1
488	fi
489
490	! $verbose || printf '\nChecking __rte_experimental tags:\n'
491	report=$(check_experimental_tags "$tmpinput")
492	if [ $? -ne 0 ] ; then
493		$headline_printed || print_headline "$subject"
494		printf '%s\n' "$report"
495		ret=1
496	fi
497
498	! $verbose || printf '\nChecking __rte_internal tags:\n'
499	report=$(check_internal_tags "$tmpinput")
500	if [ $? -ne 0 ] ; then
501		$headline_printed || print_headline "$subject"
502		printf '%s\n' "$report"
503		ret=1
504	fi
505
506	! $verbose || printf '\nChecking alignment attributes:\n'
507	report=$(check_aligned_attributes "$tmpinput")
508	if [ $? -ne 0 ] ; then
509		$headline_printed || print_headline "$subject"
510		printf '%s\n' "$report"
511		ret=1
512	fi
513
514	! $verbose || printf '\nChecking packed attributes:\n'
515	report=$(check_packed_attributes "$tmpinput")
516	if [ $? -ne 0 ] ; then
517		$headline_printed || print_headline "$subject"
518		printf '%s\n' "$report"
519		ret=1
520	fi
521
522	! $verbose || printf '\nChecking release notes updates:\n'
523	report=$(check_release_notes "$tmpinput")
524	if [ $? -ne 0 ] ; then
525		$headline_printed || print_headline "$subject"
526		printf '%s\n' "$report"
527		ret=1
528	fi
529
530	if [ "$tmpinput" != "$1" ]; then
531		rm -f "$tmpinput"
532		trap - INT
533	fi
534	[ $ret -eq 0 ] && return 0
535
536	status=$(($status + 1))
537}
538
539if [ -n "$1" ] ; then
540	for patch in "$@" ; do
541		check "$patch" ''
542	done
543elif [ ! -t 0 ] ; then # stdin
544	check '' ''
545else
546	if [ $number -eq 0 ] ; then
547		commits=$(git rev-list --reverse $range)
548	else
549		commits=$(git rev-list --reverse --max-count=$number HEAD)
550	fi
551	for commit in $commits ; do
552		check '' $commit
553	done
554fi
555pass=$(($total - $status))
556$quiet || printf '\n%d/%d valid patch' $pass $total
557$quiet || [ $pass -le 1 ] || printf 'es'
558$quiet || printf '\n'
559exit $status
560