From ce3d751915dfb381c63524d78d3ff1f1d9153f69 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Tue, 1 Sep 2026 21:33:54 +0530 Subject: [PATCH 1/9] added RemediationKey record --- .../src/main/java/com/fortify/cli/app/FortifyCLI.java | 5 +++-- .../cli/aviator/fpr/processor/RemediationProcessor.java | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java index f2ab435cb7..2b0c14c5e8 100644 --- a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java +++ b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java @@ -17,9 +17,9 @@ /** *

This class provides the {@link #main(String[])} entrypoint into the application, - * and also registers some GraalVM features, allowing the application to run properly + * and also registers some GraalVM features, allowing the application to run properly * as GraalVM native images.

- * + * * @author Ruud Senden */ public class FortifyCLI { @@ -28,6 +28,7 @@ public class FortifyCLI { * @param args Command line options passed to Fortify CLI */ public static final void main(String[] args) { + System.out.println("FCLI started"); System.exit(execute(args)); } diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index 2f717dd66b..e30595afde 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -78,6 +78,8 @@ private record PendingFileWrite(String filename, Path filePath, String content, private record RollbackFileWrite(String filename, Path filePath, byte[] originalBytes) {} + private record RemediationKey(String fileName, Path filePath,int lineFrom,int lineTo,String comparisonCode){} + private enum SkipReason { SOURCE_FILE_MISSING("Source file missing"), SOURCE_FILE_OUTSIDE_SOURCE_DIR("Source file outside source directory"), @@ -157,6 +159,7 @@ public RemediationMetric processRemediationXML() { Set modifiedFiles = new LinkedHashSet<>(); Map skippedByReason = new LinkedHashMap<>(); + // Sanitize and normalize the base source directory path once. String trimmedSourceDir = sourceCodeDirectory.trim(); if (trimmedSourceDir.length() > 1 && From d0c2b3d7a270b9de724106905bc5f76ecb4c05df Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Tue, 1 Sep 2026 22:43:43 +0530 Subject: [PATCH 2/9] added RemediationKey record --- .../fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java | 1 - 1 file changed, 1 deletion(-) diff --git a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java index 2b0c14c5e8..34eaf65c90 100644 --- a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java +++ b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java @@ -28,7 +28,6 @@ public class FortifyCLI { * @param args Command line options passed to Fortify CLI */ public static final void main(String[] args) { - System.out.println("FCLI started"); System.exit(execute(args)); } From 2f1e9fa1dd829916a7202b9dddb3d94c8abffdc3 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Wed, 2 Sep 2026 11:52:07 +0530 Subject: [PATCH 3/9] added RemediationKey record --- .../fpr/processor/RemediationProcessor.java | 283 +++++++++++++++++- 1 file changed, 270 insertions(+), 13 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index e30595afde..ae16ef2b1d 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.zip.ZipFile; @@ -37,6 +39,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import com.fortify.cli.aviator.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -53,8 +56,6 @@ import com.fortify.cli.aviator.fpr.utils.SourceDecoders; import com.fortify.cli.aviator.fpr.utils.SourceEncoder; import com.fortify.cli.aviator.fpr.utils.SourceEncoder.SourceEncodeException; -import com.fortify.cli.aviator.util.FprHandle; -import com.fortify.cli.aviator.util.FuzzyContextSearcher; public class RemediationProcessor { private static final Logger LOG = LoggerFactory.getLogger(RemediationProcessor.class); @@ -156,9 +157,10 @@ public RemediationMetric processRemediationXML() { Document remediationDoc; int totalRemediations; int appliedRemediations; + int identicalRemediations = 0; Set modifiedFiles = new LinkedHashSet<>(); Map skippedByReason = new LinkedHashMap<>(); - + Map remediationLookup = new LinkedHashMap<>(); // Sanitize and normalize the base source directory path once. String trimmedSourceDir = sourceCodeDirectory.trim(); @@ -187,29 +189,109 @@ public RemediationMetric processRemediationXML() { LOG.debug("Loaded {} remediation entries from {}", totalRemediations, remediationPath); appliedRemediations = 0; for (int i = 0; i < remediationNodes.getLength(); i++) { - Element remediation = (Element) remediationNodes.item(i); - if (processRemediation(remediation, sourceBasePath, fvdlMetadata, modifiedFiles, skippedByReason)) { + Element remediation = + (Element) remediationNodes.item(i); + + String instanceId = + remediation.getAttribute("instanceId"); + + List remediationKeys = + createRemediationKeys( + remediation, + sourceBasePath); + + String identicalInstanceId = null; + + for (RemediationKey key : remediationKeys) { + String existingInstanceId = + remediationLookup.get(key); + + if (existingInstanceId != null) { + identicalInstanceId = existingInstanceId; + break; + } + } + + if (identicalInstanceId != null) { + identicalRemediations++; appliedRemediations++; + + LOG.info( + "Identical found: {}", + identicalInstanceId); + + LOG.info( + "Identical Remediation Applied: {} is identical to {}", + instanceId, + identicalInstanceId); + + continue; + } + + if (processRemediation( + remediation, + sourceBasePath, + fvdlMetadata, + modifiedFiles, + skippedByReason)) { + + appliedRemediations++; + + for (RemediationKey key : remediationKeys) { + remediationLookup.put(key, instanceId); + } } } } catch (ParserConfigurationException | SAXException | IOException e) { - LOG.error("Error parsing remediations.xml file: {}", remediationPath, e); - throw new AviatorTechnicalException("Error processing remediation.xml file.", e); + LOG.error( + "Error parsing remediations.xml file: {}", + remediationPath, + e); + + throw new AviatorTechnicalException( + "Error processing remediation.xml file.", + e); + } catch (AviatorTechnicalException e) { throw e; + } catch (Exception e) { - LOG.error("Unexpected error processing remediation.xml: {}", remediationPath, e); - throw new AviatorTechnicalException("Unexpected error processing remediations.xml.", e); + LOG.error( + "Unexpected error processing remediations.xml: {}", + remediationPath, + e); + + throw new AviatorTechnicalException( + "Unexpected error processing remediations.xml.", + e); } - int skippedRemediations = totalRemediations - appliedRemediations; - LOG.info("Auto-remediation summary: total={}, applied={}, skipped={}", totalRemediations, appliedRemediations, skippedRemediations); + + int skippedRemediations = + totalRemediations - appliedRemediations; + + LOG.info( + "Auto-remediation summary: total={}, applied={}, identical={}, skipped={}", + totalRemediations, + appliedRemediations, + identicalRemediations, + skippedRemediations); + if (!skippedByReason.isEmpty()) { - LOG.info("Skipped remediations by reason: {}", formatSkippedReasons(skippedByReason)); + LOG.info( + "Skipped remediations by reason: {}", + formatSkippedReasons(skippedByReason)); } - return new RemediationMetric(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, skippedByReason); + + return new RemediationMetric( + totalRemediations, + appliedRemediations, + skippedRemediations, + modifiedFiles, + skippedByReason); } + private boolean processRemediation(Element remediation, Path sourceBasePath, FVDLMetadata fvdlMetadata, Set modifiedFiles, Map skippedByReason) { String instanceId = remediation.getAttribute("instanceId"); @@ -580,4 +662,179 @@ private String formatSkippedReasons(Map skippedByReason) { skippedByReason.forEach((reason, count) -> parts.add(reason + "=" + count)); return String.join(", ", parts); } + + private RemediationKey createRemediationKey( + Element fileChanges, + Element change, + Path sourceBasePath, + String comparisonCode) { + + String fileName = getRequiredElementText(fileChanges, "Filename"); + Path filePath = sourceBasePath.resolve(fileName).normalize(); + + int lineFrom = parseRequiredInt(change, "LineFrom"); + int lineTo = parseRequiredInt(change, "LineTo"); + + return new RemediationKey( + fileName, + filePath, + lineFrom, + lineTo, + comparisonCode + ); + } + + + private String trimBlankLines(String content) { + String[] lines = content.split("\\R", -1); + + int start = 0; + int end = lines.length - 1; + + while (start <= end && lines[start].isBlank()) { + start++; + } + + while (end >= start && lines[end].isBlank()) { + end--; + } + + if (start > end) { + return ""; + } + + return String.join( + System.lineSeparator(), + Arrays.copyOfRange(lines, start, end + 1)); + } + + private String normalizeProposedCode(String content, String fileName) { + if (content == null) { + return null; + } + + String language = FileTypeLanguageMapperUtil.getProgrammingLanguage( + FileUtil.getFileExtension(fileName)); + + String commentSymbol = + LanguageCommentMapperUtil.getProgrammingLanguageComment(language); + + if ("Unknown".equals(commentSymbol)) { + return trimBlankLines(content); + } + + String closingToken = commentSymbol.equals("" + : commentSymbol.equals("<%--") ? "--%>" + : null; + + Pattern markerPattern = Pattern.compile( + "[ \\t]*" + Pattern.quote(commentSymbol) + " L\\d+" + + (closingToken != null + ? "[ \\t]*" + Pattern.quote(closingToken) + : "") + + "[ \\t]*$"); + + String[] lines = content.split("\\R", -1); + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < lines.length; i++) { + Matcher matcher = markerPattern.matcher(lines[i]); + + result.append( + matcher.find() + ? lines[i].substring(0, matcher.start()) + : lines[i]); + + if (i < lines.length - 1) { + result.append(System.lineSeparator()); + } + } + + return trimBlankLines(result.toString()); + } + + private String createComparisonCode(String normalizedCode, String fileName) { + if (normalizedCode == null) { + return null; + } + + String language = FileTypeLanguageMapperUtil.getProgrammingLanguage( + FileUtil.getFileExtension(fileName)); + + String commentSymbol = + LanguageCommentMapperUtil.getProgrammingLanguageComment(language); + + if ("Unknown".equals(commentSymbol)) { + return normalizedCode.replaceAll("\\s+", ""); + } + + String comparisonCode = normalizedCode; + + // Remove block comments + String closingToken = commentSymbol.equals("" + : commentSymbol.equals("<%--") ? "--%>" + : null; + + if (closingToken != null) { + comparisonCode = comparisonCode.replaceAll( + "(?s)" + Pattern.quote(commentSymbol) + + ".*?" + Pattern.quote(closingToken), + ""); + } else if ("//".equals(commentSymbol)) { + comparisonCode = comparisonCode.replaceAll( + "(?m)" + Pattern.quote(commentSymbol) + ".*$", + ""); + comparisonCode = comparisonCode.replaceAll( + "(?s)/\\*.*?\\*/", + ""); + } else if ("#".equals(commentSymbol)) { + comparisonCode = comparisonCode.replaceAll( + "(?m)" + Pattern.quote(commentSymbol) + ".*$", + ""); + } + + // Normalize whitespace + return comparisonCode.replaceAll("\\s+", ""); + } + + private List createRemediationKeys( + Element remediation, + Path sourceBasePath) { + + List keys = new ArrayList<>(); + + NodeList fileChangesNodes = + remediation.getElementsByTagNameNS(NAMESPACE_URI, "FileChanges"); + + for (int i = 0; i < fileChangesNodes.getLength(); i++) { + Element fileChanges = (Element) fileChangesNodes.item(i); + + NodeList changeNodes = + fileChanges.getElementsByTagNameNS(NAMESPACE_URI, "Change"); + + for (int j = 0; j < changeNodes.getLength(); j++) { + Element change = (Element) changeNodes.item(j); + + String fileName = + getRequiredElementText(fileChanges, "Filename"); + + String newCode = + getRequiredElementText(change, "NewCode"); + + String normalizedCode = + normalizeProposedCode(newCode, fileName); + + String comparisonCode = + createComparisonCode(normalizedCode, fileName); + + keys.add(createRemediationKey( + fileChanges, + change, + sourceBasePath, + comparisonCode)); + } + } + + return keys; + } } From ba8e635f7141a3e15acb3260e351f46597c6d07d Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Wed, 2 Sep 2026 11:55:46 +0530 Subject: [PATCH 4/9] added logic --- .../fpr/processor/RemediationProcessor.java | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index ae16ef2b1d..1f9c6fd6ef 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -189,14 +189,12 @@ public RemediationMetric processRemediationXML() { LOG.debug("Loaded {} remediation entries from {}", totalRemediations, remediationPath); appliedRemediations = 0; for (int i = 0; i < remediationNodes.getLength(); i++) { - Element remediation = - (Element) remediationNodes.item(i); + Element remediation = (Element) remediationNodes.item(i); String instanceId = remediation.getAttribute("instanceId"); - List remediationKeys = - createRemediationKeys( + List remediationKeys = createRemediationKeys( remediation, sourceBasePath); @@ -244,27 +242,14 @@ public RemediationMetric processRemediationXML() { } } catch (ParserConfigurationException | SAXException | IOException e) { - LOG.error( - "Error parsing remediations.xml file: {}", - remediationPath, - e); - - throw new AviatorTechnicalException( - "Error processing remediation.xml file.", - e); - + LOG.error("Error parsing remediations.xml file: {}", remediationPath, e); + throw new AviatorTechnicalException("Error processing remediation.xml file.", e); } catch (AviatorTechnicalException e) { throw e; } catch (Exception e) { - LOG.error( - "Unexpected error processing remediations.xml: {}", - remediationPath, - e); - - throw new AviatorTechnicalException( - "Unexpected error processing remediations.xml.", - e); + LOG.error("Unexpected error processing remediation.xml: {}", remediationPath, e); + throw new AviatorTechnicalException("Unexpected error processing remediations.xml.", e); } int skippedRemediations = @@ -279,16 +264,9 @@ public RemediationMetric processRemediationXML() { if (!skippedByReason.isEmpty()) { LOG.info( - "Skipped remediations by reason: {}", - formatSkippedReasons(skippedByReason)); + "Skipped remediations by reason: {}",formatSkippedReasons(skippedByReason)); } - - return new RemediationMetric( - totalRemediations, - appliedRemediations, - skippedRemediations, - modifiedFiles, - skippedByReason); + return new RemediationMetric(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, skippedByReason); } From b55490b8c0c35e7b51506aa869d9ef4d4e5e0239 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Wed, 2 Sep 2026 11:59:27 +0530 Subject: [PATCH 5/9] added logic --- .../aviator/fpr/processor/RemediationProcessor.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index 1f9c6fd6ef..64fa2cf8c5 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -252,15 +252,8 @@ public RemediationMetric processRemediationXML() { throw new AviatorTechnicalException("Unexpected error processing remediations.xml.", e); } - int skippedRemediations = - totalRemediations - appliedRemediations; - - LOG.info( - "Auto-remediation summary: total={}, applied={}, identical={}, skipped={}", - totalRemediations, - appliedRemediations, - identicalRemediations, - skippedRemediations); + int skippedRemediations = totalRemediations - appliedRemediations; + LOG.info("Auto-remediation summary: total={}, applied={},indentical={},skipped={}", totalRemediations, appliedRemediations, identicalRemediations,skippedRemediations); if (!skippedByReason.isEmpty()) { LOG.info( From 26e0e89512eb3371c9d8fdb5d0e252d0fe4434c4 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Thu, 3 Sep 2026 10:07:23 +0530 Subject: [PATCH 6/9] final logic for same fix --- .../fpr/processor/RemediationProcessor.java | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index 64fa2cf8c5..f645d1c2c2 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -39,7 +39,6 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import com.fortify.cli.aviator.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -56,6 +55,7 @@ import com.fortify.cli.aviator.fpr.utils.SourceDecoders; import com.fortify.cli.aviator.fpr.utils.SourceEncoder; import com.fortify.cli.aviator.fpr.utils.SourceEncoder.SourceEncodeException; +import com.fortify.cli.aviator.util.*; public class RemediationProcessor { private static final Logger LOG = LoggerFactory.getLogger(RemediationProcessor.class); @@ -65,10 +65,10 @@ public class RemediationProcessor { private final String sourceCodeDirectory; private final ISourceDecoder sourceDecoder; - public record RemediationMetric(int totalRemediations, int appliedRemediations, int skippedRemediations, Set modifiedFiles, + public record RemediationMetric(int totalRemediations, int appliedRemediations, int identicalRemediations,int skippedRemediations, Set modifiedFiles, Map skippedByReason) { - public RemediationMetric(int totalRemediations, int appliedRemediations, int skippedRemediations, Set modifiedFiles) { - this(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, Map.of()); + public RemediationMetric(int totalRemediations, int appliedRemediations,int identicalRemediations,int skippedRemediations, Set modifiedFiles) { + this(totalRemediations, appliedRemediations,identicalRemediations, skippedRemediations, modifiedFiles, Map.of()); } } @@ -161,7 +161,7 @@ public RemediationMetric processRemediationXML() { Set modifiedFiles = new LinkedHashSet<>(); Map skippedByReason = new LinkedHashMap<>(); Map remediationLookup = new LinkedHashMap<>(); - + LOG.debug("in the processRemediationXML method"); // Sanitize and normalize the base source directory path once. String trimmedSourceDir = sourceCodeDirectory.trim(); if (trimmedSourceDir.length() > 1 && @@ -188,25 +188,49 @@ public RemediationMetric processRemediationXML() { totalRemediations = remediationNodes.getLength(); LOG.debug("Loaded {} remediation entries from {}", totalRemediations, remediationPath); appliedRemediations = 0; + for (int i = 0; i < remediationNodes.getLength(); i++) { - Element remediation = (Element) remediationNodes.item(i); + LOG.debug("........................"); + Element remediation = + (Element) remediationNodes.item(i); String instanceId = remediation.getAttribute("instanceId"); + LOG.debug("remediation{}",instanceId); - List remediationKeys = createRemediationKeys( + List remediationKeys = + createRemediationKeys( remediation, sourceBasePath); - String identicalInstanceId = null; + LOG.debug( + "Remediation {} generated {} lookup key(s): {}", + instanceId, + remediationKeys.size(), + remediationKeys); - for (RemediationKey key : remediationKeys) { - String existingInstanceId = - remediationLookup.get(key); + String identicalInstanceId = null; - if (existingInstanceId != null) { - identicalInstanceId = existingInstanceId; - break; + /* + * A remediation is identical only when all of its changes + * match an existing remediation. + */ + if (!remediationKeys.isEmpty()) { + for (String existingInstanceId : + new LinkedHashSet<>(remediationLookup.values())) { + + List existingKeys = + remediationLookup.entrySet().stream() + .filter(entry -> + existingInstanceId.equals(entry.getValue())) + .map(Map.Entry::getKey) + .toList(); + + if (existingKeys.size() == remediationKeys.size() + && existingKeys.containsAll(remediationKeys)) { + identicalInstanceId = existingInstanceId; + break; + } } } @@ -236,6 +260,7 @@ public RemediationMetric processRemediationXML() { appliedRemediations++; for (RemediationKey key : remediationKeys) { + LOG.debug("putting {}",instanceId); remediationLookup.put(key, instanceId); } } @@ -256,10 +281,9 @@ public RemediationMetric processRemediationXML() { LOG.info("Auto-remediation summary: total={}, applied={},indentical={},skipped={}", totalRemediations, appliedRemediations, identicalRemediations,skippedRemediations); if (!skippedByReason.isEmpty()) { - LOG.info( - "Skipped remediations by reason: {}",formatSkippedReasons(skippedByReason)); + LOG.info("Skipped remediations by reason: {}",formatSkippedReasons(skippedByReason)); } - return new RemediationMetric(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, skippedByReason); + return new RemediationMetric(totalRemediations, appliedRemediations, identicalRemediations, skippedRemediations, modifiedFiles, skippedByReason); } From 92422671d21a4532dc08cfbdee34876abebb51b4 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Thu, 3 Sep 2026 10:33:26 +0530 Subject: [PATCH 7/9] fixing for source line moving on earlier remediation --- .../fpr/processor/RemediationProcessor.java | 123 ++++++++++-------- 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index f645d1c2c2..8d5babcd8c 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -76,6 +76,10 @@ private record SourceFileContent(String content, Charset charset, String encodin private record PendingFileWrite(String filename, Path filePath, String content, Charset charset, String encodingSource, byte[] updatedBytes) {} + private record AppliedChange(Path filePath, int originalStart, int originalEnd, int resultingStart, int resultingEnd, String remediationId) { + private int lineDelta() { return (resultingEnd - resultingStart + 1) - (originalEnd - originalStart + 1); } + } + private record ChangeApplication(String content, AppliedChange appliedChange) {} private record RollbackFileWrite(String filename, Path filePath, byte[] originalBytes) {} @@ -91,11 +95,14 @@ private enum SkipReason { SOURCE_CONTEXT_NOT_FOUND("Source context not found"), SOURCE_CONTEXT_AMBIGUOUS("Source context matched multiple locations"), ORIGINAL_CODE_NOT_FOUND("Original code not found"), + ANCHOR_MISMATCH("Anchor does not match"), + CONFLICT("Conflicts with another fix"), REMEDIATION_ENCODE_FAILED("Remediation encode failed"), SOURCE_WRITE_FAILED("Source file write failed"), NO_CHANGES("No file changes found"), UNEXPECTED_ERROR("Unexpected remediation processing error"); + private final String displayName; SkipReason(String displayName) { @@ -161,6 +168,7 @@ public RemediationMetric processRemediationXML() { Set modifiedFiles = new LinkedHashSet<>(); Map skippedByReason = new LinkedHashMap<>(); Map remediationLookup = new LinkedHashMap<>(); + Map> appliedChangesByFile = new LinkedHashMap<>(); LOG.debug("in the processRemediationXML method"); // Sanitize and normalize the base source directory path once. String trimmedSourceDir = sourceCodeDirectory.trim(); @@ -250,12 +258,7 @@ public RemediationMetric processRemediationXML() { continue; } - if (processRemediation( - remediation, - sourceBasePath, - fvdlMetadata, - modifiedFiles, - skippedByReason)) { + if (processRemediation(remediation, sourceBasePath, fvdlMetadata, modifiedFiles, skippedByReason, appliedChangesByFile)) { appliedRemediations++; @@ -287,17 +290,21 @@ public RemediationMetric processRemediationXML() { } - private boolean processRemediation(Element remediation, Path sourceBasePath, FVDLMetadata fvdlMetadata, - Set modifiedFiles, Map skippedByReason) { + private boolean processRemediation(Element remediation, Path sourceBasePath, FVDLMetadata fvdlMetadata, Set modifiedFiles, + Map skippedByReason, Map> appliedChangesByFile) { String instanceId = remediation.getAttribute("instanceId"); try { - Map pendingWrites = prepareFileChanges(remediation, sourceBasePath, fvdlMetadata); + List stagedChanges = new ArrayList<>(); + Map pendingWrites = prepareFileChanges(remediation, sourceBasePath, fvdlMetadata, appliedChangesByFile, stagedChanges); if (pendingWrites.isEmpty()) { recordSkipped(skippedByReason, SkipReason.NO_CHANGES.displayName); return false; } try { commitRemediationWrites(instanceId, pendingWrites, modifiedFiles); + for (AppliedChange change : stagedChanges) { + appliedChangesByFile.computeIfAbsent(change.filePath(), key -> new ArrayList<>()).add(change); + } return true; } catch (RemediationCommitException e) { rollbackRemediationWrites(instanceId, e.getRollbacks()); @@ -318,8 +325,7 @@ private boolean processRemediation(Element remediation, Path sourceBasePath, FVD } } - private Map prepareFileChanges(Element remediation, Path sourceBasePath, - FVDLMetadata fvdlMetadata) { + private Map prepareFileChanges(Element remediation, Path sourceBasePath, FVDLMetadata fvdlMetadata, Map> appliedChangesByFile, List stagedChanges) { NodeList fileChangesNodes = remediation.getElementsByTagNameNS(NAMESPACE_URI, "FileChanges"); if (fileChangesNodes.getLength() == 0) { throw new SkipRemediationException(SkipReason.NO_CHANGES, "No file changes found"); @@ -327,14 +333,13 @@ private Map prepareFileChanges(Element remediation, Path Map pendingWrites = new LinkedHashMap<>(); for (int j = 0; j < fileChangesNodes.getLength(); j++) { - processFileChanges(remediation, (Element) fileChangesNodes.item(j), sourceBasePath, fvdlMetadata, pendingWrites); + processFileChanges(remediation, (Element) fileChangesNodes.item(j), sourceBasePath, fvdlMetadata, pendingWrites, appliedChangesByFile, stagedChanges); } return pendingWrites; } - private boolean processFileChanges(Element remediation, Element fileChanges, Path sourceBasePath, FVDLMetadata fvdlMetadata, - Map pendingWrites) { - String instanceId = remediation.getAttribute("instanceId"); + private boolean processFileChanges(Element remediation, Element fileChanges, Path sourceBasePath, FVDLMetadata fvdlMetadata, Map pendingWrites, Map> appliedChangesByFile, List stagedChanges) {String instanceId = remediation.getAttribute("instanceId"); String filename = getRequiredElementText(fileChanges, "Filename"); Path filePath = sourceBasePath.resolve(filename).normalize(); LOG.debug("Processing remediation {} file change for '{}' resolved to '{}'", instanceId, filename, filePath); @@ -360,8 +365,9 @@ private boolean processFileChanges(Element remediation, Element fileChanges, Pat String updatedContent = sourceFileContent.content(); for (int k = 0; k < changesNodes.getLength(); k++) { - updatedContent = applyChange(instanceId, filename, fileHash, sourceEncoding, updatedContent, - (Element) changesNodes.item(k), k + 1); + ChangeApplication application = applyChange(instanceId, filename, filePath, fileHash, sourceEncoding, updatedContent, (Element) changesNodes.item(k), k + 1, appliedChangesByFile, stagedChanges); + updatedContent = application.content(); + stagedChanges.add(application.appliedChange()); } byte[] updatedBytes = encodeSourceFile(updatedContent, sourceEncoding, filename); pendingWrites.put(filePath, new PendingFileWrite(filename, filePath, updatedContent, sourceEncoding, @@ -371,62 +377,73 @@ private boolean processFileChanges(Element remediation, Element fileChanges, Pat return true; } - private String applyChange(String instanceId, String filename, String fileHash, Charset sourceEncoding, String originalContent, - Element change, int changeIndex) { + private ChangeApplication applyChange(String instanceId, String filename, Path filePath, String fileHash, Charset sourceEncoding, String originalContent, Element change, int changeIndex, Map> appliedChangesByFile, List stagedChanges) { String lineSeparator = detectLineSeparator(originalContent); String content = normalizeLineEndings(originalContent); - List originalLines = Arrays.asList(content.split("\n", -1)); - LOG.debug("Decoded '{}' using {}; lineSeparator={}, normalizedLines={}", filename, sourceEncoding.name(), - describeLineSeparator(lineSeparator), originalLines.size()); - - int lineFrom = parseRequiredInt(change, "LineFrom"); - int lineTo = parseRequiredInt(change, "LineTo"); - LOG.debug("Remediation {} change {} for '{}' targets lines {}-{}", instanceId, changeIndex, filename, lineFrom, lineTo); - + int declaredLineFrom = parseRequiredInt(change, "LineFrom"); + int declaredLineTo = parseRequiredInt(change, "LineTo"); String calculatedHash = calculateHashBase64(content, "SHA-256"); boolean fileHashMatches = calculatedHash.equals(fileHash); - LOG.debug("Remediation {} hash check for '{}': {}", instanceId, filename, fileHashMatches ? "matched" : "mismatched"); - if (!fileHashMatches) { - LOG.debug("File hash mismatch for remediation {} in {}; searching changed source content", instanceId, filename); + List previousChanges = new ArrayList<>(); + List committedChanges = appliedChangesByFile.get(filePath); + if (committedChanges != null) { previousChanges.addAll(committedChanges); } + for (AppliedChange stagedChange : stagedChanges) { if (filePath.equals(stagedChange.filePath())) { previousChanges.add(stagedChange); } } + int lineFrom = declaredLineFrom; + int lineTo = declaredLineTo; + if (!previousChanges.isEmpty()) { + int[] projectedRange = projectLineRange(declaredLineFrom, declaredLineTo, previousChanges, instanceId, filename); + lineFrom = projectedRange[0]; + lineTo = projectedRange[1]; + validateLineRange(lineFrom, lineTo, originalLines.size(), filename); + verifyOriginalCodeAtRange(instanceId, filename, originalLines, lineFrom, lineTo, change); + } else if (!fileHashMatches) { Element contextElement = getRequiredElement(change, "Context"); - String contextText = contextElement.getTextContent(); - List contextLine = Arrays.asList(contextText.split("\\r?\\n")); + List contextLine = Arrays.asList(contextElement.getTextContent().split("\\r?\\n")); int contextLineFrom = fuzzySearchContext(instanceId, filename, originalLines, contextLine); - if (contextLineFrom == -1) { - LOG.debug("Context search failed for remediation {} in {}; context lines={}, source lines={}", instanceId, filename, - contextLine.size(), originalLines.size()); - throw new SkipRemediationException(SkipReason.SOURCE_CONTEXT_NOT_FOUND, "Source context not found for file '" + filename + - "'; file may have changed or remediation may overlap a previous change"); - } - LOG.debug("Context for remediation {} in {} matched at line {}", instanceId, filename, contextLineFrom + 1); - + if (contextLineFrom == -1) { throw new SkipRemediationException(SkipReason.SOURCE_CONTEXT_NOT_FOUND, "Source context not found for file '" + filename + "'"); } String originalCodeText = getRequiredElementText(change, "OriginalCode"); List originalCodeLine = Arrays.asList(originalCodeText.split("\\r?\\n")); int contextBefore = parseRequiredContextAttribute(contextElement, "before"); int contextAfter = parseRequiredContextAttribute(contextElement, "after"); - int[] lineFromTo = fuzzySearchOriginalCode(instanceId, filename, originalLines, originalCodeLine, - contextLineFrom, contextLine.size(), contextBefore, contextAfter); - if (lineFromTo[0] == -1 || lineFromTo[1] == -1) { - LOG.debug("Original code search failed for remediation {} in {}; context line={}, original code lines={}, source lines={}", - instanceId, filename, contextLineFrom + 1, originalCodeLine.size(), originalLines.size()); - throw new SkipRemediationException(SkipReason.ORIGINAL_CODE_NOT_FOUND, "Original code not found for file '" + filename + - "'; file may have changed or remediation may overlap a previous change"); - } + int[] lineFromTo = fuzzySearchOriginalCode(instanceId, filename, originalLines, originalCodeLine, contextLineFrom, contextLine.size(), contextBefore, contextAfter); + if (lineFromTo[0] == -1 || lineFromTo[1] == -1) { throw new SkipRemediationException(SkipReason.ORIGINAL_CODE_NOT_FOUND, "Original code not found for file '" + filename + "'"); } lineFrom = lineFromTo[0] + 1; lineTo = lineFromTo[1] + 1; - LOG.debug("Original code for remediation {} in {} matched at lines {}-{}", instanceId, filename, lineFrom, lineTo); } - validateLineRange(lineFrom, lineTo, originalLines.size(), filename); List newCodeLines = Arrays.asList(getRequiredElementText(change, "NewCode").split("\n")); List updatedLines = new ArrayList<>(); updatedLines.addAll(originalLines.subList(0, lineFrom - 1)); updatedLines.addAll(newCodeLines); updatedLines.addAll(originalLines.subList(lineTo, originalLines.size())); - LOG.debug("Staged remediation {} change {} for '{}' using FVDL encoding {}; updatedLines={}", instanceId, changeIndex, - filename, sourceEncoding.name(), updatedLines.size()); - return String.join(lineSeparator, updatedLines); + String updatedContent = String.join(lineSeparator, updatedLines); + AppliedChange appliedChange = new AppliedChange(filePath, declaredLineFrom, declaredLineTo, lineFrom, lineFrom + newCodeLines.size() - 1, instanceId); + return new ChangeApplication(updatedContent, appliedChange); + } + private int[] projectLineRange(int originalStart, int originalEnd, List appliedChanges, String instanceId, String filename) { + int projectedStart = originalStart; + int projectedEnd = originalEnd; + for (AppliedChange applied : appliedChanges) { + boolean overlaps = originalStart <= applied.originalEnd() && originalEnd >= applied.originalStart(); + if (overlaps) { + int overlapStart = Math.max(originalStart, applied.originalStart()); + int overlapEnd = Math.min(originalEnd, applied.originalEnd()); + throw new SkipRemediationException(SkipReason.CONFLICT, "Remediation '" + instanceId + "' conflicts with remediation '" + applied.remediationId() + "' in file '" + filename + "'; overlapping original lines " + overlapStart + "-" + overlapEnd); + } + if (originalStart > applied.originalEnd()) { + projectedStart += applied.lineDelta(); + projectedEnd += applied.lineDelta(); + } + } + return new int[] {projectedStart, projectedEnd}; + } + private void verifyOriginalCodeAtRange(String instanceId, String filename, List sourceLines, int lineFrom, int lineTo, Element change) { + List expectedLines = Arrays.asList(normalizeLineEndings(getRequiredElementText(change, "OriginalCode")).split("\n", -1)); + List actualLines = sourceLines.subList(lineFrom - 1, lineTo); + if (!expectedLines.equals(actualLines)) { + throw new SkipRemediationException(SkipReason.ANCHOR_MISMATCH, "Anchor does not match for remediation '" + instanceId + "' in file '" + filename + "' at lines " + lineFrom + "-" + lineTo); + } } private SourceFileContent getPendingOrSourceContent(Path filePath, String filename, FVDLMetadata fvdlMetadata, From 6abfdbcf586901745a28277852e12240b73981ca Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Thu, 3 Sep 2026 14:37:25 +0530 Subject: [PATCH 8/9] fixing for source line moving on earlier remediation --- .../java/com/fortify/cli/app/FortifyCLI.java | 1 + .../fpr/processor/RemediationProcessor.java | 30 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java index 34eaf65c90..fd12dca63c 100644 --- a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java +++ b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java @@ -33,6 +33,7 @@ public static final void main(String[] args) { private static final int execute(String[] args) { try { + System.out.println("FCLI1 started"); ConsoleHelper.installJAnsiConsole(); return DefaultFortifyCLIRunner.run(args); } finally { diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index 8d5babcd8c..b2df52c3b6 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -406,8 +406,28 @@ private ChangeApplication applyChange(String instanceId, String filename, Path f List originalCodeLine = Arrays.asList(originalCodeText.split("\\r?\\n")); int contextBefore = parseRequiredContextAttribute(contextElement, "before"); int contextAfter = parseRequiredContextAttribute(contextElement, "after"); + + LOG.debug("=== FUZZY ORIGINAL SEARCH ==="); + LOG.debug("InstanceId:{}",instanceId); + LOG.debug("Declared range: {} {}",declaredLineFrom, declaredLineTo); + LOG.debug("File hash: {}", fileHash); + LOG.debug("Calculated hash: {}", calculatedHash); + LOG.debug("OriginalCode:\n{}", originalCodeText); + LOG.debug("Context:\n{}", contextElement.getTextContent()); + LOG.debug("Context matches from: {}", contextLineFrom); + LOG.debug("=============================="); int[] lineFromTo = fuzzySearchOriginalCode(instanceId, filename, originalLines, originalCodeLine, contextLineFrom, contextLine.size(), contextBefore, contextAfter); - if (lineFromTo[0] == -1 || lineFromTo[1] == -1) { throw new SkipRemediationException(SkipReason.ORIGINAL_CODE_NOT_FOUND, "Original code not found for file '" + filename + "'"); } + if (lineFromTo[0] == -1 || lineFromTo[1] == -1) { + LOG.debug("=== ORIGINAL CODE NOT FOUND ==="); + LOG.debug("InstanceId: {}", instanceId); + LOG.debug("Filename: {}", filename); + LOG.debug("FilePath: {}", filePath); + LOG.debug("OriginalCode:\n{}", originalCodeText); + LOG.debug("Declared lines: {}-{}", declaredLineFrom, declaredLineTo); + LOG.debug("Context line: {}", contextLineFrom); + LOG.debug("==============================="); + throw new SkipRemediationException(SkipReason.ORIGINAL_CODE_NOT_FOUND, "Original code not found for file '" + filename + "'"); + } lineFrom = lineFromTo[0] + 1; lineTo = lineFromTo[1] + 1; } @@ -495,6 +515,13 @@ private int fuzzySearchContext(String instanceId, String filename, List String candidateLines = matches.stream() .map(line -> String.valueOf(line + 1)) .collect(Collectors.joining(", ")); + + LOG.debug("=== SOURCE CONTEXT AMBIGUOUS ==="); + LOG.debug("InstanceId: {}", instanceId); + LOG.debug("Filename: {}", filename); + LOG.debug("SourceContext:\n{}", String.join("\n", contextLine)); + LOG.debug("Matching locations: {}", matches); + LOG.debug("==============================="); throw new SkipRemediationException(SkipReason.SOURCE_CONTEXT_AMBIGUOUS, "Source context matched multiple locations in file '" + filename + "'; candidate lines: " + candidateLines); } @@ -509,6 +536,7 @@ private int[] fuzzySearchOriginalCode(String instanceId, String filename, List= contextEnd || contextEnd > originalLines.size()) { return new int[] {-1, -1}; } From a68d6691f3abc90893afec95169c92972673d46d Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Thu, 3 Sep 2026 14:56:13 +0530 Subject: [PATCH 9/9] fixing for source line moving on earlier remediation --- .../java/com/fortify/cli/app/FortifyCLI.java | 2 +- .../fpr/processor/RemediationProcessor.java | 133 +++++++++++++++--- .../aviator/util/FuzzyContextSearcher.java | 19 +-- 3 files changed, 120 insertions(+), 34 deletions(-) diff --git a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java index fd12dca63c..770ae0b3b9 100644 --- a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java +++ b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java @@ -33,7 +33,7 @@ public static final void main(String[] args) { private static final int execute(String[] args) { try { - System.out.println("FCLI1 started"); + System.out.println("FCLI3 started"); ConsoleHelper.installJAnsiConsole(); return DefaultFortifyCLIRunner.run(args); } finally { diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index b2df52c3b6..b35f8d6d53 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -392,11 +392,30 @@ private ChangeApplication applyChange(String instanceId, String filename, Path f int lineFrom = declaredLineFrom; int lineTo = declaredLineTo; if (!previousChanges.isEmpty()) { - int[] projectedRange = projectLineRange(declaredLineFrom, declaredLineTo, previousChanges, instanceId, filename); + int[] projectedRange = projectLineRange( + declaredLineFrom, + declaredLineTo, + previousChanges, + instanceId, + filename); + lineFrom = projectedRange[0]; lineTo = projectedRange[1]; - validateLineRange(lineFrom, lineTo, originalLines.size(), filename); - verifyOriginalCodeAtRange(instanceId, filename, originalLines, lineFrom, lineTo, change); + + validateLineRange( + lineFrom, + lineTo, + originalLines.size(), + filename); + + verifyOriginalCodeAtRange( + instanceId, + filename, + originalLines, + lineFrom, + lineTo, + change); + } else if (!fileHashMatches) { Element contextElement = getRequiredElement(change, "Context"); List contextLine = Arrays.asList(contextElement.getTextContent().split("\\r?\\n")); @@ -441,22 +460,58 @@ private ChangeApplication applyChange(String instanceId, String filename, Path f AppliedChange appliedChange = new AppliedChange(filePath, declaredLineFrom, declaredLineTo, lineFrom, lineFrom + newCodeLines.size() - 1, instanceId); return new ChangeApplication(updatedContent, appliedChange); } - private int[] projectLineRange(int originalStart, int originalEnd, List appliedChanges, String instanceId, String filename) { + private int[] projectLineRange( + int originalStart, + int originalEnd, + List appliedChanges, + String instanceId, + String filename) { + int projectedStart = originalStart; int projectedEnd = originalEnd; + for (AppliedChange applied : appliedChanges) { - boolean overlaps = originalStart <= applied.originalEnd() && originalEnd >= applied.originalStart(); + + /* + * Conflict detection is always performed against the + * original remediation line ranges. + */ + boolean overlaps = + originalStart <= applied.originalEnd() + && originalEnd >= applied.originalStart(); + if (overlaps) { - int overlapStart = Math.max(originalStart, applied.originalStart()); - int overlapEnd = Math.min(originalEnd, applied.originalEnd()); - throw new SkipRemediationException(SkipReason.CONFLICT, "Remediation '" + instanceId + "' conflicts with remediation '" + applied.remediationId() + "' in file '" + filename + "'; overlapping original lines " + overlapStart + "-" + overlapEnd); + int overlapStart = + Math.max(originalStart, applied.originalStart()); + int overlapEnd = + Math.min(originalEnd, applied.originalEnd()); + + throw new SkipRemediationException( + SkipReason.CONFLICT, + "Remediation '" + instanceId + + "' conflicts with remediation '" + + applied.remediationId() + + "' in file '" + filename + + "'; overlapping original lines " + + overlapStart + "-" + overlapEnd); } + + /* + * Only changes that occur before this remediation need + * to shift its physical location. + */ if (originalStart > applied.originalEnd()) { - projectedStart += applied.lineDelta(); - projectedEnd += applied.lineDelta(); + int delta = applied.lineDelta(); + + projectedStart += delta; + projectedEnd += delta; } } - return new int[] {projectedStart, projectedEnd}; + + return new int[] { + projectedStart, + projectedEnd + }; } private void verifyOriginalCodeAtRange(String instanceId, String filename, List sourceLines, int lineFrom, int lineTo, Element change) { List expectedLines = Arrays.asList(normalizeLineEndings(getRequiredElementText(change, "OriginalCode")).split("\n", -1)); @@ -532,21 +587,57 @@ private int fuzzySearchContext(String instanceId, String filename, List } } - private int[] fuzzySearchOriginalCode(String instanceId, String filename, List originalLines, List originalCodeLine, - int contextLineFrom, int contextLineCount, int contextBefore, int contextAfter) { + private int[] fuzzySearchOriginalCode( + String instanceId, + String filename, + List originalLines, + List originalCodeLine, + int contextLineFrom, + int contextLineCount, + int contextBefore, + int contextAfter) { + + /* + * First try the location implied by the context. + * This keeps the context as the primary anchor. + */ int contextStart = contextLineFrom + contextBefore; int contextEnd = contextLineFrom + contextLineCount - contextAfter; - if (contextStart < 0 || contextStart >= contextEnd || contextEnd > originalLines.size()) { - return new int[] {-1, -1}; + if (contextStart >= 0 + && contextStart < contextEnd + && contextEnd <= originalLines.size()) { + + int[] match = FuzzyContextSearcher.fuzzySearchOriginalCode( + originalLines.subList(contextStart, contextEnd), + originalCodeLine, + 0, + 0); + + if (match[0] != -1 && match[1] != -1) { + return new int[] { + match[0] + contextStart, + match[1] + contextStart + }; + } } - int[] lineFromTo = FuzzyContextSearcher.fuzzySearchOriginalCode( - originalLines.subList(contextStart, contextEnd), originalCodeLine, 0, 0); - if (lineFromTo[0] == -1 || lineFromTo[1] == -1) { - return lineFromTo; - } - return new int[] {lineFromTo[0] + contextStart, lineFromTo[1] + contextStart}; + /* + * Context boundaries can become unreliable when blank lines, + * formatting changes, or inserted lines are involved. + * + * Fall back to searching the complete source file. + */ + LOG.debug( + "Original code not found inside context window; searching entire source for remediation {} in '{}'", + instanceId, + filename); + + return FuzzyContextSearcher.fuzzySearchOriginalCode( + originalLines, + originalCodeLine, + 0, + 0); } private boolean isFilePresent(Path path) { diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/util/FuzzyContextSearcher.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/util/FuzzyContextSearcher.java index 4a4b8e6dd5..9faab27030 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/util/FuzzyContextSearcher.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/util/FuzzyContextSearcher.java @@ -110,20 +110,16 @@ public static int[] fuzzySearchOriginalCode(List sourceLines, List normalizedSource, List normalizedOriginalCode, int maxMismatches, - int sourceIndex) { + private static int findOriginalCodeEnd( + List normalizedSource, + List normalizedOriginalCode, + int maxMismatches, + int sourceIndex) { + int mismatches = 0; int lineTo = -1; - boolean matchedAnyLine = false; for (String originalCodeLine : normalizedOriginalCode) { - if (originalCodeLine.isEmpty()) { - continue; - } - - if (matchedAnyLine) { - sourceIndex = skipEmptySourceLines(normalizedSource, sourceIndex); - } if (sourceIndex >= normalizedSource.size()) { return -1; } @@ -136,10 +132,9 @@ private static int findOriginalCodeEnd(List normalizedSource, List normalizedSource, int sourceIndex) {