| 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 |
|
| 61 |
1.1 2.2 |
|
| 62 |
1.1 |
|
| 65 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 66 |
1.1 |
|
| 69 |
1.1 |
|
| 84 |
1.1 |
|
| 85 |
1.1 |
|
| 90 |
1.1 2.2 |
|
| 91 |
1.1 |
|
| 93 |
1.1 |