DetectCredentialsStage.java

1
package org.egothor.methodatlas.command;
2
3
import java.nio.file.Path;
4
import java.util.ArrayList;
5
import java.util.List;
6
import java.util.Map;
7
import java.util.stream.IntStream;
8
import org.egothor.methodatlas.ai.CredentialTriageVerdict;
9
import org.egothor.methodatlas.api.CredentialCandidate;
10
import org.egothor.methodatlas.api.CredentialDetector;
11
import org.egothor.methodatlas.api.CredentialScanUnit;
12
import org.egothor.methodatlas.emit.CredentialFinding;
13
14
/**
15
 * Runs the deterministic detectors over the scan units, attaches best-effort
16
 * enclosing-method attribution, and produces {@link CredentialFinding}s. AI triage
17
 * (added later) enriches the findings afterwards; this stage leaves the triage
18
 * fields {@code null}.
19
 *
20
 * @since 4.1.0
21
 */
22
public final class DetectCredentialsStage {
23
24
    private final List<CredentialDetector> detectors;
25
    private final Map<Path, List<MethodRange>> attribution;
26
27
    /**
28
     * Creates a stage.
29
     *
30
     * @param detectors   loaded detectors; never {@code null}
31
     * @param attribution map of absolute file path to method ranges for
32
     *                    enclosing-method attribution; never {@code null}
33
     */
34
    public DetectCredentialsStage(List<CredentialDetector> detectors, Map<Path, List<MethodRange>> attribution) {
35
        this.detectors = List.copyOf(detectors);
36
        this.attribution = Map.copyOf(attribution);
37
    }
38
39
    /**
40
     * Detects and builds findings for the supplied units, in unit-then-source order.
41
     *
42
     * @param units units to scan; never {@code null}
43
     * @return findings; never {@code null}
44
     */
45
    @SuppressWarnings("PMD.CloseResource") // detectors are owned and closed by the caller, not by this stage
46
    public List<CredentialFinding> run(List<CredentialScanUnit> units) {
47
        List<CredentialFinding> findings = new ArrayList<>();
48
        for (CredentialScanUnit unit : units) {
49
            for (CredentialDetector detector : detectors) {
50
                for (CredentialCandidate c : detector.detect(unit)) {
51
                    findings.add(new CredentialFinding(c, unit.filePath(), unit.fqcn(),
52
                            methodFor(unit.filePath(), c.beginLine()), null, null, null));
53
                }
54
            }
55
        }
56 1 1. run : replaced return value with Collections.emptyList for org/egothor/methodatlas/command/DetectCredentialsStage::run → KILLED
        return findings;
57
    }
58
59
    private String methodFor(Path file, int line) {
60
        List<MethodRange> ranges = attribution.get(file);
61 2 1. methodFor : removed conditional - replaced equality check with true → KILLED
2. methodFor : removed conditional - replaced equality check with false → KILLED
        if (ranges == null) {
62 1 1. methodFor : replaced return value with "" for org/egothor/methodatlas/command/DetectCredentialsStage::methodFor → SURVIVED
            return null;
63
        }
64
        for (MethodRange r : ranges) {
65 6 1. methodFor : removed conditional - replaced comparison check with true → SURVIVED
2. methodFor : changed conditional boundary → SURVIVED
3. methodFor : changed conditional boundary → SURVIVED
4. methodFor : removed conditional - replaced comparison check with true → SURVIVED
5. methodFor : removed conditional - replaced comparison check with false → KILLED
6. methodFor : removed conditional - replaced comparison check with false → KILLED
            if (line >= r.beginLine() && line <= r.endLine()) {
66 1 1. methodFor : replaced return value with "" for org/egothor/methodatlas/command/DetectCredentialsStage::methodFor → KILLED
                return r.method();
67
            }
68
        }
69 1 1. methodFor : replaced return value with "" for org/egothor/methodatlas/command/DetectCredentialsStage::methodFor → NO_COVERAGE
        return null;
70
    }
71
72
    /**
73
     * Merges LLM triage verdicts into a per-class finding list by candidate index.
74
     * Findings without a matching verdict are returned unchanged (their triage
75
     * fields stay {@code null}).
76
     *
77
     * @param findings deterministic findings for one class, in candidate-index order;
78
     *                 never {@code null}
79
     * @param verdicts verdicts keyed by candidate index; never {@code null}
80
     * @return findings with triage fields populated where a verdict exists; never {@code null}
81
     */
82
    public static List<CredentialFinding> mergeVerdicts(List<CredentialFinding> findings,
83
            Map<Integer, CredentialTriageVerdict> verdicts) {
84 1 1. mergeVerdicts : replaced return value with Collections.emptyList for org/egothor/methodatlas/command/DetectCredentialsStage::mergeVerdicts → KILLED
        return IntStream.range(0, findings.size())
85 1 1. lambda$mergeVerdicts$0 : replaced return value with null for org/egothor/methodatlas/command/DetectCredentialsStage::lambda$mergeVerdicts$0 → KILLED
                .mapToObj(i -> applyVerdict(findings.get(i), verdicts.get(i)))
86
                .toList();
87
    }
88
89
    private static CredentialFinding applyVerdict(CredentialFinding finding, CredentialTriageVerdict verdict) {
90 2 1. applyVerdict : removed conditional - replaced equality check with false → KILLED
2. applyVerdict : removed conditional - replaced equality check with true → KILLED
        if (verdict == null) {
91 1 1. applyVerdict : replaced return value with null for org/egothor/methodatlas/command/DetectCredentialsStage::applyVerdict → KILLED
            return finding;
92
        }
93 1 1. applyVerdict : replaced return value with null for org/egothor/methodatlas/command/DetectCredentialsStage::applyVerdict → KILLED
        return new CredentialFinding(finding.candidate(), finding.filePath(), finding.fqcn(), finding.method(),
94
                verdict.credibilityScore(), verdict.endpoint(), verdict.rationale());
95
    }
96
97
    /**
98
     * A method's line span, used for attribution.
99
     *
100
     * @param method    simple method name
101
     * @param beginLine one-based first line
102
     * @param endLine   one-based last line
103
     * @since 4.1.0
104
     */
105
    public record MethodRange(String method, int beginLine, int endLine) {
106
    }
107
}

