master
sh 152 lines 5.42 KB
Raw
1 #!/usr/bin/env bash
2 # Bundle one Coverity defect into a per-CID working directory.
3 #
4 # Usage:
5 # prepare-defect.sh [--force] <cid> [<scope>]
6 #
7 # <scope> defaults to "outstanding" -- it's only used as a subdirectory name
8 # for organizing artifacts, not a Coverity API parameter.
9 #
10 # Inputs (must already exist; produced by fetch-table.sh + fetch-details.sh):
11 # .local/audits/coverity/raw/<scope>-all.json
12 # .local/audits/coverity/details/<scope>/cid-<N>.json (or details/cid-<N>.json)
13 #
14 # Outputs (under <audit-dir>/triage/<scope>/cid-<N>/):
15 # defect-summary.json -- the row from the table dump
16 # defect-details.json -- the per-defect details
17 # source-context.c -- ~150 lines around the main event in the flagged file
18 # TODO.md -- per-defect scratch (so review work doesn't pile at repo root)
19 #
20 # Idempotent. Re-running keeps existing files; pass --force to regenerate.
21 #
22 # This script does NOT prescribe a review pipeline. After preparing the bundle,
23 # how you triage it (single model, multiple models, manual review, etc.) is
24 # adhoc and should be agreed with the user.
25
26 set -euo pipefail
27
28 # shellcheck source=./_lib.sh
29 # shellcheck disable=SC1091
30 source "$(dirname "$0")/_lib.sh"
31
32 FORCE=0
33 if [[ "${1:-}" == "--force" ]]; then
34 FORCE=1; shift
35 fi
36
37 CID="${1:?usage: $0 [--force] <cid> [<scope>]}"
38 SCOPE="${2:-outstanding}"
39
40 cov_require_numeric_cid "${CID}"
41
42 # Scope is used as a path component (.../triage/<scope>/cid-N/...). Reject
43 # anything that could path-escape; allow only simple lowercase identifiers.
44 if [[ ! "${SCOPE}" =~ ^[a-z][a-z0-9_-]*$ ]]; then
45 echo -e "${COV_RED}[ERROR]${COV_NC} scope must match ^[a-z][a-z0-9_-]*\$ (got: '${SCOPE}')" >&2
46 exit 1
47 fi
48
49 ROOT="$(cov_repo_root)"
50 AUDIT="$(cov_audit_dir)"
51
52 OUT_DIR="${AUDIT}/triage/${SCOPE}/cid-${CID}"
53 ROW_FILE="${AUDIT}/raw/${SCOPE}-all.json"
54 DETAILS_SRC="${AUDIT}/details/${SCOPE}/cid-${CID}.json"
55
56 if [[ ( ! -f "${DETAILS_SRC}" || ! -r "${DETAILS_SRC}" ) \
57 && -f "${AUDIT}/details/cid-${CID}.json" \
58 && -r "${AUDIT}/details/cid-${CID}.json" ]]; then
59 DETAILS_SRC="${AUDIT}/details/cid-${CID}.json"
60 fi
61
62 if [[ ! -f "${ROW_FILE}" || ! -r "${ROW_FILE}" ]]; then
63 echo -e "${COV_RED}Missing ${ROW_FILE}. Run fetch-table.sh first.${COV_NC}" >&2
64 exit 1
65 fi
66 if [[ ! -f "${DETAILS_SRC}" || ! -r "${DETAILS_SRC}" ]]; then
67 echo -e "${COV_RED}Missing ${DETAILS_SRC}. Run fetch-details.sh first.${COV_NC}" >&2
68 exit 1
69 fi
70
71 mkdir -p "${OUT_DIR}"
72
73 # defect-summary.json -- CID is validated numeric above, --argjson is safe.
74 summary="${OUT_DIR}/defect-summary.json"
75 if [[ ! -s "${summary}" || "${FORCE}" == "1" ]]; then
76 jq --argjson cid "${CID}" '.[] | select(.cid==$cid)' "${ROW_FILE}" > "${summary}"
77 if [[ ! -s "${summary}" ]]; then
78 echo -e "${COV_RED}CID ${CID} not found in ${ROW_FILE}. Wrong scope, or table is stale?${COV_NC}" >&2
79 rm -f "${summary}"
80 exit 2
81 fi
82 fi
83
84 # defect-details.json
85 details="${OUT_DIR}/defect-details.json"
86 if [[ ! -s "${details}" || "${FORCE}" == "1" ]]; then
87 cp "${DETAILS_SRC}" "${details}"
88 fi
89
90 # source-context.c
91 display_file="$(jq -r '.displayFile // ""' "${summary}")"
92 repo_file="${display_file#/}" # strip leading slash; paths are repo-relative
93 main_line="$(jq -r '
94 (.occurrences[0].eventSets[0].eventTree | map(select(.main==true))[0]
95 // .occurrences[0].eventSets[0].eventTree[-1])
96 | .lineNumber // 1
97 ' "${details}")"
98
99 ctx_file="${OUT_DIR}/source-context.c"
100 if [[ ! -s "${ctx_file}" || "${FORCE}" == "1" ]]; then
101 # Validation:
102 # 1. Non-empty: an empty displayFile would resolve `${ROOT}/${repo_file}`
103 # to `${ROOT}/`, which IS a readable directory.
104 # 2. No `..`: prevent path traversal escaping the repo. Coverity's
105 # displayFile is its source-tree path; legitimate values never
106 # contain `..`. Reject anything that does.
107 # 3. Regular file + readable.
108 repo_file_ok=1
109 [[ -z "${repo_file}" ]] && repo_file_ok=0
110 [[ "${repo_file}" == *..* ]] && repo_file_ok=0
111 [[ -f "${ROOT}/${repo_file}" && -r "${ROOT}/${repo_file}" ]] || repo_file_ok=0
112 if (( repo_file_ok )); then
113 start=$((main_line - 100))
114 (( start < 1 )) && start=1
115 end=$((main_line + 50))
116 {
117 printf '// Extracted from %s (lines %d..%d; main event at line %d).\n' \
118 "${repo_file}" "${start}" "${end}" "${main_line}"
119 printf '// Line numbers are the original file line numbers.\n\n'
120 awk -v s="${start}" -v e="${end}" 'NR>=s && NR<=e {printf "%5d %s\n", NR, $0}' \
121 "${ROOT}/${repo_file}"
122 } > "${ctx_file}"
123 else
124 echo -e "${COV_YELLOW}Source ${repo_file} not in tree -- CODE_GONE candidate.${COV_NC}" >&2
125 printf '// Source file %s not present in the current tree.\n// Candidate CODE_GONE.\n' \
126 "${repo_file}" > "${ctx_file}"
127 fi
128 fi
129
130 # TODO.md (so review work doesn't create a TODO at repo root)
131 per_todo="${OUT_DIR}/TODO.md"
132 if [[ ! -s "${per_todo}" || "${FORCE}" == "1" ]]; then
133 short_type="$(jq -r '.displayType // "unknown"' "${summary}")"
134 impact="$(jq -r '.displayImpact // "unspecified"' "${summary}")"
135 cat > "${per_todo}" <<EOF
136 # CID ${CID} -- ${short_type} (${impact} impact)
137
138 File: ${repo_file}
139 Line: ${main_line}
140
141 Bundle:
142 - defect-summary.json
143 - defect-details.json
144 - source-context.c
145
146 (Use this file for per-defect notes, plan, decisions. Keep all per-defect
147 artifacts inside this directory.)
148 EOF
149 fi
150
151 echo -e "${COV_GREEN}Prepared ${OUT_DIR}${COV_NC}" >&2
152 ls -1 "${OUT_DIR}"