xref: /dpdk/devtools/checkpatches.sh (revision e9fd1ebf981f361844aea9ec94e17f4bda5e1479)
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_release_notes() { # <patch>
340	rel_notes_prefix=doc/guides/rel_notes/release_
341	IFS=. read year month release < VERSION
342	current_rel_notes=${rel_notes_prefix}${year}_${month}.rst
343
344	! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" |
345		grep -v $current_rel_notes
346}
347
348number=0
349range='origin/main..'
350quiet=false
351verbose=false
352while getopts hn:qr:v ARG ; do
353	case $ARG in
354		n ) number=$OPTARG ;;
355		q ) quiet=true ;;
356		r ) range=$OPTARG ;;
357		v ) verbose=true ;;
358		h ) print_usage ; exit 0 ;;
359		? ) print_usage ; exit 1 ;;
360	esac
361done
362shift $(($OPTIND - 1))
363
364if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
365	default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl"
366	if [ -f "$default_path" ] && [ -x "$default_path" ] ; then
367		DPDK_CHECKPATCH_PATH="$default_path"
368	else
369		print_usage >&2
370		echo
371		echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
372		exit 1
373	fi
374fi
375
376print_headline() { # <title>
377	printf '\n### %s\n\n' "$1"
378	headline_printed=true
379}
380
381total=0
382status=0
383
384check () { # <patch-file> <commit>
385	local ret=0
386	local subject=''
387	headline_printed=false
388
389	total=$(($total + 1))
390	if [ -n "$1" ] ; then
391		tmpinput=$1
392	else
393		tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
394		trap "rm -f '$tmpinput'" INT
395
396		if [ -n "$2" ] ; then
397			git format-patch --find-renames \
398			--no-stat --stdout -1 $commit > "$tmpinput"
399		else
400			cat > "$tmpinput"
401		fi
402	fi
403
404	# Subject can be on 2 lines
405	subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput")
406	! $verbose || print_headline "$subject"
407
408	! $verbose || printf 'Running checkpatch.pl:\n'
409	report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
410	if [ $? -ne 0 ] ; then
411		$headline_printed || print_headline "$subject"
412		printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
413		ret=1
414	fi
415
416	! $verbose || printf '\nChecking API additions/removals:\n'
417	report=$($VALIDATE_NEW_API "$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 forbidden tokens additions:\n'
425	report=$(check_forbidden_additions "$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_experimental tags:\n'
433	report=$(check_experimental_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 __rte_internal tags:\n'
441	report=$(check_internal_tags "$tmpinput")
442	if [ $? -ne 0 ] ; then
443		$headline_printed || print_headline "$subject"
444		printf '%s\n' "$report"
445		ret=1
446	fi
447
448	! $verbose || printf '\nChecking release notes updates:\n'
449	report=$(check_release_notes "$tmpinput")
450	if [ $? -ne 0 ] ; then
451		$headline_printed || print_headline "$subject"
452		printf '%s\n' "$report"
453		ret=1
454	fi
455
456	if [ "$tmpinput" != "$1" ]; then
457		rm -f "$tmpinput"
458		trap - INT
459	fi
460	[ $ret -eq 0 ] && return 0
461
462	status=$(($status + 1))
463}
464
465if [ -n "$1" ] ; then
466	for patch in "$@" ; do
467		check "$patch" ''
468	done
469elif [ ! -t 0 ] ; then # stdin
470	check '' ''
471else
472	if [ $number -eq 0 ] ; then
473		commits=$(git rev-list --reverse $range)
474	else
475		commits=$(git rev-list --reverse --max-count=$number HEAD)
476	fi
477	for commit in $commits ; do
478		check '' $commit
479	done
480fi
481pass=$(($total - $status))
482$quiet || printf '\n%d/%d valid patch' $pass $total
483$quiet || [ $pass -le 1 ] || printf 'es'
484$quiet || printf '\n'
485exit $status
486