残念ながら、Java で String に対して正規表現を使用する場合、タイムアウトを指定する方法はありません。したがって、どのパターンがどの入力に適用されるかを厳密に制御できない場合、(あまり適切に設計されていない) パターンを (悪意のある?) 入力に一致させようと無限に試行しながら、大量の CPU を消費するスレッドが発生する可能性があります。
Thread#stop() が廃止された理由を認識しています ( http://download.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.htmlを参照)。それらは、ThreadDeath 例外の場合に破損する可能性があり、実行中の JVM 環境を汚染し、微妙なエラーにつながる可能性があるオブジェクトを中心にしています。
JVM の仕組みについて私よりも深い洞察を持っている人への私の質問は次のとおりです。停止する必要があるスレッドが、プログラムの残りの部分で使用されるオブジェクトに対する (明らかな) モニターまたは参照を保持していない場合、それにもかかわらず、Thread#stop() を使用することは許容できますか?
正規表現のマッチングをタイムアウトで処理できるように、かなり防御的なソリューションを作成しました。コメントやコメントをいただければ幸いです。特に、回避しようと努力しているにも関わらず、このアプローチが引き起こす可能性のある問題について。
ありがとう!
import java.util.concurrent.Callable;
public class SafeRegularExpressionMatcher {
// demonstrates behavior for regular expression running into catastrophic backtracking for given input
public static void main(String[] args) {
SafeRegularExpressionMatcher matcher = new SafeRegularExpressionMatcher(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "(x+x+)+y", 2000);
System.out.println(matcher.matches());
}
final String stringToMatch;
final String regularExpression;
final int timeoutMillis;
public SafeRegularExpressionMatcher(String stringToMatch, String regularExpression, int timeoutMillis) {
this.stringToMatch = stringToMatch;
this.regularExpression = regularExpression;
this.timeoutMillis = timeoutMillis;
}
public Boolean matches() {
CallableThread<Boolean> thread = createSafeRegularExpressionMatchingThread();
Boolean result = tryToGetResultFromThreadWithTimeout(thread);
return result;
}
private CallableThread<Boolean> createSafeRegularExpressionMatchingThread() {
final String stringToMatchForUseInThread = new String(stringToMatch);
final String regularExpressionForUseInThread = new String(regularExpression);
Callable<Boolean> callable = createRegularExpressionMatchingCallable(stringToMatchForUseInThread,
regularExpressionForUseInThread);
CallableThread<Boolean> thread = new CallableThread<Boolean>(callable);
return thread;
}
private Callable<Boolean> createRegularExpressionMatchingCallable(final String stringToMatchForUseInThread,
final String regularExpressionForUseInThread) {
Callable<Boolean> callable = new Callable<Boolean>() {
public Boolean call() throws Exception {
return Boolean.valueOf(stringToMatchForUseInThread.matches(regularExpressionForUseInThread));
}
};
return callable;
}
private Boolean tryToGetResultFromThreadWithTimeout(CallableThread<Boolean> thread) {
startThreadAndApplyTimeout(thread);
Boolean result = processThreadResult(thread);
return result;
}
private void startThreadAndApplyTimeout(CallableThread<Boolean> thread) {
thread.start();
try {
thread.join(timeoutMillis);
} catch (InterruptedException e) {
throwRuntimeException("Interrupt", e);
}
}
private Boolean processThreadResult(CallableThread<Boolean> thread) {
Boolean result = null;
if (thread.isAlive()) {
killThread(thread); // do not use anything from the thread anymore, objects may be damaged!
throwRuntimeException("Timeout", null);
} else {
Exception exceptionOccurredInThread = thread.getException();
if (exceptionOccurredInThread != null) {
throwRuntimeException("Exception", exceptionOccurredInThread);
} else {
result = thread.getResult();
}
}
return result;
}
private void throwRuntimeException(String situation, Exception e) {
throw new RuntimeException(situation + " occured while applying pattern /" + regularExpression + "/ to input '"
+ stringToMatch + " after " + timeoutMillis + "ms!", e);
}
/**
* This method uses {@link Thread#stop()} to kill a thread that is running wild. Although it is acknowledged that
* {@link Thread#stop()} is inherently unsafe, the assumption is that the thread to kill does not hold any monitors on or
* even references to objects referenced by the rest of the JVM, so it is acceptable to do this.
*
* After calling this method nothing from the thread should be used anymore!
*
* @param thread Thread to stop
*/
@SuppressWarnings("deprecation")
private static void killThread(CallableThread<Boolean> thread) {
thread.stop();
}
private static class CallableThread<V> extends Thread {
private final Callable<V> callable;
private V result = null;
private Exception exception = null;
public CallableThread(Callable<V> callable) {
this.callable = callable;
}
@Override
public void run() {
try {
V result = compute();
setResult(result);
} catch (Exception e) {
exception = e;
} catch (ThreadDeath e) {
cleanup();
}
}
private V compute() throws Exception {
return callable.call();
}
private synchronized void cleanup() {
result = null;
}
private synchronized void setResult(V result) {
this.result = result;
}
public synchronized V getResult() {
return result;
}
public synchronized Exception getException() {
return exception;
}
}
}
編集:
このソリューションを教えてくれたdawceのおかげで、追加のスレッドを必要とせずに元の問題を解決できました。そこにコードを投稿しました。回答してくれたすべての人に感謝します。