The invokedynamic string concatenation strategies don't preserve the order of operations when evaluating subexpressions and converting them to strings.
With the inline strategy, each subexpression is evaluated and converted to a string in order. With indy each subexpression is evaluated in order *but not converted to a string*, and later each subexpression is converted to a string in order.
Originally reported on compiler-dev@ [1]. briangoetz confirmed the original behaviour of the inline strategy [2] is correct, and sketched one possible fix [3]:
> The nature of indy leads to the structure we ended up with. The natural
> way to do this is to have the various arguments passed as real stack
> arguments, causing them to all be evaluated before being pushed through
> the MH nest (which is where the string conversion happens, using
> MethodHandle::filterArguments.) We'd essentially have to write a new
> bootstrap (but leave the old one in place), and have the compiler
> generate bytecode to do the string conversion as the values are being
> pushed on the stack. This requires a different bootstrap and a
> different calling convention. So old bytecode will stay around, and the
> old bootstrap would have to be kept around ... pretty disruptive.
[1] https://mail.openjdk.java.net/pipermail/compiler-dev/2021-September/017911.html
[2] https://mail.openjdk.java.net/pipermail/compiler-dev/2021-September/017912.html
[3] https://mail.openjdk.java.net/pipermail/compiler-dev/2021-September/017929.html
Repro:
class T {
static String test() {
StringBuilder builder = new StringBuilder("foo");
return "" + builder + builder.append("bar");
}
public static void main(String[] args) {
System.err.println(test());
}
}
$ javac -XDstringConcat=inline T.java ; java T
foofoobar
$ javac -XDstringConcat=indy T.java ; java T
foobarfoobar