Mutations

56

1.1
Location : run
Killed by : org.egothor.methodatlas.command.DetectCredentialsStageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsStageTest]/[method:buildsFindingsFromCandidatesWithoutAi()]
replaced return value with Collections.emptyList for org/egothor/methodatlas/command/DetectCredentialsStage::run → KILLED

61

1.1
Location : methodFor
Killed by : org.egothor.methodatlas.command.DetectCredentialsStageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsStageTest]/[method:attributesEnclosingMethodWhenLineInRange()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : methodFor
Killed by : org.egothor.methodatlas.command.DetectCredentialsStageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsStageTest]/[method:buildsFindingsFromCandidatesWithoutAi()]
removed conditional - replaced equality check with false → KILLED

62

1.1
Location : methodFor
Killed by : none
replaced return value with "" for org/egothor/methodatlas/command/DetectCredentialsStage::methodFor → SURVIVED
Covering tests

65

1.1
Location : methodFor
Killed by : none
removed conditional - replaced comparison check with true → SURVIVED
Covering tests

2.2
Location : methodFor
Killed by : none
changed conditional boundary → SURVIVED Covering tests

3.3
Location : methodFor
Killed by : none
changed conditional boundary → SURVIVED Covering tests

4.4
Location : methodFor
Killed by : org.egothor.methodatlas.command.DetectCredentialsStageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsStageTest]/[method:attributesEnclosingMethodWhenLineInRange()]
removed conditional - replaced comparison check with false → KILLED

5.5
Location : methodFor
Killed by : org.egothor.methodatlas.command.DetectCredentialsStageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsStageTest]/[method:attributesEnclosingMethodWhenLineInRange()]
removed conditional - replaced comparison check with false → KILLED

6.6
Location : methodFor
Killed by : none
removed conditional - replaced comparison check with true → SURVIVED Covering tests

66

1.1
Location : methodFor
Killed by : org.egothor.methodatlas.command.DetectCredentialsStageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsStageTest]/[method:attributesEnclosingMethodWhenLineInRange()]
replaced return value with "" for org/egothor/methodatlas/command/DetectCredentialsStage::methodFor → KILLED

69

1.1
Location : methodFor
Killed by : none
replaced return value with "" for org/egothor/methodatlas/command/DetectCredentialsStage::methodFor → NO_COVERAGE

84

1.1
Location : mergeVerdicts
Killed by : org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest]/[method:findingWithoutMatchingVerdictIsUnchanged()]
replaced return value with Collections.emptyList for org/egothor/methodatlas/command/DetectCredentialsStage::mergeVerdicts → KILLED

85

1.1
Location : lambda$mergeVerdicts$0
Killed by : org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest]/[method:findingWithoutMatchingVerdictIsUnchanged()]
replaced return value with null for org/egothor/methodatlas/command/DetectCredentialsStage::lambda$mergeVerdicts$0 → KILLED

90

1.1
Location : applyVerdict
Killed by : org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest]/[method:findingWithoutMatchingVerdictIsUnchanged()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : applyVerdict
Killed by : org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest]/[method:mergesVerdictIntoFindingByIndex()]
removed conditional - replaced equality check with true → KILLED

91

1.1
Location : applyVerdict
Killed by : org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest]/[method:findingWithoutMatchingVerdictIsUnchanged()]
replaced return value with null for org/egothor/methodatlas/command/DetectCredentialsStage::applyVerdict → KILLED

93

1.1
Location : applyVerdict
Killed by : org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DetectCredentialsTriageMergeTest]/[method:mergesVerdictIntoFindingByIndex()]
replaced return value with null for org/egothor/methodatlas/command/DetectCredentialsStage::applyVerdict → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1