Skip to content

Commit

Permalink
Make regexp execution loop interruptible #1189
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
awa-xima committed Jan 17, 2024
1 parent e1f9159 commit e392856
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/org/mozilla/javascript/regexp/NativeRegExp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions testsrc/org/mozilla/javascript/tests/NativeRegExpTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit e392856

Please sign in to comment.