This is an automated email from the ASF dual-hosted git repository.

luigidemasi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kit.git

commit e22d95201b0f35a5e4b4b4ad61b05fe711d05c2d
Author: Jinal Sarvaiya <[email protected]>
AuthorDate: Thu Nov 5 14:55:02 2026 +0000

    fix #100: eliminate quadratic scan paths and same-line bare correction 
regression
---
 .../camel/kit/ship/controller/ShipStageResult.java | 25 +++++++++++++---
 .../kit/ship/controller/ShipStageResultTest.java   | 35 ++++++++++++++++++----
 2 files changed, 50 insertions(+), 10 deletions(-)

diff --git 
a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java
 
b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java
index 664125c7..23907b16 100644
--- 
a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java
+++ 
b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java
@@ -47,7 +47,6 @@ record ShipStageResult(
     private static final int MAX_ASSISTANT_BYTES = 1024 * 1024;
     private static final int MAX_REPORT_BYTES = MAX_ASSISTANT_BYTES;
     private static final Pattern ROUTE_ID = 
Pattern.compile("[A-Za-z0-9][A-Za-z0-9_.-]{0,127}");
-    private static final Pattern BARE_JSON_START = 
Pattern.compile("(?:^|\\n)\\s*\\{");
     private static final Comparator<RouteContract> ROUTE_ORDER = Comparator
             .comparing(RouteContract::routeId)
             .thenComparing(RouteContract::routePath)
@@ -350,8 +349,7 @@ record ShipStageResult(
                 close = afterClose;
             }
             if (!found) {
-                pos = contentStart;
-                continue;
+                break;
             }
 
             String content = text.substring(contentStart, close).strip();
@@ -383,7 +381,26 @@ record ShipStageResult(
     }
 
     private static boolean hasBareJsonStart(String fragment) {
-        return BARE_JSON_START.matcher(fragment).find();
+        int i = 0;
+        int len = fragment.length();
+        while (i < len) {
+            char c = fragment.charAt(i);
+            if (c == '`') {
+                int j = i + 1;
+                while (j < len && fragment.charAt(j) != '`' && 
fragment.charAt(j) != '\n') {
+                    j++;
+                }
+                if (j < len && fragment.charAt(j) == '`') {
+                    i = j + 1;
+                    continue;
+                }
+            }
+            if (c == '{') {
+                return true;
+            }
+            i++;
+        }
+        return false;
     }
 
     static byte[] utf8(String value, int maximum, String label) throws 
IOException {
diff --git 
a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java
 
b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java
index a756134b..bb0ec226 100644
--- 
a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java
+++ 
b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java
@@ -439,14 +439,24 @@ class ShipStageResultTest {
     @Test
     void malformedFenceOpenersDoNotCauseQuadraticScan() throws Exception {
         StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < 4000; i++) {
-            sb.append("```json\n");
-            sb.append("Not valid JSON, just prose line 
").append(i).append('\n');
+        for (int i = 0; i < 90_000; i++) {
+            sb.append("```json\n{\n");
         }
-        ObjectNode valid = result("149-local-controller", null);
-        
sb.append("```json\n").append(JSON.writeValueAsString(valid)).append("\n```\n");
         String input = sb.toString();
-        assertTrue(input.length() > 150_000, "input must exceed 150 KB");
+        assertTrue(input.length() > 800_000, "input must exceed 800 KB");
+
+        long start = System.nanoTime();
+        assertRejected(Stage.DISCOVERY, input, "90k unfinished openers");
+        long elapsedMs = (System.nanoTime() - start) / 1_000_000;
+        assertTrue(elapsedMs < 2000,
+                "parse took " + elapsedMs + " ms — expected under 2000 ms");
+    }
+
+    @Test
+    void blankLinePrefixDoesNotCauseQuadraticScan() throws Exception {
+        ObjectNode valid = result("149-local-controller", null);
+        String prefix = "\n".repeat(100_000);
+        String input = prefix + "```json\n" + JSON.writeValueAsString(valid) + 
"\n```\n";
 
         long start = System.nanoTime();
         ShipStageResult parsed = ShipStageResult.parse(Stage.DISCOVERY, input);
@@ -484,6 +494,19 @@ class ShipStageResultTest {
         assertRejected(Stage.DESIGN, bareThenFenced, "bare then fenced 
envelope");
     }
 
+    @Test
+    void rejectsSameLineBareCorrection() throws Exception {
+        ObjectNode fenced = result(null, null);
+        ObjectNode bare = result(null, null);
+        bare.put("materialAmbiguity", true);
+        bare.withArray("unansweredQuestions").addObject()
+                .put("question", "Which retry 
limit?").putNull("defaultApplied");
+        String fencedThenSameLineBare = "```json\n" + 
JSON.writeValueAsString(fenced)
+                                        + "\n```\nCorrection: " + 
JSON.writeValueAsString(bare);
+        assertRejected(Stage.DESIGN, fencedThenSameLineBare,
+                "fenced then same-line bare correction");
+    }
+
     @Test
     void rejectsArrayWrappedEnvelope() throws Exception {
         ObjectNode valid = result("149-local-controller", null);

Reply via email to