diff --git a/src/main/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetector.java index 1c69f4f1..7b83e036 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetector.java @@ -142,12 +142,20 @@ public class SqlMigrationDetector extends AbstractRegexDetector { + "[^>]*?\\breferencedTableName\\s*+=\\s*+\"(\\w++)\""); // -- Liquibase YAML patterns (regex-based; avoids pulling in SnakeYAML here) -- + // Intermediate lines use a negative-lookahead + possessive `*+` instead of reluctant `*?`: + // no backtracking (prevents SonarCloud S5998 stack-overflow / S5852 ReDoS), same + // semantics — lines that *would* match the target key are excluded from the skip group, + // so the outer match terminates exactly where the reluctant version would have. private static final Pattern LQ_CREATE_TABLE_YAML = Pattern.compile( - "createTable\\s*+:[^\\n]*+\\n(?:\\s++[^\\n]*+\\n)*?\\s++tableName\\s*+:\\s*+([\\w\"']++)"); + "createTable\\s*+:[^\\n]*+\\n" + + "(?:(?!\\s++tableName\\s*+:)\\s++[^\\n]*+\\n)*+" + + "\\s++tableName\\s*+:\\s*+([\\w\"']++)"); private static final Pattern LQ_ADD_FK_YAML = Pattern.compile( "addForeignKeyConstraint\\s*+:[^\\n]*+\\n" - + "(?:\\s++[^\\n]*+\\n)*?\\s++baseTableName\\s*+:\\s*+([\\w\"']++)[^\\n]*+\\n" - + "(?:\\s++[^\\n]*+\\n)*?\\s++referencedTableName\\s*+:\\s*+([\\w\"']++)"); + + "(?:(?!\\s++baseTableName\\s*+:)\\s++[^\\n]*+\\n)*+" + + "\\s++baseTableName\\s*+:\\s*+([\\w\"']++)[^\\n]*+\\n" + + "(?:(?!\\s++referencedTableName\\s*+:)\\s++[^\\n]*+\\n)*+" + + "\\s++referencedTableName\\s*+:\\s*+([\\w\"']++)"); // -- Flyway version parsing -- private static final Pattern FLYWAY_VERSION = Pattern.compile( diff --git a/src/test/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetectorTest.java b/src/test/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetectorTest.java index 7fdb0ad1..5a3cde0b 100644 --- a/src/test/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetectorTest.java +++ b/src/test/java/io/github/randomcodespace/iq/detector/sql/SqlMigrationDetectorTest.java @@ -209,6 +209,42 @@ void liquibaseYamlChangeSetDetected() { && e.getTarget().getId().endsWith(":invoices"))); } + @Test + void liquibaseYamlChangeSet_largeIntermediateContent_completesQuickly() { + // Regression guard for SonarCloud S5998 / S5852 on LQ_*_YAML: the reluctant-outer + // `(?:\s++[^\n]*+\n)*?` patterns were rewritten with a negative-lookahead + + // possessive outer so stack/runtime cannot blow up on many indented lines. + // Pathological input: a createTable with 500 indented column lines preceding + // tableName — the pre-fix reluctant walk would scale quadratically. + StringBuilder columns = new StringBuilder(); + for (int i = 0; i < 500; i++) { + columns.append(" - column_").append(i).append(": stub_value\n"); + } + String yaml = """ + databaseChangeLog: + - changeSet: + id: 1 + author: bob + changes: + - createTable: + """ + + columns + + " tableName: wide_table\n"; + DetectorContext ctx = new DetectorContext( + "src/main/resources/db/db.changelog-wide.yml", "yaml", yaml); + + long start = System.nanoTime(); + DetectorResult result = detector.detect(ctx); + long elapsedMs = (System.nanoTime() - start) / 1_000_000; + + assertTrue(hasEntity(sqlEntitiesOf(result), "wide_table", "table"), + "wide_table must still be detected after the anti-backtracking rewrite"); + // 2s is 100x the observed fast-path time; we only care that it doesn't blow up + // exponentially — the exact threshold isn't load-bearing. + assertTrue(elapsedMs < 2000, + "detection of wide_table should complete in under 2s, was " + elapsedMs + "ms"); + } + // -- Positive: Rails -- @Test