/* * 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: <> InstanceFieldGet [{{l\d+}}] field_name:Main.foo field_type:Reference loop:<> /// CHECK: NullCheck [<>] dex_pc:<> loop:<> /// CHECK-NEXT: InstanceFieldGet [<>] dex_pc:<> field_name:Foo.bar field_type:Reference loop:<> /// CHECK-NEXT: add w<>, {{w\d+}}, #0x8 (8) /// CHECK-NEXT: adr lr, #+0x{{c|10}} // 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<>] 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; }