eclipse-openj9 / openj9

Eclipse OpenJ9: A Java Virtual Machine for OpenJDK that's optimized for small footprint, fast start-up, and high throughput. Builds on Eclipse OMR (https://github.com/eclipse/omr) and combines with the Extensions for OpenJDK for OpenJ9 repo.
Other
3.27k stars 721 forks source link

JDK 11/17/21 - Missing ArrayIndexOutOfBoundsException in a loop, and instead incorrectly throwing NullPointerException #18803

Closed JeffersonYu1 closed 7 months ago

JeffersonYu1 commented 7 months ago

JDK 11/17/21 - Missing ArrayIndexOutOfBoundsException in a loop, and instead incorrectly throwing NullPointerException

System / OS / Java Runtime Information

Java version

$ java -version
openjdk version "17.0.9" 2023-10-17
IBM Semeru Runtime Open Edition 17.0.9.0 (build 17.0.9+9)
Eclipse OpenJ9 VM 17.0.9.0 (build openj9-0.41.0, JRE 17 Linux amd64-64-Bit Compressed References 20231017_614 (JIT enabled, AOT enabled)
OpenJ9   - 461bf3c70
OMR      - 5eee6ad9d
JCL      - 3699725139c based on jdk-17.0.9+9)

Operating system details

$ cat /etc/*release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
$ uname -a
Linux luzhou 5.4.0-86-generic #97-Ubuntu SMP Fri Sep 17 19:19:40 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Description

Incorrect result is given when running the following program. The bug affects 11.0.21, 17.0.9, 21.0.1 on default, hot, veryhot, and scorching levels.

Steps to reproduce

The following steps shows how to reproduce the bug on JDK 17.0.9 in a Ubuntu Linux environment.

Compile

$ javac C.java

Execute

$ java C
Exception in thread "main" java.lang.NullPointerException
        at C.main(C.java)
$ java -Xjit:optlevel=scorching C 
Exception in thread "main" java.lang.NullPointerException
        at C.main(C.java)
$ java -Xnojit C
100000 // expected value

Source code for an executable test case

// C.java
public class C {
    static int[] a = new int[1];

    static void m() {
        int j = 0;
        for (int i = 0; (boolean) (j >= 0) && j++ < 2; i--) {
            a[i] = 0;
        }
    }

    public static void main(String[] args) {
        int count = 0;
        int N = 100_000;
        for (int i = 0; i < N; ++i) {
            try {
                m();
            } catch (ArrayIndexOutOfBoundsException e) {
                count += 1;
            }
        }
        System.out.println(count);
    }
}

Workaround

Disable JIT.

$ java -Xnojit C
pshipton commented 7 months ago

@hzongaro fyi

BradleyWood commented 7 months ago

Looks like loop versioner issue.

Last failing index: 78 = loop versioner

$ java "-Xjit:limit={*C.main*}(optLevel=hot,traceFull,log=log.txt,lastOptIndex=78,count=0),disableAsyncCompilation" C
Exception in thread "main" java.lang.NullPointerException
    at C.main(C.java)
$ java "-Xjit:limit={*C.main*}(optLevel=hot,traceFull,log=log.txt,disableLoopVersioner,count=0),disableAsyncCompilation" C
100000
hzongaro commented 7 months ago

@jdmpapin, I know you've been looking at some Loop Versioner issues recently. May I ask you to work with @BradleyWood on this problem?

jdmpapin commented 7 months ago

The loop in m() has two opposite-direction comparisons against j in its condition. The j >= 0 condition ends up at the end of the loop body, just before the only back-edge, and so that's the one that loop versioner uses to predict the number of iterations when versioning bound checks. But since j is increasing (j++), versioner treated the 0 as an upper bound, as though the condition were j <= 0, in which case the loop body would execute only once. In reality it executes twice, and on the second iteration it tries to access a[-1]. The -1 is effectively zero-extended, and writing at a very large offset causes a segfault, which is then interpreted as an implicit null check failure