Add logic validation checks (CHECK 89-94) to QA script

Extended toolkit-qa-check.sh with 6 new logic validation checks to detect
semantic/behavioral errors that syntactic checks alone cannot catch:

- CHECK 89 (CRITICAL): Inverted/contradictory grep patterns
  Detects: grep -v X | grep X (always returns empty, logic error)

- CHECK 90 (HIGH): Type mismatch in comparisons
  Detects: Numeric operators on string variables ([ $var -lt 80 ] where var='75.23%')

- CHECK 91 (HIGH): Command argument ordering errors
  Detects: Filename before options in grep/sed (grep FILE -e PATTERN)

- CHECK 92 (HIGH): Missing command availability checks
  Detects: Uses of optional commands (nc, dig, host, jq) without 'command -v' checks

- CHECK 93 (HIGH): Uninitialized variables in AWK
  Detects: AWK variables set in patterns without BEGIN initialization

- CHECK 94 (HIGH): Undefined variable references
  Detects: Variables that appear undefined or typos in variable names

Also added helper functions for logic analysis:
- detect_grep_contradiction() - detects contradictory patterns
- infer_numeric_context() - determines if variable should be numeric
- check_awk_var_init() - checks AWK variable initialization
- get_function_vars() - extracts defined variables from functions

These checks complement the existing 88 checks by focusing on logic errors
that would pass syntax validation but cause runtime bugs.

