From e392856049bded8dadb0a0fa9340976b5fe069b0 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Wed, 17 Jan 2024 19:20:25 +0100 Subject: [PATCH] Make regexp execution loop interruptible #1189 Uses Thread.currentThread.isInterrupted() so that the interruption flag remains set to true, we only terminate the RegExp evaluation loop, but other (potentially third-party calling) code may still have to check for the interrupted flag to stop its execution as well. I also added a test with a long-running regexp that fails without the interrupt check. --- .../javascript/regexp/NativeRegExp.java | 3 +++ .../javascript/tests/NativeRegExpTest.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/org/mozilla/javascript/regexp/NativeRegExp.java b/src/org/mozilla/javascript/regexp/NativeRegExp.java index 2b7cffdefe..66dedbdaca 100644 --- a/src/org/mozilla/javascript/regexp/NativeRegExp.java +++ b/src/org/mozilla/javascript/regexp/NativeRegExp.java @@ -1878,6 +1878,9 @@ private static boolean executeREBytecode(REGlobalData gData, String input, int e for (; ; ) { + if (Thread.currentThread().isInterrupted()) { + throw new RuntimeException("Received interrupt while executing regexp"); + } if (reopIsSimple(op)) { int match = simpleMatch(gData, input, op, program, pc, end, true); result = match >= 0; diff --git a/testsrc/org/mozilla/javascript/tests/NativeRegExpTest.java b/testsrc/org/mozilla/javascript/tests/NativeRegExpTest.java index 944629438d..e1abcce32d 100644 --- a/testsrc/org/mozilla/javascript/tests/NativeRegExpTest.java +++ b/testsrc/org/mozilla/javascript/tests/NativeRegExpTest.java @@ -5,12 +5,21 @@ package org.mozilla.javascript.tests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import org.junit.Test; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + public class NativeRegExpTest { @Test @@ -49,6 +58,21 @@ public void ignoreCase() throws Exception { testEvaluate("i-false-true-false-false", "/foo/i;"); } + /** @throws Exception if an error occurs */ + @Test + public void interruptLongRunningRegExpEvaluation() throws Exception { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + try { + Future future = executorService.submit(() -> test("false", "/(.*){1,32000}[bc]/.test(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");")); + assertThrows(TimeoutException.class, () -> future.get(1, TimeUnit.SECONDS)); + executorService.shutdownNow(); + assertTrue(executorService.awaitTermination(30, TimeUnit.SECONDS)); + } + finally { + executorService.shutdown(); + } + } + /** @throws Exception if an error occurs */ @Test public void multilineCtor() throws Exception {