I'm trying to keep the current blog post run going (at least compared to the
last years) by blogging about random things I observe or learn. Today's post: I
found out by coincidence that OpenJDK 9 changed a detail about how regular
expressions are matched. The post is not about regular expressions in general,
but regular expressions as implemented by OpenJDK's java.util.regex.Pattern.
Exponential backtracking
The post's title already spoils it: the change is related to backtracking.
OpenJDK's Pattern implements quantifiers in regular expressions by trying
out the different possibilities and it then backtracks in case there was no
match. So for example, to see if a matches the pattern a?a, first the
pattern aa is tried and because it doesn't match (not enough as in the
input), the matching backtracks to the beginning and tries the pattern a
next, which then succeeds.
Now let's see what happens if the input 1111a is matched against the pattern
(1*)* in OpenJDK 8, like in the following code:
Pattern.compile("(1*)*").matcher("1111a").matches()). In words: in a group,
match zero or more 1s and repeat that group zero or more times.
In the beginning, the 1* inside the group matches greedily all characters
1 in the input until the a. Then the remaining input (the a) is used as
input for the remaining part of the regex, which is the group repetition. The
group is repeated again, but 1* still doesn't match the a and the matching
fails because not all input is used. Note that the implementation is smart
enough to not try to repeat the group forever with a zero-width match for
1*.
The implementation then remembers that in the beginning, it consumed all the
1s greedily. The question is: would the regex probably match the input if
the first greedy matching doesn't consume all the 1s, but all except the
last one? So the implementation backtracks to the point where it consumed all
but the last 1 and then tries to match the remaining regex against the input
1a. This approach still doesn't result in a match. That means another
backtrack and 11a is tried next. The big problem is now: that input 11a
will be again first matched greedily by 1*, because remaining regex means
the group repetition. And as there is still no match due to the trailing a,
this matching as part of the backtracking will backtrack as well.
In other words, for every character 1 in the input, there are two choices:
match and consume the character as part of the sub-expression 1* or don't
match and continue with the remaining expression (the group repetition). That
results in 2n different combinations that need to be tested, with
n being the number of leading 1s in the input. There will never be a
match, but that will only be known after every combination has been tested. So,
exponential runtime.
This is not exactly big news. The phenomena of catastrophic backtracking is well
understood and there are various ways how either the regular expression can be
changed to avoid the backtracking (see for example The Explosive Quantifier
Trap ) or how regexes can be implemented
without backtracking (see for example Regular Expression Matching Can Be Simple
And Fast). It is even known as an attack under the name ReDoS and has its
own OWASP entry.
The change in OpenJDK 9
In OpenJDK, an optimization was added to avoid the
exponential backtracking. Whenever a greedy repetition (*, +, curly
braces, etc) doesn't match some input, the input's cursor position is memoized.
Then, when the repetition is executed again during backtracking, it's checked
whether it already failed to match for this cursor position and if so, the
repetition isn't tested at all (as it will fail no match).
How does that help against exponential backtracking? Let's have a look again at
the previous regex (\d*) that should match the input 1111a. First the
greedy match of the 1s, then the failed attempt to match a and then the
backtracking of the first greedy match. The first backtracking attempt is with
the remaining input 1a. It doesn't match and it's memoized that this input
failed. Then 11a is tried next. It also fails to match, but it also
backtracks itself due to the first greedy match on the leading 11. During
that backtracking, the inputs 1a and 11a need to be tested, but only the
former is actually tested, due to the latter being memoized to have failed.
Hence the backtracking is now linear instead of exponential.
Note that this optimization only works if the pattern doesn't use any
backreferences.
More questions? Then study the source
of OpenJDK's Pattern implementation!
Im heutigen Blog-Post geht es um Webrahmenwerke. Genauer: es geht darum, wie man
auf typsichere Art und Weise Links zu Controllern erzeugt. Typsicher in dem
Sinne, dass man Variablen im Routing nur durch Werte des selben Typs ersetzen
kann, den der Controller auch tatsächlich erwartet. Übergibt man die falsche
Anzahl an Werten oder Werte des falschen Typs, soll der Code erst gar nicht
kompilieren – in anderen Worten: Es sollen nicht erst Exceptions zur Laufzeit
geworfen werden, sondern bereits beim kompilieren. Für das Beispiel beschränken
wir uns auf Pfad-Variablen.
Die Code-Beispiele für die Controller sind dabei lose an JAX-RS orientiert.
Es werden die folgenden Konventionen benutzt:
Methoden, die Requests behandeln, haben lediglich die Pfad-Variablen als
Parameter. Die Reihenfolge der Parameter ist dabei die Reihenfolge, in der sie
im Routing-Template definiert sind.
Note
In diesem Blog-Beitrag wird nur die Methode vorgestellt, mit der man Links
erstellen kann. Ein Router, der Requests auf Controller dispatcht, wird nicht
vorgestellt.
Außerdem wird eine gewisse Vertrautheit mit Java 8 (insbesondere Lambdas)
vorausgesetzt sowie auch ein wenig mit JAX-RS. Für eine Intro zu Lambdas
siehe etwa den Lambda Quick Start
Prior Art
Da Links erzeugen ein Recht häufiges Problem ist, gibt es natürlich so einiges
an Prior Art. Wahrscheinlich in so ziemlich jedem Webrahmenwerk, in den
unterschiedlichsten Ausprägungen. Ich habe stellvertretend einmal drei
herausgegriffen:
Zeigt schon mal die grobe Idee, wenn auch in Python und nicht in Java. Gewisse
Fehler werden abgefangen (falsche Anzahl an Argumenten zum Beispiel).
Allerdings passiert alles zur Laufzeit. Man braucht also eine recht gut
ausgeprägte Test-Suite, um Fehler beim Linken zu erkennen.
Allerdings funktioniert die Methoden-Auflösung zur Laufzeit und via Name als
String. Die Parametertypen müssen explizit angegeben werden und die
Typüberprüfung passiert ebenfalls erst zur Laufzeit.
Warum nicht Methodenreferenzen?
Da Java 8 endlich Methodenreferenzen eingeführt hat, liegt natürlich die Idee
nahe, es damit irgendwie zu versuchen.
Stellen wir uns vor, dass wir den folgenden Controller hätten:
Als erstes erstellen wir ein FunctionalInterface.
Das Interface soll eine Referenz auf eine Methode der Klasse H darstellen,
die keine Parameter entgegen nimmt und ein R als Ergebnis zurück gibt.
Dementsprechend sieht das Interface wie folgt aus:
Dieses Interface kann aus einer Methodenreferenz erstellt werden:
NoParam<HelloWorldController,String>hello=HelloWorldController::hello;// Aufrufen könnte man die Methode jetzt so (angenommen,
// someHelloWorldController ist eine Instanz von HelloWorldController):
Stringresult=hello.apply(someHelloWorldController);assert"Hello, world!".equals(result);
Folglich können wir damit dann die folgende Methode bauen:
<H,R>URIlinkTo(NoParam<H,R>handler){// Hier der Code, der die Routing-Informationen von handler ausliest und
// daraus dann eine URI baut
}
Jetzt ist es möglich, aus einem anderen Controller heraus einen Link zu unserer
gewünschten Methode HelloWorldController#hello() zu bauen:
URIhelloLink=linkTo(HelloWorldController::hello);
Wenn wir ein Argument zu viel übergeben würden (zum Beispiel, weil wir
denken, dass die hello-Ressource einen Namen entgegen nimmt, um einen
personalisierten Gruß zu erzeugen, kompiliert der Code nicht:
java: no suitable method found for linkTo(HelloWorldController::hello)
Ziel erreicht. Um tatsächlich Pfad-Parameter zu unterstützen, müssen wir jetzt
einfach (relativ mechanisch) weitere Interfaces einführen.
Erweitern wir zunächst unseren Controller um einen personalisierten Gruß:
Wenig überraschend steht H hierbei für den Typ des Controllers, P für
den Parameter und R für den Rückgabewert.
Desweiteren muss eine weitere Überladung von linkTo eingeführt werden:
URIlinkTo(OneParam<H,P,R>handler,Pparam){// Hier wieder Routing-Infos von handler auslesen und dann param einsetzen
}
Das ist zum Implementieren zwar ein wenig wortreich (für jede Anzahl an
Pfad-Variablen ein eigenes Interface und eine entsprechende linkTo-Methode),
aber das muss man zum Glück nur einmal tun und außerdem hat man ja auch nicht unendlich
lange Pfade in der Praxis.
Viel gravierender ist jedoch: es funktioniert überhaupt nicht. Man kann zwar aus
einer Methodenreferenz ein Lambda bauen. Allerdings geht die Information, aus
welcher Methode das Lambda erzeugt wird, dabei verloren. Wir brauchen die
Information, um welche Methode es sich handelt, jedoch, da wir ansonsten nicht
an die Route kommen.
Proxies to the rescue
Da die Antwort auf die meisten Probleme in Java "(dynamische) Code-Generierung"
ist, probieren wir es doch auch einmal damit. Genauer gesagt dynamische
Proxy-Objekte. Die Idee ist dabei folgendermaßen:
Wir erzeugen uns ein Proxy-Objekt vom gleichen Typ der Handler-Klasse.
Wir rufen die Methode auf, die übergeben wurde (genauer gesagt, das Lambda)
Das Proxy-Objekt ruft nicht wirklich die eigentliche Methode auf, sondern
merkt sich einfach, welche Methode aufgerufen wurde.
Wir holen uns die gemerkte Methode vom Proxy-Objekt.
Gehen wir davon aus, dass wir eine Klasse MethodResolver<T>, die die
Proxy-Objekte erstellt, könnte unsere linkTo-Methode also in der Art
aussehen:
URIlinkto(Class<H>handlerClass,OneParam<H,P,R>handler,Pparam){MethodResolver<H>methodResolver=MethodResolver.on(handlerClass);handler.apply(methodResolver,param);Methodmethod=methodResolver.resolve();// Mit handlerClass und method kann man jetzt an die Routing-Informationen
// kommen
}
Die meisten AOP-Rahmenwerke bieten Method-Interceptors an, mit denen man das
recht einfach umsetzen kann. Für Proxetta könnte ein entsprechendes Advice zum
Beispiel so aussehen:
/**
* MethodResolver advice applied on all methods. It puts the method in a class
* variable that can be accessed later using reflection.
*/classMethodResolverAdviceimplementsProxyAdvice{publicMethodmethod;publicObjectexecute(){finalClass<?>targetClass=targetClass();finalStringmethodName=targetMethodName();finalClass<?>[]argumentTypes=createArgumentsClassArray();try{method=targetClass.getMethod(methodName,argumentTypes);}catch(NoSuchMethodExceptione){thrownewRuntimeException(e);}returnreturnValue(null);}}
Pithos, ein S3-Klon, der Cassandra zur Ablage benutzt, unterstützt aktuell
nur Signaturen in V2. S3
unterstützt inzwischen aber eigentlich nur noch V4,
weswegen der offizielle AmazonS3Client
ein paar Probleme bei der Verwendung mit Pithos macht:
Exception in thread "main" com.amazonaws.services.s3.model.AmazonS3Exception: The request signature we calculated does not match the signature you provided. Check your key and signing method. (Service: Amazon S3; Status Code: 403; Error Code: SignatureDoesNotMatch; Request ID: 1e04fbc1-bf91-4bb3-af1e-6829ce549524), S3 Extended Request ID: 1e04fbc1-bf91-4bb3-af1e-6829ce549524
at com.amazonaws.http.AmazonHttpClient.handleErrorResponse(AmazonHttpClient.java:1182)
at com.amazonaws.http.AmazonHttpClient.executeOneRequest(AmazonHttpClient.java:770)
at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:489)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:310)
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:3608)
at com.amazonaws.services.s3.AmazonS3Client.getObject(AmazonS3Client.java:1135)
at com.amazonaws.services.s3.AmazonS3Client.getObject(AmazonS3Client.java:1015)
...
Mit einem kleinen (Trick|Hack) kann man den Client dann aber trotzdem dazu
bewegen, V2 zum Signieren zu benutzen:
Wie man am Titel bereits leicht erraten kann: heute geht es (zum
ersten Mal in diesem Blog?) um Java. Genauer: Wie man heterogene
Container in Java umsetzen kann.
Zunächst zur Idee: Wir wollen eine Klasse Favorites
implementieren, in die man seine Lieblingsobjekte speichern und auch
wieder abrufen kann. Damit man nicht einfach irgendein beliebiges
Objekt zurückbekommt, sondern ein Objekt von einem bestimmten Typ,
übergibt man beim Speichern und abrufen die Klasse des Objekts.
Der geneigte Java-erfahrene Leser wird direkt erkennen, dass
das Beispiel aus Effective Java von Joshua Bloch ist. Das
ist absolut richtig und auch Teile des hier gezeigten Codes
stammen aus diesem Buch, das durchaus lesenswert ist (auch
für Nicht-Java-Programmierer).
Die erste Idee, das zu implementieren, könnte etwa so aussehen:
Man nimmt also einfach das Klassenobjekt des Werts als Schlüssel in
einer Map, um den Wert zu speichern. Beim Auslesen wird dann auch
wieder das Klassenobjekt übergeben, womit man an den Wert kommt. Wurde
für den Typ kein Wert hinterlegt, wird einfach nichts gefunden.
An sich scheint das auch ganz gut zu funktionieren:
Auch der Fall, dass kein Wert hinterlegt wurde für den Typ,
funktioniert einfach: favorites.get(type) gibt dann null
zurück und null kann zu allem gecastet werden.
Und hätte Java jetzt nicht Type Erasure, wäre der Blogpost auch
schon zu Ende. Da Java allerdings Type Erasure hat, stößt man recht
bald auf ein Problem, wenn man auf versucht, ein Generic in den
Container zu packen: List<Integer>.class ist ein Syntaxfehler. Das
liegt daran, dass List<Integer> und List<String> dasselbe
Klassenobjekt haben, nämlich List.class. Das bedeutet aber auch,
dass man eine Liste von Strings in Favorites packen kann und
danach als Liste von Integern auslesen kann (bzw. man kann es
zumindest versuchen):
Wenn man das jetzt ausführt, scheint es sogar zu
funktionieren. Jedenfalls läuft es anstandslos durch. Allerdings nur
bis man dann den Wert tatsächlich einmal versucht als den angegebenen
Wert zu verwenden:
Integerfirst=values.get(0);
Bei einer erneuten Ausführung wird jetzt eine Ausnahme geworfen:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Bei genauem Hinschauen erkennt man auch bereits beim Kompilieren, dass
hier potenziell zur Laufzeit etwas kaputt gehen könnte:
TypeReference kann jetzt zur Laufzeit nachsehen, welcher Wert als
Typvariable übergeben wurde. Das wiederum kann dann Favorite für sich
benutzen.
Die Implementierung von TypeReference:
importjava.lang.reflect.ParameterizedType;importjava.lang.reflect.Type;publicabstractclassTypeReference<T>{privatefinalTypetype;protectedTypeReference(){TypesuperClass=getClass().getGenericSuperclass();if(superClassinstanceofClass<?>){thrownewRuntimeException("Missing type parameter");}type=((ParameterizedType)superClass).getActualTypeArguments()[0];}publicTypegetType(){returntype;}}
Die Ausgabe ist wie erwartet Some string 1234 null. Fertig ist er
also, unser typsicherer heterogener Container, der auch mit Generics
funktioniert (wenn auch etwas umständlich).
Oder fast. Wenn da dieses @SuppressWarnings("unchecked") nicht
wäre. Immerhin haben wir vor einem Augenblick erst gesehen, dass man
Compiler-Warnungen nicht ignorieren sollte. Und tatsächlich kann man
auch für die neue Favorites einen Fall konstruieren, bei dem zur
Laufzeit eine Ausnahme fliegt:
static<T>List<T>favoriteList(Favoritesf){TypeReference<List<T>>typeReference=newTypeReference<List<T>>(){};List<T>result=f.getFavorite(typeReference);if(result==null){result=newArrayList<T>();f.putFavorite(typeReference,result);}returnresult;}publicstaticvoidmain(String[]args){Favoritesf=newFavorites();List<String>listOfStrings=favoriteList(f);List<Integer>listOfIntegers=favoriteList(f);listOfIntegers.add(42);Stringbooooom=listOfStrings.get(0);}// java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Um diese Ausnahme zu vermeiden, müsste man die Typ-Argumente
durchgehen und schauen, ob noch irgendwo eine TypeVariable
vorkommt. Ein Beispiel, in dem das gemacht wird, ist GenericType<T>
aus JAX-RS (Quelltext). Das
würde aber noch immer zur Laufzeit eine Exception werfen und nicht zur
Compile-Zeit einen Fehler produzieren.