JDK-8262739 : String inflation C2 intrinsic prevents insertion of anti-dependencies
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 9,11,15,16,17
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2021-02-25
  • Updated: 2021-05-25
  • Resolved: 2021-03-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 17
11.0.12-oracleFixed 17 b16Fixed
Related Reports
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Reproduced on Mac OS with both Java 11 and Java 15

A DESCRIPTION OF THE PROBLEM :
We observed that the reverse() method of the org.apache.commons.lang.StringUtils class contained in Apache commons-lang version 2.6 started returning incorrect results after thousands of iterations.

The reason we suspect an issue with compact strings and the optimizer is because:

1. The bug only reproduces with strings which can be represented compactly (all code points < 256)
2. The bug only reproduces after calling the method in a loop for an indeterminate number of times (the range we've seen is from a minimum of around 50K iterations to a maximum of around 135K iterations).
3. The bug does not reproduce when compact strings are disabled via -XX:-CompactStrings
4. The bug does not reproduce when the optimizer is disabled via -Djava.compiler=NONE

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached source code.  A couple of ways you can reproduce it:

1. Clone the repository I created at https://github.com/jyemin/compact-strings-bug and execute "./gradlew run".
2. Manually download commons-lang jar file from https://repo1.maven.org/maven2/commons-lang/commons-lang/2.6/ and compile the attached source code with this jar in your classpath

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected that the following would be printed:

  Original string: 123456
  Expected reversed string: 654321

  Completed normally.  Exiting.
ACTUAL -
Instead, the following is printed:

  Original string: 123456
  Expected reversed string: 654321

  Iteration: 118306
  Actual: 654326

  Iteration: 118307
  Actual: 654326

  Iteration: 118308
  Actual: 654326

  Iteration: 118309
  Actual: 654326

  Iteration: 118310
  Actual: 654326

  Exiting after 5 failures

A few additional observations:

1. The number of iterations before it starts returning incorrect results varies, but once it starts behaving incorrectly, every subsequent call behaves incorrectly, in exactly the same way
2. It also seems to be the case that there is a single incorrect character in the reversed string.  It's always the last character, and instead of being the expected first character from the original string, it's the last character from the original string.


---------- BEGIN SOURCE ----------
import org.apache.commons.lang.StringUtils;

public class CompactStringBug {

    protected static final int MAX_FAILURES = 5;

    public static void main(String[] args) {
        final String id = "123456";
        final String expectedReversedId = "654321";

        /*
          The bug does not reproduce if you include a multibyte character in the string
        */
//        final String id = "12345\u1234";
//        final String expectedReversedId = "\u123454321";

        System.out.println("Original string: " + id);
        System.out.println("Expected reversed string: " + expectedReversedId);
        System.out.println();

        int failures = 0;
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            String reversedId = StringUtils.reverse(id);
            if (!expectedReversedId.equals(reversedId)) {
                failures++;
                System.out.println("Iteration: " + i);
                System.out.println("Actual: " + reversedId);
                System.out.println();
                if (failures == MAX_FAILURES) {
                    System.out.println("Exiting after " + MAX_FAILURES + " failures");
                    System.exit(1);
                }
            }
        }
        System.out.println("Completed normally.  Exiting.");
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
We've found a number of workarounds.  The most straightforward is to replace use of commons-lang StringUtils with java.lang.StringBuilder#reverse.  

These are some other ways we've worked around the bug during analysis, but only the first is practicable in production:

1. Disable compact strings, e.g. by starting java with -XX:-CompactStrings
2. Disable the optimizer, e.g. by starting java with -Djava.compiler=NONE
3. Ensure that at least one character in the string is multibyte (i.e. code point > 255)


FREQUENCY : always



Comments
Confirmed: $ java -version openjdk version "17-ea" 2021-09-14 OpenJDK Runtime Environment (build 17-ea+16-1315) OpenJDK 64-Bit Server VM (build 17-ea+16-1315, mixed mode, sharing) $ java -cp build/classes/java/main:commons-lang-2.6.jar CompactStringBug Original string: 123456 Expected reversed string: 654321 Completed normally. Exiting.
09-04-2021

Fix Request (11u) I would like to backport this patch to 11u for parity with Oracle 11.0.12-oracle. The original patch does not apply cleanly, 11u change has been reviewed.
07-04-2021

Request the submitter to verify the fix with the latest version of JDK at https://jdk.java.net/17/
07-04-2021

11u code review thread: https://mail.openjdk.java.net/pipermail/jdk-updates-dev/2021-April/005608.html
06-04-2021

Changeset: 3caea470 Author: Tobias Hartmann <thartmann@openjdk.org> Date: 2021-03-29 12:40:11 +0000 URL: https://git.openjdk.java.net/jdk/commit/3caea470
29-03-2021

The problem is indeed related to the String inflation intrinsic in C2. It block correct insertion of anti-dependencies between loads/stores from/to a char[], leading to incorrect ordering of instructions after scheduling and therefore incorrect results.
16-03-2021

ILW = Incorrect execution of compiled code, with customer application and small reproducer, disable _inflateStringC C2 intrinsic = HMM = P2
15-03-2021

Here's a clue: -XX:+PrintIntrinsics indicates the following intrinsics are used during the test: java.lang.StringUTF16::compress java.lang.StringLatin1::equals java.lang.StringLatin1::inflate Running with -XX:DisableIntrinsic=_inflateStringC causes the problem to disappear.
05-03-2021

[~tongwan] Can you run with -Xint, also -XX:TieredStopAtLevel=1 to see it duplicates?
01-03-2021

I tried the following command: java -XX:TieredStopAtLevel=1 -cp .;commons-lang-2.6.jar CompactStringBug and the test completed normally.
01-03-2021

The observations on Windows 10: JDK 8: Passed. JDK 11: Failed, exiting after 5 failures JDK 15: Failed. JDK 17ea+6: Failed.
26-02-2021

Additional information from the submitter: 1. Clone the repository at https://github.com/jyemin/compact-strings-bug, and use Gradle to download it by running the command "./gradlew run". Gradle is self-installing so that should be all you have to do. You just need the JDK in your path. 2. Download it manually from https://repo1.maven.org/maven2/commons-lang/commons-lang/2.6/commons-lang-2.6.jar and compile the test program with "javac -cp ...". A couple of other observations that I've made since submitting the bug report: 1. The common-lang-2.6 jar file was compiled back in 2011 and has a class file version of 47. 2. If I grab the source code for the StringUtils.reverse method recompile it, the bug no longer reproduces.
26-02-2021

Requested more details from the submitter to compile the reproducer.
26-02-2021