1 /*
  2  * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /**
 25  * @test
 26  * @summary Tests that all FieldAccess and FieldModification notifications
 27             are generated for value classes.
 28  * @requires vm.jvmti
 29  * @enablePreview
 30  * @run main/othervm/native -agentlib:FieldAccessModify -XX:+EnableValhalla FieldAccessModify
 31  */
 32 
 33 import java.lang.reflect.Field;
 34 import java.util.Arrays;
 35 
 36 public class FieldAccessModify {
 37 
 38     private static final String agentLib = "FieldAccessModify";
 39 
 40     private static value class ValueClass {
 41         public int valueClass_fld1;
 42         public int valueClass_fld2;
 43 
 44         public ValueClass(int v1, int v2) { valueClass_fld1 = v1; valueClass_fld2 = v2; }
 45 
 46         public String toString() {
 47             return "ValueClass { fld1=" + valueClass_fld1 + ", fld2=" + valueClass_fld2 + "}";
 48         }
 49 
 50     }
 51 
 52     private static class InstanceHolder {
 53         public final ValueClass instanceHolder_fld1;
 54 
 55         public InstanceHolder(int v) {
 56             instanceHolder_fld1 = new ValueClass(v, v + 100);
 57         }
 58 
 59         public String toString() {
 60             return "InstanceHolder { fld1 is " + instanceHolder_fld1 + "}";
 61         }
 62     }
 63 
 64     private static value class ValueHolder {
 65         public ValueClass valueHolder_fld1;
 66 
 67         public ValueHolder(int v) {
 68             valueHolder_fld1 = new ValueClass(v, v + 200);
 69         }
 70 
 71         public String toString() {
 72             return "ValueHolder { fld1 is " + valueHolder_fld1 + "}";
 73         }
 74     }
 75 
 76     private static class TestHolder {
 77         public ValueClass valueObj = new ValueClass(1, 1);
 78         public InstanceHolder instanceHolderObj = new InstanceHolder(1);
 79         public ValueHolder valueHolderObj = new ValueHolder(1);
 80     }
 81 
 82     public static void main(String[] args) throws Exception {
 83         try {
 84             System.loadLibrary(agentLib);
 85         } catch (UnsatisfiedLinkError ex) {
 86             System.err.println("Failed to load " + agentLib + " lib");
 87             System.err.println("java.library.path: " + System.getProperty("java.library.path"));
 88             throw ex;
 89         }
 90 
 91         // create objects for access testing before setting watchers
 92         TestHolder testHolder = new TestHolder();
 93 
 94         if (!initWatchers(ValueClass.class, ValueClass.class.getDeclaredField("valueClass_fld1"))) {
 95             throw new RuntimeException("Watchers initializations error (valueClass_fld1)");
 96         }
 97         if (!initWatchers(ValueClass.class, ValueClass.class.getDeclaredField("valueClass_fld2"))) {
 98             throw new RuntimeException("Watchers initializations error (valueClass_fld2)");
 99         }
100         if (!initWatchers(InstanceHolder.class, InstanceHolder.class.getDeclaredField("instanceHolder_fld1"))) {
101             throw new RuntimeException("Watchers initializations error (instanceHolder_fld1)");
102         }
103         if (!initWatchers(ValueHolder.class, ValueHolder.class.getDeclaredField("valueHolder_fld1"))) {
104             throw new RuntimeException("Watchers initializations error (valueHolder_fld1)");
105         }
106 
107         test("ValueClass (access)", () -> {
108                 testHolder.valueObj.toString();     // should access both valueClass_fld1 and valueClass_fld2
109             }, new TestResult() {
110                 public boolean valueClass_fld1_access;
111                 public boolean valueClass_fld2_access;
112             });
113 
114         test("InstanceHolder (access)", () ->  {
115                 testHolder.instanceHolderObj.toString();
116             }, new TestResult() {
117                 public boolean instanceHolder_fld1_access;
118                 // ValueClass fields should be accessed too
119                 public boolean valueClass_fld1_access;
120                 public boolean valueClass_fld2_access;
121             });
122 
123         test("ValueHolder (access)", () ->  {
124                 testHolder.valueHolderObj.toString();
125             }, new TestResult() {
126                 public boolean valueHolder_fld1_access;
127                 // ValueClass fields should be accessed too
128                 public boolean valueClass_fld1_access;
129                 public boolean valueClass_fld2_access;
130             });
131 
132         test("ValueClass (modify)", () ->  {
133                 ValueClass obj = new ValueClass(1, 1);
134             }, new TestResult() {
135                 public boolean valueClass_fld1_modify;
136                 public boolean valueClass_fld2_modify;
137             });
138 
139         test("InstanceHolder (modify)", () ->  {
140                 InstanceHolder obj = new InstanceHolder(10);
141             }, new TestResult() {
142                 public boolean instanceHolder_fld1_modify;
143                 // ValueClass fields should be modified too
144                 boolean valueClass_fld1_modify;
145                 public boolean valueClass_fld2_modify;
146             });
147 
148         test("ValueHolder (modify)", () ->  {
149                 ValueHolder obj = new ValueHolder(11);
150             }, new TestResult() {
151                 public boolean valueHolder_fld1_modify;
152                 // ValueClass fields should be modified too
153                 public boolean valueClass_fld1_modify;
154                 public boolean valueClass_fld2_modify;
155             });
156 
157     }
158 
159     private static void log(String msg) {
160         System.out.println(msg);
161         System.out.flush();
162     }
163 
164     private static void assertTrue(boolean value) {
165         if (!value) {
166             throw new RuntimeException("assert");
167         }
168     }
169 
170     // For every access/modify notification native part tries to locate
171     // boolean "<field_name>_access"/"<field_name>_modify" field and sets it to true
172     private static class TestResult {
173 
174         // verify that all fields are set to true
175         public void verify() {
176             Arrays.stream(this.getClass().getDeclaredFields()).forEach(f -> verify(f));
177         }
178 
179         private void verify(Field f) {
180             try {
181                 if (!f.getBoolean(this)) {
182                     throw new RuntimeException(f.getName() + " notification is missed");
183                 }
184                 log("  - " + f.getName() + ": OK");
185             } catch (IllegalAccessException ex) {
186                 throw new RuntimeException(ex);
187             }
188         }
189     }
190 
191     @FunctionalInterface
192     private interface TestAction {
193         void apply();
194     }
195 
196     private static void test(String descr, TestAction action, TestResult result) throws Exception {
197         log(descr + ": starting");
198         if (!startTest(result)) {
199             throw new RuntimeException("startTest failed");
200         }
201         action.apply();
202         // wait some time to ensure all posted events are handled
203         Thread.sleep(500);
204 
205         stopTest();
206 
207         // check the results
208         result.verify();
209 
210         log(descr + ": OK");
211         log("");
212     }
213 
214     private static native boolean initWatchers(Class cls, Field field);
215     private static native boolean startTest(TestResult results);
216     private static native void stopTest();
217 
218 }