From 619f7c46747f443c543a63cfecc5d77b4d5da536 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Tue, 14 May 2024 08:39:34 -0400 Subject: [PATCH] Implement ability to timeout execution of a regexp --- src/org/joni/ByteCodeMachine.java | 8 +++++++- src/org/joni/Matcher.java | 21 ++++++++++++++++++++ src/org/joni/MatcherFactory.java | 6 ++++++ src/org/joni/Regex.java | 8 ++++++++ src/org/joni/exception/TimeoutException.java | 7 +++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/org/joni/exception/TimeoutException.java diff --git a/src/org/joni/ByteCodeMachine.java b/src/org/joni/ByteCodeMachine.java index d25ea6d3..0870a998 100644 --- a/src/org/joni/ByteCodeMachine.java +++ b/src/org/joni/ByteCodeMachine.java @@ -176,6 +176,7 @@ private final int execute(final boolean checkThreadInterrupt) throws Interrupted int interruptCheckCounter = 0; while (true) { if (interruptCheckCounter++ >= interruptCheckEvery) { + if (timeout != -1) handleTimeout(); handleInterrupted(checkThreadInterrupt); interruptCheckCounter = 0; } @@ -306,11 +307,16 @@ private final int execute(final boolean checkThreadInterrupt) throws Interrupted } // main while } + private void handleTimeout() throws InterruptedException { + if (System.nanoTime() - startTime > timeout) throw TIMEOUT_EXCEPTION; + } + private final int executeSb(final boolean checkThreadInterrupt) throws InterruptedException { final int[] code = this.code; int interruptCheckCounter = 0; while (true) { if (interruptCheckCounter++ >= interruptCheckEvery) { + if (timeout != -1) handleTimeout(); handleInterrupted(checkThreadInterrupt); interruptCheckCounter = 0; } @@ -447,7 +453,7 @@ private final int executeSb(final boolean checkThreadInterrupt) throws Interrupt private void handleInterrupted(final boolean checkThreadInterrupt) throws InterruptedException { if (interrupted || (checkThreadInterrupt && Thread.currentThread().isInterrupted())) { Thread.interrupted(); - throw new InterruptedException(); + throw INTERRUPTED_EXCEPTION; } interruptCheckEvery = Math.min(interruptCheckEvery << 1, MAX_INTERRUPT_CHECK_EVERY); } diff --git a/src/org/joni/Matcher.java b/src/org/joni/Matcher.java index a2d4d18f..3bab0de5 100644 --- a/src/org/joni/Matcher.java +++ b/src/org/joni/Matcher.java @@ -27,8 +27,11 @@ import org.jcodings.constants.CharacterType; import org.jcodings.specific.ASCIIEncoding; import org.joni.constants.internal.AnchorType; +import org.joni.exception.TimeoutException; public abstract class Matcher extends IntHolder { + static final InterruptedException INTERRUPTED_EXCEPTION = new InterruptedException(); + static final InterruptedException TIMEOUT_EXCEPTION = new TimeoutException(); public static final int FAILED = -1; public static final int INTERRUPTED = -2; @@ -49,13 +52,26 @@ public abstract class Matcher extends IntHolder { protected int msaBegin; protected int msaEnd; + protected long timeout; // nanoseconds + + // nanoseconds since entering searchCommon (underlying machines will check during interrupt checks + // which will cheapen how often we look but also it should be granular enough to not matter). + protected long startTime; + Matcher(Regex regex, Region region, byte[]bytes, int p, int end) { + this(regex, region, bytes, p, end, -1); + } + + // FIXME: For next major version this should be the main constructor and MatcherFactory should + // have the abstract method be for this. + Matcher(Regex regex, Region region, byte[]bytes, int p, int end, long timeout) { this.regex = regex; this.enc = regex.enc; this.bytes = bytes; this.str = p; this.end = end; this.msaRegion = region; + this.timeout = timeout; } // main matching method @@ -323,6 +339,7 @@ public final int searchInterruptible(int gpos, int start, int range, int option) } private final int searchCommon(int gpos, int start, int range, int option, boolean interrupt) throws InterruptedException { + if (timeout != -1) startTime = System.nanoTime(); int s, prev; int origStart = start; int origRange = range; @@ -640,4 +657,8 @@ static void debugSearch(String name, int textP, int textEnd, int textRange) { Config.log.println(name + ": text: " + textP + ", text_end: " + textEnd + ", text_range: " + textRange); } + public void setTimeout(long timeout) { + System.out.println("TIMEOUT = " + timeout); + this.timeout = timeout; + } } diff --git a/src/org/joni/MatcherFactory.java b/src/org/joni/MatcherFactory.java index 5ae2f3ec..48f3ae44 100644 --- a/src/org/joni/MatcherFactory.java +++ b/src/org/joni/MatcherFactory.java @@ -22,6 +22,12 @@ abstract class MatcherFactory { abstract Matcher create(Regex regex, Region region, byte[]bytes, int p, int end); + public Matcher create(Regex regex, Region region, byte[]bytes, int p, int end, long timeout) { + Matcher matcher = create(regex, region, bytes, p, end); + matcher.setTimeout(timeout); + return matcher; + } + static final MatcherFactory DEFAULT = new MatcherFactory() { @Override Matcher create(Regex regex, Region region, byte[]bytes, int p, int end) { diff --git a/src/org/joni/Regex.java b/src/org/joni/Regex.java index 967a3f91..10099ed6 100644 --- a/src/org/joni/Regex.java +++ b/src/org/joni/Regex.java @@ -185,10 +185,18 @@ public Matcher matcher(byte[]bytes, int p, int end) { return factory.create(this, numMem == 0 ? null : Region.newRegion(numMem + 1), bytes, p, end); } + public Matcher matcher(byte[]bytes, int p, int end, long timeout) { + return factory.create(this, numMem == 0 ? null : Region.newRegion(numMem + 1), bytes, p, end, timeout); + } + public Matcher matcherNoRegion(byte[]bytes, int p, int end) { return factory.create(this, null, bytes, p, end); } + public Matcher matcherNoRegion(byte[]bytes, int p, int end, long timeout) { + return factory.create(this, null, bytes, p, end, timeout); + } + public int numberOfCaptures() { return numMem; } diff --git a/src/org/joni/exception/TimeoutException.java b/src/org/joni/exception/TimeoutException.java new file mode 100644 index 00000000..18866809 --- /dev/null +++ b/src/org/joni/exception/TimeoutException.java @@ -0,0 +1,7 @@ +package org.joni.exception; + +public class TimeoutException extends InterruptedException { + public TimeoutException() { + super(); + } +}