Skip to content

Commit 6581cc9

Browse files
authored
Merge branch 'main' into no-saucelabs
2 parents e5f07a6 + 33f4048 commit 6581cc9

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

vaadin-testbench-loadtest/testbench-converter-plugin/src/main/java/com/vaadin/testbench/loadtest/util/HarToK6Converter.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,14 @@ public class HarToK6Converter {
5757
private static final Pattern ENTITY_KEY_ARG_PATTERN = Pattern.compile(
5858
"\"templateEventMethodName\":\"(select|setDetailsVisible)\",\"templateEventMethodArgs\":\\[\"(\\d+)\"\\]");
5959

60-
/** Matches mSync input values (user-entered form data) in UIDL bodies. */
60+
/**
61+
* Matches mSync input values (user-entered form data) in UIDL bodies. The
62+
* value group uses a possessive quantifier and atomic alternation
63+
* ({@code ++}/{@code *+}) so the JSON string content is consumed in a
64+
* single pass.
65+
*/
6166
private static final Pattern MSYNC_INPUT_VALUE_PATTERN = Pattern.compile(
62-
"\"type\":\"mSync\",\"node\":\\d+,\"feature\":\\d+,\"property\":\"value\",\"value\":\"((?:[^\"\\\\]|\\\\.)*)\"");
67+
"\"type\":\"mSync\",\"node\":\\d+,\"feature\":\\d+,\"property\":\"value\",\"value\":\"((?:[^\"\\\\]++|\\\\.)*+)\"");
6368

6469
/**
6570
* Matches positive {@code "node":N} occurrences in UIDL request bodies.

vaadin-testbench-loadtest/testbench-converter-plugin/src/test/java/com/vaadin/testbench/loadtest/util/HarToK6ConverterTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
import java.nio.file.Path;
1414
import java.util.ArrayList;
1515
import java.util.List;
16+
import java.util.concurrent.atomic.AtomicReference;
1617

1718
import org.junit.jupiter.api.Test;
1819
import org.junit.jupiter.api.io.TempDir;
1920

2021
import static org.junit.jupiter.api.Assertions.assertEquals;
2122
import static org.junit.jupiter.api.Assertions.assertFalse;
23+
import static org.junit.jupiter.api.Assertions.assertNull;
2224
import static org.junit.jupiter.api.Assertions.assertTrue;
2325

2426
class HarToK6ConverterTest {
@@ -132,6 +134,64 @@ void csvRoundTripPreservesMultipleSpecialValues() throws IOException {
132134
"Second field round-trip should preserve escaped quotes");
133135
}
134136

137+
@Test
138+
void largeMsyncValueDoesNotOverflowStack() throws IOException {
139+
// Reproduces issue #2266: MSYNC_INPUT_VALUE_PATTERN used a quantified
140+
// alternation ((?:[^"\\]|\\.)*) which, on OpenJDK, recurses once per
141+
// repetition. A large mSync value therefore blows the call stack with
142+
// a StackOverflowError before this is fixed.
143+
//
144+
// Run the matcher on its own thread with a small stack so the test
145+
// fails fast and deterministically across platforms, rather than
146+
// depending on the (large, JVM-default) main-thread stack size.
147+
String value = "x".repeat(200_000);
148+
String initEntry = entry("GET", "http://localhost:8080/?v-r=init");
149+
String msyncBody = "{\"csrfToken\":\"abc-123\","
150+
+ "\"rpc\":[{\"type\":\"mSync\",\"node\":42,\"feature\":1,"
151+
+ "\"property\":\"value\"," + "\"value\":\"" + value + "\"}],"
152+
+ "\"syncId\":1,\"clientId\":1}";
153+
String postEntry = entryWithBody("POST",
154+
"http://localhost:8080/?v-r=uidl&v-uiId=0", msyncBody);
155+
156+
Path harFile = tempDir.resolve("msync-large.har");
157+
Path outputFile = tempDir.resolve("msync-large.js");
158+
Files.writeString(harFile, createHar(initEntry, postEntry));
159+
160+
AtomicReference<Throwable> failure = new AtomicReference<>();
161+
Thread worker = new Thread(null, () -> {
162+
try {
163+
new HarToK6Converter().convert(harFile, outputFile);
164+
} catch (Throwable t) {
165+
failure.set(t);
166+
}
167+
}, "converter", 256 * 1024);
168+
worker.start();
169+
try {
170+
worker.join();
171+
} catch (InterruptedException e) {
172+
Thread.currentThread().interrupt();
173+
throw new AssertionError("Interrupted while waiting for converter",
174+
e);
175+
}
176+
177+
Throwable thrown = failure.get();
178+
if (thrown instanceof StackOverflowError) {
179+
throw new AssertionError(
180+
"Converting a large mSync value overflowed the stack (issue #2266)",
181+
thrown);
182+
}
183+
assertNull(thrown,
184+
"Conversion should complete without error, but threw: "
185+
+ thrown);
186+
187+
// The value is captured into the CSV, confirming the matcher ran.
188+
Path csvFile = tempDir.resolve("msync-large-data.csv");
189+
assertTrue(Files.exists(csvFile), "CSV data file should be created");
190+
List<List<String>> records = parseCsvRecords(Files.readString(csvFile));
191+
assertEquals(value, records.get(1).get(0),
192+
"Large mSync value should round-trip into the CSV");
193+
}
194+
135195
/**
136196
* Helper: converts a single mSync value through the full HAR→k6 pipeline
137197
* and returns the generated CSV file path.

0 commit comments

Comments
 (0)