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:
@@ -154,6 +154,86 @@ should_skip_check() {
|
|||||||
return 1 # Don't skip
|
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 "═══════════════════════════════════════════════════════════════"
|
||||||
echo "SERVER TOOLKIT QA SCAN - PHASE 3"
|
echo "SERVER TOOLKIT QA SCAN - PHASE 3"
|
||||||
echo "Path: $TOOLKIT_PATH"
|
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 "Found: $count case statements missing standalone support"
|
||||||
echo ""
|
echo ""
|
||||||
} >> "$REPORT"
|
} >> "$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)
|
# PERFORMANCE CHECKS (INFO level - not counted as issues)
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user