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  * @ignore Fix JDK-8328461
 26  * @test
 27  * @summary Tests that all FieldAccess and FieldModification notifications
 28             are generated for value classes.
 29  * @requires vm.jvmti
 30  * @enablePreview
 31  * @compile FieldAccessModify.java
 32  * @run main/othervm/native -agentlib:FieldAccessModify FieldAccessModify
 33  */
 34 
 35 import java.lang.reflect.Field;
 36 import java.util.Arrays;
 37 
 38 
 39 public class FieldAccessModify {
 40 
 41     private static final String agentLib = "FieldAccessModify";
 42 
 43     private static value class MyPrimitive {
 44         public int MyPrimitive_fld1;
 45         public int MyPrimitive_fld2;
 46 
 47         public MyPrimitive(int v1, int v2) { MyPrimitive_fld1 = v1; MyPrimitive_fld2 = v2; }
 48 
 49         public static MyPrimitive create(int v1, int v2) {
 50             return new MyPrimitive(v1, v2);
 51         }
 52 
 53         public String toString() {
 54             return "MyPrimitive { fld1=" + MyPrimitive_fld1 + ", fld2=" + MyPrimitive_fld2 + "}";
 55         }
 56 
 57     }
 58 
 59     private static class InstanceHolder {
 60         public final MyPrimitive InstanceHolder_fld1;
 61 
 62         public InstanceHolder(int v) {
 63             InstanceHolder_fld1 = MyPrimitive.create(v, v + 100);
 64         }
 65 
 66         public String toString() {
 67             return "InstanceHolder { fld1 is " + InstanceHolder_fld1 + "}";
 68         }
 69     }
 70 
 71     private static value class PrimitiveHolder {
 72         public MyPrimitive PrimitiveHolder_fld1;
 73 
 74         public PrimitiveHolder(int v) {
 75             PrimitiveHolder_fld1 = MyPrimitive.create(v, v + 200);
 76         }
 77 
 78         public String toString() {
 79             return "PrimitiveHolder { fld1 is " + PrimitiveHolder_fld1 + "}";
 80         }
 81     }
 82 
 83     private static class TestHolder {
 84         public MyPrimitive primitiveObj = MyPrimitive.create(1, 1);
 85         public InstanceHolder instanceHolderObj = new InstanceHolder(1);
 86         public PrimitiveHolder primitiveHolderObj = new PrimitiveHolder(1);
 87     }
 88 
 89     public static void main(String[] args) throws Exception {
 90         try {
 91             System.loadLibrary(agentLib);
 92         } catch (UnsatisfiedLinkError ex) {
 93             System.err.println("Failed to load " + agentLib + " lib");
 94             System.err.println("java.library.path: " + System.getProperty("java.library.path"));
 95             throw ex;
 96         }
 97 
 98         // create objects for access testing before setting watchers
 99         TestHolder testHolder = new TestHolder();
100 
101         if (!initWatchers(MyPrimitive.class, MyPrimitive.class.getDeclaredField("MyPrimitive_fld1"))) {
102             throw new RuntimeException("Watchers initializations error (MyPrimitive_fld1)");
103         }
104         if (!initWatchers(MyPrimitive.class, MyPrimitive.class.getDeclaredField("MyPrimitive_fld2"))) {
105             throw new RuntimeException("Watchers initializations error (MyPrimitive_fld2)");
106         }
107         if (!initWatchers(InstanceHolder.class, InstanceHolder.class.getDeclaredField("InstanceHolder_fld1"))) {
108             throw new RuntimeException("Watchers initializations error (InstanceHolder_fld1)");
109         }
110         if (!initWatchers(PrimitiveHolder.class, PrimitiveHolder.class.getDeclaredField("PrimitiveHolder_fld1"))) {
111             throw new RuntimeException("Watchers initializations error (PrimitiveHolder_fld1)");
112         }
113 
114         test("MyPrimitive (access)", () -> {
115                 testHolder.primitiveObj.toString();     // should access both MyPrimitive_fld1 and MyPrimitive_fld2
116             }, new TestResult() {
117                 public boolean MyPrimitive_fld1_access;
118                 public boolean MyPrimitive_fld2_access;
119             });
120 
121         test("InstanceHolder (access)", () ->  {
122                 testHolder.instanceHolderObj.toString();
123             }, new TestResult() {
124                 public boolean InstanceHolder_fld1_access;
125                 // MyPrimitive fields should be accessed too
126                 public boolean MyPrimitive_fld1_access;
127                 public boolean MyPrimitive_fld2_access;
128             });
129 
130         test("PrimitiveHolder (access)", () ->  {
131                 testHolder.primitiveHolderObj.toString();
132             }, new TestResult() {
133                 public boolean PrimitiveHolder_fld1_access;
134                 // MyPrimitive fields should be accessed too
135                 public boolean MyPrimitive_fld1_access;
136                 public boolean MyPrimitive_fld2_access;
137             });
138 
139         test("MyPrimitive (modify)", () ->  {
140                 MyPrimitive obj = MyPrimitive.create(1, 1);
141             }, new TestResult() {
142                 // KNOWN_ISSUE public boolean MyPrimitive_fld1_modify;
143                 // KNOWN_ISSUE public boolean MyPrimitive_fld2_modify;
144             });
145 
146         test("InstanceHolder (modify)", () ->  {
147                 InstanceHolder obj = new InstanceHolder(10);
148             }, new TestResult() {
149                 public boolean InstanceHolder_fld1_modify;
150                 // MyPrimitive fields should be modified too
151                 // KNOWN_ISSUE public boolean MyPrimitive_fld1_modify;
152                 // KNOWN_ISSUE public boolean MyPrimitive_fld2_modify;
153             });
154 
155         test("PrimitiveHolder (modify)", () ->  {
156                 PrimitiveHolder obj = new PrimitiveHolder(11);
157             }, new TestResult() {
158                 // KNOWN_ISSUE public boolean PrimitiveHolder_fld1_modify;
159                 // MyPrimitive fields should be modified too
160                 // KNOWN_ISSUE public boolean MyPrimitive_fld1_modify;
161                 // KNOWN_ISSUE public boolean MyPrimitive_fld2_modify;
162             });
163 
164     }
165 
166     private static void log(String msg) {
167         System.out.println(msg);
168         System.out.flush();
169     }
170 
171     private static void assertTrue(boolean value) {
172         if (!value) {
173             throw new RuntimeException("assert");
174         }
175     }
176 
177     // For every access/modify notification native part tries to locate
178     // boolean "<field_name>_access"/"<field_name>_modify" field and sets it to true
179     private static class TestResult {
180 
181         // verify that all fields are set to true
182         public void verify() {
183             Arrays.stream(this.getClass().getDeclaredFields()).forEach(f -> verify(f));
184         }
185 
186         private void verify(Field f) {
187             try {
188                 if (!f.getBoolean(this)) {
189                     throw new RuntimeException(f.getName() + " notification is missed");
190                 }
191                 log("  - " + f.getName() + ": OK");
192             } catch (IllegalAccessException ex) {
193                 throw new RuntimeException(ex);
194             }
195         }
196     }
197 
198     @FunctionalInterface
199     private interface TestAction {
200         void apply();
201     }
202 
203     private static void test(String descr, TestAction action, TestResult result) throws Exception {
204         log(descr + ": starting");
205         if (!startTest(result)) {
206             throw new RuntimeException("startTest failed");
207         }
208         action.apply();
209         // wait some time to ensure all posted events are handled
210         Thread.sleep(500);
211 
212         stopTest();
213 
214         // check the results
215         result.verify();
216 
217         log(descr + ": OK");
218         log("");
219     }
220 
221     private static native boolean initWatchers(Class cls, Field field);
222     private static native boolean startTest(TestResult results);
223     private static native void stopTest();
224 
225 }