| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| class Foo { |
| volatile Object bar; |
| } |
| |
| public class Main { |
| |
| public static void main(String[] args) { |
| Main main = new Main(); |
| main.test(); |
| System.out.println("passed"); |
| } |
| |
| // Check that no explicit null check is emitted for the field load of volatile |
| // field `Foo.bar` before entering the Baker read barrier thunk. |
| // |
| // Note: We cannot check the ARM64 assembly code of the Baker read barrier |
| // thunk code, as it is not emitted in the CFG output. |
| // |
| /// CHECK-START-ARM64: void Main.test() disassembly (after) |
| /// CHECK: <<Foo:l\d+>> InstanceFieldGet [{{l\d+}}] field_name:Main.foo field_type:Reference loop:<<Loop:B\d+>> |
| /// CHECK: NullCheck [<<Foo>>] dex_pc:<<PC:\d+>> loop:<<Loop>> |
| /// CHECK-NEXT: InstanceFieldGet [<<Foo>>] dex_pc:<<PC>> field_name:Foo.bar field_type:Reference loop:<<Loop>> |
| /// CHECK-NEXT: add w<<BaseRegNum:\d+>>, {{w\d+}}, #0x8 (8) |
| /// CHECK-NEXT: adr lr, #+0xc |
| // The following instruction (generated by |
| // `art::arm64::CodeGeneratorARM64::EmitBakerReadBarrierCbnz`) checks the |
| // Marking Register (X20) and goes into the Baker read barrier thunk if MR is |
| // not null. The null offset (#+0x0) in the CBNZ instruction is a placeholder |
| // for the offset to the Baker read barrier thunk (which is not yet set when |
| // the CFG output is emitted). |
| /// CHECK-NEXT: cbnz x20, #+0x0 |
| /// CHECK-NEXT: ldar {{w\d+}}, [x<<BaseRegNum>>] |
| |
| public void test() { |
| // Continually check that reading field `foo.bar` throws a |
| // NullPointerException while allocating over 64 MiB of memory (with heap |
| // size limited to 16 MiB), in order to increase memory pressure and |
| // eventually trigger a concurrent garbage collection, which will start by |
| // putting the GC in marking mode and enable read barriers (when the |
| // Concurrent Copying collector is used). |
| for (int i = 0; i != 64 * 1024; ++i) { |
| allocateAtLeast1KiB(); |
| try { |
| // Read volatile field `bar` of `foo`, which is null, and is expected |
| // to produce a NullPointerException. On ARM64, this is implemented as a |
| // load-acquire (LDAR instruction). |
| // |
| // When the Concurrent Copying GC is marking, read barriers are enabled |
| // and the field load executes code from a Baker read barrier thunk. |
| // On ARM64, there used to be a bug in this thunk for the load-acquire |
| // case, where an explicit null check was missing, triggering an |
| // unhandled SIGSEGV when trying to load the lock word from the volatile |
| // field (b/140507091). |
| Object foo_bar = foo.bar; |
| } catch (NullPointerException e) { |
| continue; |
| } |
| // We should not be here. |
| throw new Error("Expected NullPointerException"); |
| } |
| } |
| |
| // Allocate at least 1 KiB of memory on the managed heap. |
| // Retain some allocated memory and release old allocations so that the |
| // garbage collector has something to do. |
| public static void allocateAtLeast1KiB() { |
| memory[allocationIndex] = new Object[1024 / 4]; |
| ++allocationIndex; |
| if (allocationIndex == memory.length) { |
| allocationIndex = 0; |
| } |
| } |
| |
| public static Object[] memory = new Object[1024]; |
| public static int allocationIndex = 0; |
| |
| private Foo foo; |
| |
| } |