Progress counter updated from /88 to /94 (6 new checks added).
Added qa-suppress annotations to prevent false positives in the QA script itself.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
cschantz
2026-02-07 01:04:24 -05:00
parent df9de9c95e
commit a19ad8ca3d
+272
View File
@@ -154,6 +154,86 @@ should_skip_check() {
return 1 # Don't skip
}
#==============================================================================
# LOGIC ANALYSIS HELPERS for new checks (89-94)
#==============================================================================
# Helper: Detect contradictory grep patterns in the same command chain
# Detects patterns like: grep -v pattern | grep pattern (always returns empty)
# qa-suppress:grep-contradict
detect_grep_contradiction() {
local line_content="$1"
# qa-suppress:grep-contradict
# Check for grep -v followed by grep looking for same/similar pattern
if echo "$line_content" | grep -qE 'grep.*-[vi].*[^a-zA-Z0-9_]\|.*grep.*[^a-zA-Z0-9_]'; then
# qa-suppress:grep-contradict
# More specific: grep -v X | grep X or grep -v "pattern" | grep "pattern"
if echo "$line_content" | grep -qE 'grep.*-v\s+"?([^"]+)"?.*\|.*grep[^-]*([^a-zA-Z0-9_|]|\1)'; then
return 0 # Found contradiction
fi
fi
return 1
}
# Helper: Check if variable is used in numeric context
# Returns 0 if variable appears to be used numerically, 1 if string context
infer_numeric_context() {
local var_name="$1"
local file="$2"
local line_num="$3"
# Get context around the variable
local context=$(sed -n "$((line_num-2)),$((line_num+2))p" "$file" 2>/dev/null)
# Check for numeric operators/comparisons
if echo "$context" | grep -qE "\[\s*\\\$?${var_name}[[:space:]]+-[lg][te]|${var_name}[[:space:]]*-[lg][te]|\${${var_name}[%#/:-]|}|\(\(\s*\${?${var_name}"; then
return 0 # Numeric context
fi
return 1 # String context
}
# Helper: Extract variable definitions from a function
# Returns list of variables defined (without $ prefix)
get_function_vars() {
local func_start="$1"
local func_end="$2"
local file="$3"
sed -n "${func_start},${func_end}p" "$file" 2>/dev/null | \
grep -oE '(local\s+|[a-zA-Z_][a-zA-Z0-9_]*\s*=)' | \
sed -E 's/(local\s+|=)//g' | \
sort -u
}
# Helper: Check if variable is initialized before use
# Returns 0 if found uninitialized, 1 if properly initialized
check_awk_var_init() {
local awk_block="$1"
local var_name="$2"
# Check if variable appears in BEGIN block (initialization)
if echo "$awk_block" | grep -qE 'BEGIN\s*\{[^}]*'"${var_name}"'\s*='; then
return 1 # Initialized in BEGIN
fi
# Check if variable is set before first use in main block
local first_use=$(echo "$awk_block" | grep -n "$var_name" | head -1 | cut -d: -f1)
local first_set=$(echo "$awk_block" | grep -n "${var_name}\s*=" | head -1 | cut -d: -f1)
if [ -z "$first_use" ]; then
return 1 # Variable not used
fi
if [ -z "$first_set" ] || [ "$first_use" -lt "$first_set" ]; then
return 0 # Used before set (uninitialized)
fi
return 1 # Properly initialized
}
echo "═══════════════════════════════════════════════════════════════"
echo "SERVER TOOLKIT QA SCAN - PHASE 3"
echo "Path: $TOOLKIT_PATH"
@@ -3200,6 +3280,198 @@ done < <(grep -n "case.*CONTROL_PANEL" "$TOOLKIT_PATH" --include="*.sh" -r 2>/de
echo "Found: $count case statements missing standalone support"
echo ""
} >> "$REPORT"
#==============================================================================
# CHECK 89: Inverted/Contradictory Grep Patterns (CRITICAL)
#==============================================================================
show_progress 89 "Inverted/contradictory grep patterns"
{
echo "## CHECK 89: Inverted/Contradictory Grep Patterns"
echo "Severity: CRITICAL"
echo "Pattern: grep -v X | grep ... (filters out then filters for, contradictory logic)"
echo "Impact: Logic error - filtering out pattern and then filtering for same pattern"
echo ""
count=0
# Look for common contradictory patterns without variable substitution in regex
while IFS=: read -r file line_num line_content; do
# qa-suppress:grep-contradict
# Simple detection: grep -v ... | grep (most likely contradictory)
# qa-suppress:grep-contradict
# Only flag if it looks suspicious (v flag before pipe, then grep after)
if echo "$line_content" | grep -qE 'grep.*-[viE].*\|.*grep' && \
! echo "$line_content" | grep -qE '\|\s*(sed|awk|cut|sort|uniq)'; then
# Likely a contradiction unless it's followed by sed/awk/cut which changes things
# qa-suppress:grep-contradict
# Check if it's not a false positive (e.g., grep -v comment | grep -c pattern)
if ! echo "$line_content" | grep -qE '(grep.*-c|grep.*-q|head|tail)'; then
if ! is_suppressed "$file" "$line_num" "grep-contradict"; then
echo "CRITICAL|$file|$line_num|[LOGIC-ERR] Contradictory grep: -v pipe followed by grep (may filter nothing)"
count_issue "CRITICAL"
((count++))
[ "$count" -ge 15 ] && break
fi
fi
fi
done < <(grep -rn 'grep.*-v.*|.*grep' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null)
echo "Found: $count contradictory grep patterns"
echo ""
} >> "$REPORT"
#==============================================================================
# CHECK 90: Type Mismatch in Comparisons (HIGH)
#==============================================================================
show_progress 90 "Type mismatch in comparisons"
{
echo "## CHECK 90: Type Mismatch in Comparisons"
echo "Severity: HIGH"
echo "Pattern: Numeric operator (-eq/-lt/-gt) on variables containing non-numeric values"
echo "Examples: [ \$rate -lt 80 ] where rate contains '%', [ \$status -eq 0 ] where status is string"
echo ""
count=0
# Pattern 1: Variables with % character used in numeric comparison
while IFS=: read -r file line_num line_content; do
if echo "$line_content" | grep -qE '\$[a-zA-Z_][a-zA-Z0-9_%]*.*-[lg][te]|rate.*%.*-[lg][te]'; then
if ! is_suppressed "$file" "$line_num" "type-mismatch"; then
echo "HIGH|$file|$line_num|[TYPE-MISMATCH] Numeric operator on variable that may contain non-numeric value"
count_issue "HIGH"
((count++))
[ "$count" -ge 15 ] && break
fi
fi
done < <(grep -rn -E '\[\s*\$[a-zA-Z_].*-[lg][te].*[0-9]|rate.*[%].*-[lg][te]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null)
echo "Found: $count type mismatches"
echo ""
} >> "$REPORT"
#==============================================================================
# CHECK 91: Command Argument Ordering Errors (HIGH)
#==============================================================================
show_progress 91 "Command argument ordering errors"
{
echo "## CHECK 91: Command Argument Ordering Errors"
echo "Severity: HIGH"
echo "Pattern: Filename variable before options in grep/sed (grep \$FILE -e PATTERN)"
echo "Impact: Command fails or behaves unexpectedly - filename treated as pattern"
echo ""
count=0
# Check for grep with filename variable followed by option flags
while IFS=: read -r file line_num line_content; do
if echo "$line_content" | grep -qE 'grep\s+\$[A-Z_].*\s+-[eEiIvlLrnhFxaw]|sed\s+\$[A-Z_].*\s+-[es]'; then
if ! is_suppressed "$file" "$line_num" "arg-order"; then
echo "HIGH|$file|$line_num|[ARG-ORDER] Command: filename variable before option flags"
count_issue "HIGH"
((count++))
[ "$count" -ge 15 ] && break
fi
fi
done < <(grep -rn 'grep.*\$[A-Z_].*-[eEiIvlLrnhFxaw]\|sed.*\$[A-Z_].*-[es]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null)
echo "Found: $count argument ordering errors"
echo ""
} >> "$REPORT"
#==============================================================================
# CHECK 92: Missing Command Availability Checks (HIGH)
#==============================================================================
show_progress 92 "Missing command availability checks"
{
echo "## CHECK 92: Missing Command Availability Checks"
echo "Severity: HIGH"
echo "Pattern: Uses optional command without checking availability (nc, dig, host, jq, etc)"
echo "Impact: Script fails on systems where command not installed"
echo ""
count=0
# Common commands that should be checked for availability
while IFS=: read -r file line_num line_content; do
# Check if line uses an optional command
if echo "$line_content" | grep -qE '\b(nc|dig|host|jq|yq|envsubst|getent|timeout)\b'; then
# Verify it's not a comment or already has a check
if ! echo "$line_content" | grep -qE 'command -v|which|#.*qa-suppress'; then
if ! is_suppressed "$file" "$line_num" "no-cmd-check"; then
echo "HIGH|$file|$line_num|[NO-CMD-CHECK] Optional command used without availability check"
count_issue "HIGH"
((count++))
[ "$count" -ge 15 ] && break
fi
fi
fi
done < <(grep -rn '\b(nc|dig|host|jq|yq|envsubst|timeout)\s' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null)
echo "Found: $count missing command checks"
echo ""
} >> "$REPORT"
#==============================================================================
# CHECK 93: Uninitialized Variables in AWK (HIGH)
#==============================================================================
show_progress 93 "Uninitialized AWK variables"
{
echo "## CHECK 93: Uninitialized Variables in AWK"
echo "Severity: HIGH"
echo "Pattern: AWK variables set in pattern but not initialized in BEGIN"
echo "Impact: Undefined behavior on non-matching lines, logic errors"
echo ""
count=0
# Look for AWK blocks that have assignments but no BEGIN block
while IFS=: read -r file line_num; do
awk_line=$(sed -n "${line_num}p" "$file" 2>/dev/null)
# Check if AWK block has pattern actions with variable assignments
if echo "$awk_line" | grep -qE '{\s*[a-z_]+\s*=' && \
! echo "$awk_line" | grep -qE 'BEGIN\s*\{'; then
if ! is_suppressed "$file" "$line_num" "awk-uninit"; then
echo "HIGH|$file|$line_num|[AWK-UNINIT] AWK variables assigned without BEGIN initialization"
count_issue "HIGH"
((count++))
[ "$count" -ge 15 ] && break
fi
fi
done < <(grep -rn "awk\s*'" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | cut -d: -f1,2)
echo "Found: $count AWK uninitialized variable issues"
echo ""
} >> "$REPORT"
#==============================================================================
# CHECK 94: Undefined Variable References (HIGH)
#==============================================================================
show_progress 94 "Undefined variable references"
{
echo "## CHECK 94: Undefined Variable References"
echo "Severity: HIGH"
echo "Pattern: Uses of variables that appear undefined (typos, scope issues)"
echo "Examples: \$TEMP_LOG (should be \$MAIL_LOG), undefined in subshells"
echo ""
count=0
# Look for common undefined variable patterns
while IFS=: read -r file line_num line_content; do
# Check for obvious typos/undefined variables
# Look for TEMP_* variables that might be undefined
if echo "$line_content" | grep -qE '\$TEMP_[A-Z_]+|\$[A-Z_]*LOG[A-Z_]*|\$[A-Z_]*FILE'; then
# Check if these are actually defined in the file
if ! grep -qE "^[[:space:]]*(TEMP_|declare TEMP_|local TEMP_)" "$file" 2>/dev/null; then
if ! is_suppressed "$file" "$line_num" "undef-var"; then
echo "HIGH|$file|$line_num|[UNDEF-VAR] Variable reference appears undefined in this file"
count_issue "HIGH"
((count++))
[ "$count" -ge 15 ] && break
fi
fi
fi
done < <(grep -rn '\$TEMP_\|TEMP_LOG\|\$[A-Z_]*FILE' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null)
echo "Found: $count undefined variable references"
echo ""
} >> "$REPORT"
#==============================================================================
# PERFORMANCE CHECKS (INFO level - not counted as issues)
#==============================================================================