1 /*
  2  * Copyright (c) 2020, 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 import java.lang.invoke.MethodHandles;
 25 import java.lang.reflect.Method;
 26 import java.nio.file.Path;
 27 import java.util.function.BiConsumer;
 28 
 29 import jdk.incubator.foreign.MemoryLayout;
 30 import jdk.incubator.foreign.MemorySegment;
 31 import jdk.incubator.foreign.ResourceScope;
 32 import org.testng.annotations.Test;
 33 
 34 import static jdk.incubator.foreign.MemoryLayout.PathElement.groupElement;
 35 import static org.testng.Assert.assertEquals;
 36 import static org.testng.Assert.assertNotNull;
 37 import static org.testng.Assert.fail;
 38 
 39 /*
 40  * @test
 41  * @modules jdk.incubator.jextract
 42  * @library /test/lib
 43  * @build JextractToolRunner
 44  * @bug 8244512 8252759
 45  * @summary test nested structs and unions
 46  * @run testng/othervm --enable-native-access=jdk.incubator.jextract TestNested
 47  */
 48 public class TestNested extends JextractToolRunner {
 49     @Test
 50     public void testNestedStructs() {
 51         Path nestedOutput = getOutputFilePath("nestedgen");
 52         Path nestedH = getInputFilePath("nested.h");
 53         run("-d", nestedOutput.toString(), nestedH.toString()).checkSuccess();
 54         try(Loader loader = classLoader(nestedOutput)) {
 55             checkClass(loader, "Foo",
 56                 checkField("bar", MemorySegment.class, 0),
 57                 checkField("color", int.class, 8)
 58             );
 59             checkClass(loader, "Foo$Bar",
 60                 checkField("x", int.class, 0),
 61                 checkField("y", int.class, 4)
 62             );
 63             checkClass(loader, "U",
 64                 checkField("point", MemorySegment.class, 0),
 65                 checkField("rgb", int.class, 0),
 66                 checkField("i", int.class, 0)
 67             );
 68             checkClass(loader, "U$Point",
 69                 checkField("x", short.class, 0),
 70                 checkField("y", short.class, 2)
 71             );
 72             checkClass(loader, "MyStruct",
 73                 checkField("a", byte.class, 0),
 74 
 75                 checkField("b", int.class, 4, "$anon$0"),
 76                 checkField("c", int.class, 8, "$anon$0", "$anon$0"),
 77 
 78                 checkField("d", byte.class, 12, "$anon$0"),
 79                 checkField("f", MemorySegment.class, 13, "$anon$0"),
 80 
 81                 checkField("g", int.class, 16, "$anon$1"),
 82                 checkField("h", long.class, 16, "$anon$1"),
 83 
 84                 checkField("k", MemorySegment.class, 24)
 85             );
 86             checkClass(loader, "MyStruct$MyStruct_Z",
 87                 checkField("e", byte.class, 0)
 88             );
 89             checkClass(loader, "MyStruct$k",
 90                 checkField("i", int.class, 0),
 91                 checkField("j", int.class, 4)
 92             );
 93             checkClass(loader, "MyUnion",
 94                 checkField("a", byte.class, 0),
 95 
 96                 checkField("b", int.class, 0, "$anon$0"),
 97                 checkField("c", int.class, 4, "$anon$0", "$anon$0"),
 98 
 99                 checkField("d", byte.class, 8, "$anon$0"),
100                 checkField("f", MemorySegment.class, 9, "$anon$0"),
101 
102                 checkField("g", int.class, 0, "$anon$1"),
103                 checkField("h", int.class, 4, "$anon$1"),
104 
105                 checkField("k", MemorySegment.class, 0)
106             );
107             checkClass(loader, "MyUnion$MyUnion_Z",
108                 checkField("e", byte.class, 0)
109             );
110             checkClass(loader, "MyUnion$k",
111                 checkField("i", int.class, 0),
112                 checkField("j", long.class, 0)
113             );
114             checkClass(loader, "X",
115                 checkField("Z", MemorySegment.class, 0, "$anon$0")
116             );
117             checkClass(loader, "X$Z",
118                 checkField("y", int.class, 0)
119             );
120             checkClass(loader, "X2",
121                 checkField("y", int.class, 0, "$anon$0", "$anon$0")
122             );
123             checkClass(loader, "NestedUnion",
124                 checkField("x", int.class, 0),
125                 checkField("y", int.class, 4, "$anon$0"),
126                 checkField("z", int.class, 4, "$anon$0")
127             );
128         } finally {
129             deleteDir(nestedOutput);
130         }
131     }
132 
133     @SafeVarargs
134     private static void checkClass(Loader loader, String name, BiConsumer<Class<?>, MemoryLayout>... checks) {
135         Class<?> cls = loader.loadClass(name);
136         assertNotNull(cls);
137         MemoryLayout layout = findLayout(cls);
138         for (var check : checks) {
139             check.accept(cls, layout);
140         }
141     }
142 
143     private static BiConsumer<Class<?>, MemoryLayout> checkField(String fieldName, Class<?> fieldType,
144                                                                  long expectedOffset, String... fieldPath) {
145         MemoryLayout.PathElement[] path = new MemoryLayout.PathElement[fieldPath.length + 1];
146         int i = 0;
147         for (; i < fieldPath.length; i++) {
148             path[i] = groupElement(fieldPath[i]);
149         }
150         path[i] = groupElement(fieldName);
151         return (cls, layout) -> {
152             assertEquals(layout.byteOffset(path), expectedOffset);
153             checkAccessors(cls, layout, fieldName, fieldType, layout.select(path));
154         };
155     }
156 
157     private static void checkAccessors(Class<?> cls, MemoryLayout layout, String fieldName, Class<?> type,
158                                        MemoryLayout fieldLayout) {
159         try {
160             if (type == MemorySegment.class) {
161                 Method slicer = cls.getMethod(fieldName + "$slice", MemorySegment.class);
162                 assertEquals(slicer.getReturnType(), MemorySegment.class);
163                 try (ResourceScope scope = ResourceScope.newConfinedScope()) {
164                     MemorySegment struct = MemorySegment.allocateNative(layout, scope);
165                     MemorySegment slice = (MemorySegment) slicer.invoke(null, struct);
166                     assertEquals(slice.byteSize(), fieldLayout.byteSize());
167                 }
168             } else {
169                 Method getter = cls.getMethod(fieldName + "$get", MemorySegment.class);
170                 assertEquals(getter.getReturnType(), type);
171                 Method setter = cls.getMethod(fieldName + "$set", MemorySegment.class, type);
172                 assertEquals(setter.getReturnType(), void.class);
173 
174                 Object zero = MethodHandles.zero(type).invoke();
175                 try (ResourceScope scope = ResourceScope.newConfinedScope()) {
176                     MemorySegment struct = MemorySegment.allocateNative(layout, scope);
177                     setter.invoke(null, struct, zero);
178                     Object actual = getter.invoke(null, struct);
179                     assertEquals(actual, zero);
180                 }
181             }
182         } catch (Throwable t) {
183             fail("Unexpected exception", t);
184         }
185     }
186 }