+++ /dev/null
-#!/usr/bin/env bash
-#
-# Copyright 2019 Michael D. Lowis
-#-------------------------------------------------------------------------------
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# Wrapper script for system compiler that enables some flags by default and scans
-# for library dependencies.
-#
-# Compilation Features:
-# * Enables --std=c99 -pedantic
-# * Enables -Wall -Wextra -Werror
-# * Defines AUTOLIB() macro to specify dependencies in sources and headers
-# * Auto-includes commonly include headers such as stddef.h, stdio.h, etc.
-#
-# Linking Features:
-# * Scans objects for AUTOLIB()-ed libs, and adds the -l flags for them
-
-# predeclare our variables
-declare -a objects
-declare -a libpaths
-compile=false
-runtime='
-#define _POSIX_C_SOURCE 200809L
-#define _XOPEN_SOURCE 700
-#define AUTOLIB(n) \
- int __autolib_##n __attribute__ ((weak));
-#include <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <limits.h>
-#include <sys/types.h>
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <ctype.h>
-#include <unistd.h>
-#include <fcntl.h>
-' # end of runtime definition
-
-# scan the rest of the options for lib paths, libs and objects
-for i do
- case "$i" in
- *.[ao]) # Add objects and static libs to object list
- objects+=("$i") ;;
-
- -L*) # Add libpaths to the search list
- libpaths+=("${i#-L}") ;;
-
- -c|-E) # Mark this as compilation/preprocess only
- compile=true ;;
- esac
-done
-
-# if we're compiling, generate the header, compile, and exit
-if $compile; then
- genfile=$(mktemp)
- printf '%s\n' "$runtime" > "$genfile"
- cc -isystem "${genfile%/*}" -include "${genfile##*/}" --std=c99 -pedantic -Wall -Wextra -Werror "$@"
- status=$?
- rm "$genfile"
- exit "$status"
-fi
-
-# scan objects/libs for referenced libraries
-scan_for_libs(){
- [[ $# -ne 0 ]] && nm "$@" | sed -n '/__autolib/ s/.*_\+autolib_// p' | sort -u
-}
-
-# if we made it here, we must be linking. scan for dependencies
-mapfile -t libraries < <(scan_for_libs "${objects[@]}")
-cc "$@" "${libraries[@]/#/-l}"
--- /dev/null
+#!/usr/bin/env bash
+#
+# Copyright 2019 Michael D. Lowis
+#-------------------------------------------------------------------------------
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Compiler wrapper script that runs a crude linting process on each source file
+# before passing arguments along to the compiler. The linter rules are inspired
+# by The Power of 10 Rules defined by NASA/JPL
+# (http://web.eecs.umich.edu/~imarkov/10rules.pdf)
+#
+# Below are the NASA/JPL rules that this script attempts to check along with
+# descriptions of what the script checks for and ways it differs from the
+# original rule.
+#
+# 1) "Restrict all code to very simple control flow constructs - do not use goto
+# statements, setjmp or longjmp constructs, or direct or indirect recursion."
+#
+# * error on use of goto, setjmp, and longjmp
+# * error on multiple return statements in a single function
+# * recursion is *not* checked for
+#
+# 2) "No function should be longer than what can be printed on a single sheet of
+# paper in a standard format with one line per statement and one line per
+# declaration. Typically, this means no more than about 60 lines of code per
+# function."
+#
+# * warn on functions greater than 60 lines.
+#
+# 3) "The code's assertion density should average to minimally two assertions per
+# function. Assertions must be used to check for anomalous conditions that
+# should never happen in real-life executions. Assertions must be side-effect
+# free and should be defined as Boolean tests. When an assertion fails, an
+# explicit recovery action must be taken such as returning an error condition
+# to the caller of the function that executes the failing assertion. Any
+# assertion for which a static checking tool can prove that it can never
+# fail or never hold violates this rule."
+#
+# * warn when public functions have no assertions (assert, ensure, require)
+#
+# 4) "The use of the preprocessor must be limited to the inclusion of header files
+# and simple macro definitions. Token pasting, variable argument lists
+# (ellipses), and recursive macro calls are not allowed. All macros must
+# expand into complete syntactic units. The use of conditional compilation
+# directives must be kept to a minimum."
+#
+# * error on use of #if or #undef
+# * (TODO) error on use of function like macros
+#
+# 5) "All code must be compiled, from the first day of development, with all
+# compiler warnings enabled at the most pedantic setting available. All code
+# must compile without warnings. All code must also be checked daily with at
+# least one, but preferably more than one, strong static source code analyzer
+# and should pass all analyses with zero warnings."
+#
+# * invokes compiler -Wall -Wextra -Werror -std=c99 -pedantic
+
+declare -a sources
+declare -a objects
+declare -a libpaths
+compile=false
+runtime='
+#define _POSIX_C_SOURCE 200809L
+#define _XOPEN_SOURCE 700
+#define AUTOLIB(n) \
+ int __autolib_##n __attribute__ ((weak));
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+' # end of runtime definition
+
+# scan the rest of the options for lib paths, libs and objects
+for i do
+ case "$i" in
+ *.c) # Add source to the list
+ sources+=("$i") ;;
+
+ *.[ao]) # Add objects and static libs to object list
+ objects+=("$i") ;;
+
+ -L*) # Add libpaths to the search list
+ libpaths+=("${i#-L}") ;;
+
+ -c|-E) # Mark this as compilation/preprocess only
+ compile=true ;;
+ esac
+done
+
+script=$(cat <<EOS
+ # initialize some state
+ BEGIN {
+ MAXLINES = 50
+ syms["setjmp"] = 1
+ syms["longjmp"] = 1
+ syms["goto"] = 1
+ keyw["if"] = 1
+ keyw["for"] = 1
+ keyw["while"] = 1
+ cpp["undef"] = 1
+ cpp["if"] = 1
+ asserts["assert"] = 1
+ asserts["require"] = 1
+ asserts["ensure"] = 1
+ }
+
+ # define helper functions
+ function warn(msg){
+ print FILENAME ":" FNR ": warning:", msg
+ }
+
+ function error(msg){
+ print FILENAME ":" FNR ": error:", msg
+ code = 1
+ }
+
+ function get_syms(line, ary){
+ gsub(/\W+/, " ")
+ split(\$0, ary, " ")
+ }
+
+ function scan_func(){
+ if (M[i] == "return")
+ {
+ nreturns += 1
+ }
+ else if (asserts[M[i]])
+ {
+ nasserts += 1
+ }
+ }
+
+ function process_idents(opening, closing){
+ level += opening
+ for (i = 0; i < length(M); i++)
+ {
+ if (paren && (M[i] == "static"))
+ {
+ privatefn = 1
+ }
+ else if (keyw[M[i]])
+ {
+ control = 1
+ }
+ else if (syms[M[i]])
+ {
+ error("use of prohibited symbol '" M[i] "'");
+ }
+ else if (level > 0)
+ {
+ scan_func()
+ }
+ }
+ level -= closing
+ }
+
+ function close_function(){
+ if (nreturns > 1)
+ {
+ error("functions should have a maximum of one return statement")
+ }
+
+ if ((FNR - linenum) > MAXLINES)
+ {
+ warn("function is " (FNR - linenum) " lines long. blocks larger than 50 lines are considered too complex")
+ }
+
+ if (level < 0)
+ {
+ error("too many closing braces detected")
+ }
+
+ if (!privatefn && (nasserts == 0))
+ {
+ warn("public function has no assertions")
+ }
+
+ privatefn = 0
+ nasserts = 0
+ nreturns = 0
+ infunc = 0
+ }
+
+ # if we're in a comment block, look for the end
+ (incomment && match(\$0, /\*\//)) {
+ incomment = 0
+ }
+
+ # scan preprocessor statements
+ match(\$0, /^\s*#\s*(\w+)/, M) {
+ if (cpp[M[1]])
+ {
+ error("use of #" M[1] " is prohibited")
+ }
+ }
+
+ # scan all identifiers for prohibited symbols
+ !incomment && /^\s*[^#]/ {
+ control = 0
+ paren = match(\$0, /\(/)
+ gsub(/"(\\\\"|[^"])*"/, "") # ignore string literals
+ sub(/\/\/.*$/, "") # ignore line comments
+ gsub(/\/\*.*\*\//, "") # ignore block comments on single line
+ incomment = sub(/\/\*.*$/, "") # ignore block comment starts
+ sub(/^.*\*\//, "") # ignore block comment stops
+ opening = gsub(/{/, "")
+ closing = gsub(/}/, "")
+ get_syms(\$0, M);
+
+ # reset state if not in a function
+ if (level == 0)
+ {
+ linenum = FNR + 1
+ privatefn = 0
+ nasserts = 0
+ nreturns = 0
+ infunc = 0
+ }
+
+ if (!level && paren && opening)
+ {
+ warn("function braces should be on their own lines")
+ }
+
+ process_idents(opening, closing)
+
+ if (control && opening)
+ {
+ warn("braces of control constructs should be on their own lines")
+ }
+
+ if (infunc && level <= 0)
+ {
+ close_function()
+ }
+
+ if (level == 0 && paren)
+ {
+ infunc = 1
+ }
+ }
+
+ # set return code based on errors detected
+ END {
+ if (level > 0)
+ {
+ error("reached EOF with open block");
+ }
+ exit code
+ }
+EOS
+)
+
+# run the linter and bail if it fails
+if [[ ${#sources[@]} -gt 0 ]]; then
+ #printf "%s\n" "$script"
+ if ! gawk "$script" "${sources[@]}"; then
+ exit 1
+ fi
+fi
+
+# if we're compiling, generate the header, compile, and exit
+if $compile; then
+ genfile=$(mktemp)
+ printf '%s\n' "$runtime" > "$genfile"
+ cc -isystem "${genfile%/*}" -include "${genfile##*/}" --std=c99 -pedantic -Wall -Wextra -Werror "$@"
+ status=$?
+ rm "$genfile"
+ exit "$status"
+fi
+
+# scan objects/libs for referenced libraries
+scan_for_libs(){
+ [[ $# -ne 0 ]] && nm "$@" | sed -n '/__autolib/ s/.*_\+autolib_// p' | sort -u
+}
+
+# if we made it here, we must be linking. scan for dependencies
+mapfile -t libraries < <(scan_for_libs "${objects[@]}")
+cc "$@" "${libraries[@]/#/-l}"
+