#!/usr/bin/env zsh # Check possible problems in the MPFR source. set -e setopt EXTENDED_GLOB # mpfrlint can be run from the tools directory oldpwd=$PWD [[ -d src ]] || [[ $oldpwd:t != tools ]] || cd .. err=0 if [[ -t 1 ]] then term=1 pfx="" sfx="" fi err-if-output() { local dir msg checkoutput=1 checkstatus=1 output st h line while [[ $1 == -* ]] do case $1 in --dir=*) dir=${1#--dir=} ;; --msg=*) msg=${1#--msg=} ;; -o) # The command generates output even in case of success; # do not regard this as an error. checkoutput="" ;; -t) # The command normally returns with a non-zero exit status; # do not regard this as an error. checkstatus="" ;; *) echo "unrecognized option '$1' for $0" >&2 exit 1 ;; esac shift done [[ -n $dir ]] && pushd $dir set +e # Note: the double quotes for $@[2,-1] are important to pass empty args. output=(${(f)"$("$@[2,-1]" 2>&1)"}) st=$? if [[ ( -n "$checkstatus" && $st -ne 0 ) || ( -n "$checkoutput" && -n "$output" ) ]] then if [[ -n "$1" && -t 1 ]] then [[ -n "$pfx" ]] || pfx=$(tput 2>/dev/null bold) [[ -n "$sfx" ]] || sfx=$(tput 2>/dev/null sgr0) fi h=${1:+$pfx$1:$sfx } for line in ${output:-"exit status = $st"} do printf "%s%s\n" $h $line done [[ -n "$msg" ]] && printf "%s %s\n" "$pfx-->$sfx" "$msg" err=1 fi set -e [[ -n $dir ]] && popd # Do not fail. true } if [[ $1 == test ]] then export LC_ALL=C err-if-output Err-e exit 17 err-if-output Err-f false err-if-output Err-t true err-if-output "" echo "Empty first argument" err-if-output "1 line" echo foo err-if-output "2 lines" printf "foo\nbar\n" err-if-output -o Err-f false err-if-output -o Err-t true err-if-output -o "cp test" cp err-if-output -o "1 line" echo foo err-if-output -t Err-f false err-if-output -t Err-t true err-if-output -t error echo output echo "Test done." exit : < # is never included in this case (see comment in the code). names='E[0-9A-Z]|FE_[A-Z]|LC_[A-Z]|(PRI|SCN)[Xa-z]|SIG_?[A-Z]|TIME_[A-Z]' msg='ISO C 7.1.3 (Reserved identifiers) and 7.31 (Future library directions).' grep -E "# *define ($names)" $srctests | \ grep -v 'src/mpfr-gmp.h:#define EXP(' | \ err-if-output --msg="$msg" "reserved identifiers (macro names)" cat # Detect the possible use of forbidden macros in mpfr.h, such as those # starting with "HAVE_" or "WANT_". Public macros defined by MPFR must # start with "MPFR_". err-if-output -t "" perl -ne ' /^#/ && ! /^# *error / or next; while (/\b([_A-Z]+)\b/g) { my $m = $1; $m =~ /^(_*MPFR_|_*GMP_|__(GNUC|ICC|STDC)(_|$)|_MSC_|U?INTMAX_C$)/ and next; print "Forbidden macro in mpfr.h line $.: $m\n" }' src/mpfr.h err-if-output -t "math.h" grep '^# *include *' src/*.c flaglist="underflow|overflow|divby0|nanflag|inexflag|erangeflag" grep -E "mpfr_($flaglist)_p" src/*.{c,h} | \ grep -v -i 'mpfr_clear_' | \ grep -v '^src/exceptions.c:' | \ grep -v '^src/mpfr-impl.h:#define mpfr_.*_p()' | \ grep -v '^src/mpfr.h:__MPFR_DECLSPEC ' | \ err-if-output "flags" cat grep -E "mpfr_(clear|set)_($flaglist) *\(" src/*.{c,h} | \ grep -v '^src/exceptions.c:' | \ grep -v '^src/mpfr.h:' | \ err-if-output "flags" cat grconf() { grep -v '^dnl ' acinclude.m4 configure.ac | \ err-if-output -t "grconf '$1'" grep -E "$1" } grconf '^(.*if +|[[:space:]]*)(test|\[).* == ' grconf '="`' grconf '[^a-z][ef]?grep[^a-z]' grconf '[^a-z]sed[^a-z]' err-if-output --msg="Use GMP_NUMB_BITS instead." \ -t "GMP_LIMB_BITS" grep GMP_LIMB_BITS $srctests grep GMP_RND $srctests | err-if-output -t "GMP_RND*" grep -v '#define GMP_RND' # Note: GMP internals should not be used. There are some exceptions, # normally used only if WANT_GMP_INTERNALS is defined (this should be # checked manually). grep '__gmp[nz]_' $srctests | \ grep -v __gmpn_rootrem | \ grep -v __gmpn_sbpi1_divappr_q | \ grep -v __gmpn_invert_limb | \ grep -v __gmpn_rsblsh_n | \ err-if-output "GMP internals" cat # __MPFR_DECLSPEC (based on the __MPFR_WITHIN_MPFR status) must be used # instead of __GMP_DECLSPEC (based on the __GMP_WITHIN_GMP status, always # undefined in MPFR); in particular, the __GMP_DECLSPEC occurrences from # GMP's longlong.h file must be changed to __MPFR_DECLSPEC when porting # this file to MPFR. See: # https://sympa.inria.fr/sympa/arc/mpfr/2018-08/msg00000.html # https://sympa.inria.fr/sympa/arc/mpfr/2018-08/msg00001.html err-if-output --msg="Use __MPFR_DECLSPEC instead." -t "__GMP_DECLSPEC" \ grep --exclude=src/mpfr.h __GMP_DECLSPEC $srctests err-if-output --msg="Use mpfr_limb_ptr and mpfr_limb_srcptr instead." \ -t "mp_ptr and mp_srcptr" grep -E 'mp_(src)?ptr' $srctests # Do not use __mpfr_struct structure members in .c files. err-if-output -t "__mpfr_struct members" \ grep -E '[^0-9a-z_]_mpfr_(prec|sign|exp|d)' {src,tests}/*.c # Detect some uses of "x != x" and "x == x". If this occurs in source code, # x is probably a native FP number (otherwise the test is useless), but in # such a case, the DOUBLE_ISNAN macro should be used. err-if-output -t "x != x and x == x tests" \ grep '( *\([^[:space:]]*\) *[!=]= *\1 *)' $srctests for i in exp prec rnd do grep "[^a-z]mp_${i}_t" $srctests | \ grep -v "\(# *define\|# *ifndef\|typedef\) *mp_${i}_t" | \ grep -v "\[mp_${i}_t\]" | \ err-if-output "mp_*_t" cat done for file in $srctests do err-if-output "MPFR_LOG_MSG format" perl -e ' my $f = do { local $/; <> }; while ($f =~ /MPFR_LOG_MSG\s*\(\s*\(.*?\)\s*\)/gs) { my $s = $&; print "$ARGV: $s\n" if index($s,"\\n\"") < 0 || $s !~ /"\s*,/ }' $file done # Macros of the form: # #define FOO { ... } # may be unsafe and could yield obscure failures where writing "FOO;" as # this is here a block followed by a null statement. The following form # is preferred in most of the cases: # #define FOO do { ... } while (0) # so that "FOO;" is a single statement. # To avoid false positives, a comment can be inserted, e.g.: # #define FOO /* initializer */ { 0, 17 } for file in $srctests do err-if-output "Missing 'do ... while (0)'" perl -e ' while (<>) { my $s = $_; while ($s =~ s/\\\n//) { $s .= <> } $s =~ /^#\s*define\s+\w+(\([^)]*\))?\s*{/ and $s =~ tr/ \t/ /s, print "$ARGV: $s"; }' $file done # Do not use snprintf as it is not available in ISO C90. # Even on platforms where it is available, the prototype # may not be included (e.g. with gcc -ansi), so that the # code may be compiled incorrectly. grep '[^a-z_]snprintf *([^)]' $srctests | \ err-if-output -t "snprintf" grep -v '/\*.*[^a-z_]snprintf *([^)]' # Constant checking should use either MPFR_STAT_STATIC_ASSERT # or MPFR_ASSERTN(0) for not yet implemented corner cases. # This test is a heuristic. # Note: as long as the support of _MPFR_EXP_FORMAT = 4 is not complete, # run-time assertions involving MPFR_EMAX_MAX, LONG_MAX, etc. should be # used instead of static assertions to allow testing and correction of # the code (then the removal of the assertions). grep 'MPFR_ASSERT[DN][^a-z]*;' src/*.c | grep -v 'MPFR_ASSERTN *(0)' | \ grep -v '\(MPFR_EMAX_MAX\|MPFR_EXP_MAX\).*LONG_MAX' | \ grep -v '\(MPFR_EMIN_MIN\|MPFR_EXP_MIN\).*LONG_MIN' | \ grep -v MPFR_BLOCK_EXCEP | \ err-if-output --msg="Use MPFR_STAT_STATIC_ASSERT in general." \ -t "Constant checking" cat # ASSERT and ASSERT_ALWAYS must not be used for assertion checking. # Use MPFR_STAT_STATIC_ASSERT for static assertions, otherwise either # MPFR_ASSERTD (debug mode / hint for the compiler) or MPFR_ASSERTN. err-if-output -t "ASSERT / ASSERT_ALWAYS" \ grep -E '[^_]ASSERT(_ALWAYS)? *(\(|$)' {src,tests}/*.c # Use MPFR_TMP_LIMBS_ALLOC. err-if-output -t "Use MPFR_TMP_LIMBS_ALLOC" \ grep 'MPFR_TMP_ALLOC.*\(BYTES_PER_MP_LIMB\|sizeof.*mp_limb_t\)' src/*.c # Use simple mp_limb_t constants: MPFR_LIMB_ZERO, MPFR_LIMB_ONE, etc. grep '(mp_limb_t) *-\?[01][^0-9]' $srctests | grep -v '#define MPFR_LIMB_' | \ grep -v 'MPFR_ASSERTN *(MPFR_MANT.*== *(mp_limb_t)' | \ err-if-output -t "Use simple mp_limb_t constants" cat # "~0;" has already been seen in the code. Check also a bit more. # Such code is either an error or poorly written, and in this case, quite # fragile. In practice, this may work because most (or all) platforms use # two's complement, and 0 being of type int (signed), this will give -1; # then when converted to a larger unsigned type, this will give the maximum # value as expected. However the reader may not know this explanation, and # a small change of the code can easily make it incorrect. The correct way # to obtain this result is to cast the constant to the expected type, then # do the bitwise complement; alternatively, cast -1 to the expected type. # For limbs, the macro MPFR_LIMB_MAX should be used. err-if-output \ --msg="Possibly incorrect type for ~ (use MPFR_LIMB_MAX for limbs)" \ -t "Suspicious code" \ grep '~[0-9] *[-+*&|;]' $srctests # Code possibly wrong with some C/GMP implementations. In short, if the # right operand of a shift depends on GMP_NUMB_BITS, then the left operand # should be of type mp_limb_t. There might be false positives. err-if-output \ --msg="Use a constant of type mp_limb_t instead of unsigned long?" \ -t "Suspicious code" \ grep -E '[^A_Za-z_][0-9]+[UL]* *<<.*GMP_NUMB_BITS' src/*.c for file in $srctests */Makefile.am acinclude.m4 configure.ac do # Note: this is one less that the POSIX minimum limit in case # implementations are buggy like POSIX examples. :) err-if-output "" perl -ne "/.{2047,}/ and print \ \"Line \$. of file $file has more than 2046 bytes.\n\"" "$file" done # Code style: a sign decimal constant for mpfr_set_inf and mpfr_set_zero # should be either 1 or -1 (except for the tests in tset.c). grep -E 'mpfr_set_(inf|zero) *\([^,]*, *[-+]?([02-9]|1[^)])' $srctests | \ err-if-output -t "mpfr_set_(inf|zero) second argument" \ grep -v tests/tset\\.c: # In general, one needs to include mpfr-impl.h (note that some platforms # such as MS Windows use a config.h, which is included by mpfr-impl.h). for file in src/*.c do [[ "$file" == src/(jyn_asympt|mini-gmp|round_raw_generic).c ]] || \ grep -q '^# *include *"\(mpfr-impl\|fits.*\|gen_inverse\|random_deviate\)\.h"' $file || \ { echo "Missing '#include \"mpfr-impl.h\"' in $file?" && err=1 } done # Re-seeding mpfr_rands in the tests is bad practice, as it would affect # later tests, defeating the purpose of GMP_CHECK_RANDOMIZE. err-if-output \ --msg='Do not re-seed mpfr_rands; use another gmp_randstate_t variable.' \ -t "mpfr_rands" grep --exclude=tests.c \ 'gmp_randseed.*mpfr_rands' tests/*.{c,h} # "mpfr-impl.h" must not be included directly by the test programs. # Otherwise __MPFR_WITHIN_MPFR will be defined, yielding failures # under MS Windows with DLL. err-if-output \ --msg='Include "mpfr-test.h" instead of "mpfr-impl.h" to prevent __MPFR_WITHIN_MPFR from being defined.' \ -t "mpfr-impl.h inclusion" grep --exclude=mpfr-test.h \ '^ *# *include *"mpfr-impl.h"' tests/*.{c,h} # Check that the usual test programs call tests_start_mpfr and tests_end_mpfr. tprg=($(sed -n '/^check_PROGRAMS/,/[^\\]$/ { s/.*=// s/\\// p }' tests/Makefile.am)) [[ -n $tprg ]] for t in $tprg do [[ $t != mpf*_compat ]] || continue tc=tests/$t.c if grep -q "main *(" $tc; then for fn in tests_start_mpfr tests_end_mpfr do err-if-output "missing call to $fn in $tc" grep -q "$fn *();" $tc done fi done # mpfr_printf-like functions shouldn't be used in the tests, # as they need (HAVE_STDARG defined). for file in tests/*.c do sed '/#if\(def\| *defined *(\)\? *HAVE_STDARG/,/#\(endif\|else\) .*HAVE_STDARG/d /\/\*.*\*\//d /\/\*/,/\*\//d' $file | \ err-if-output -t "unprotected mpfr_printf-like function call in $file" \ grep "mpfr_[a-z]*printf" done grep __gmp_ tests/*.c | \ err-if-output -t "__gmp_" grep -v '^tests/tabort_defalloc' grep -E '[^a-z_](m|re)alloc *\(' tests/*.c | \ err-if-output -t "alloc" grep -Ev '^tests/(memory|talloc-cache).c:' err-if-output --dir=doc "check-typography" ./check-typography fdlv1="`sed -n '/Version / { s/.*Version // s/,.*// p q }' doc/fdl.texi`" fdlv2="`sed -n '/GNU Free Documentation License/ { s/.*Version // s/ or.*// p q }' doc/mpfr.texi`" [[ $fdlv1 == $fdlv2 ]] || { cat < /dev/null 2> /dev/null; then err-if-output "ck-mparam" tools/ck-mparam else echo "Warning! gcc is not installed. Cannot run ck-mparam." >&2 fi # Note about the TZ value: GMT0 and UTC0 are both specified by POSIX, # and UTC0 is the preferred value, but old systems only accept GMT0. # The "0" is important ("GMT" alone does not work on Tru64 Unix). texisvnd=`LC_ALL=C TZ=GMT0 svn info doc/mpfr.texi 2> /dev/null | sed -n 's/Last Changed Date:.*, [0-9]* \([A-Z][a-z][a-z] [0-9][0-9][0-9][0-9]\)).*/\1/p'` if [[ $? -eq 0 ]] && [[ -n "$texisvnd" ]] then texidate=`sed -n 's/@set UPDATED-MONTH \([A-Z][a-z][a-z]\).*\( [0-9][0-9][0-9][0-9]\)/\1\2/p' doc/mpfr.texi` [[ $texidate == $texisvnd ]] || { cat < /dev/null 2> /dev/null; then doc=(FAQ.html Makefile.am README.dev add-with-carry.c check-typography \ faq.xsl fdl.texi mini-gmp mpfr.texi sum.txt update-faq) err-if-output "codespell" codespell ${term:+--enable-colors} \ -q3 -I codespell.ignore -x codespell.exclude \ AUTHORS BUGS INSTALL NEWS README TODO doc/${^doc} examples $srctests else echo "Warning! codespell is not installed. Cannot check spelling." >&2 fi err-if-output "ck-clz_tab" tools/ck-clz_tab err-if-output "ck-inits-clears" tools/ck-inits-clears err-if-output "ck-version-info" tools/ck-version-info ############################################################################ [[ $err -eq 0 ]] || echo "Terminated with errors." >&2 exit $err