| 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}" |