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