diff --git a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/JextractTaskImpl.java b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/JextractTaskImpl.java index 46c6ff64d76..aaafc35643c 100644 --- a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/JextractTaskImpl.java +++ b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/JextractTaskImpl.java @@ -42,6 +42,7 @@ public class JextractTaskImpl implements JextractTask { private final boolean compileSources; private final List<Path> headers; + static final boolean VERBOSE = Boolean.getBoolean("jextract.verbose"); public JextractTaskImpl(boolean compileSources, Path... headers) { this.compileSources = compileSources; diff --git a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/MacroParserImpl.java b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/MacroParserImpl.java index c5539b77266..80a1929b37d 100644 --- a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/MacroParserImpl.java +++ b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/MacroParserImpl.java @@ -81,16 +81,24 @@ Optional<Macro> eval(String macroName, String... tokens) { Optional.of((Macro)result) : Optional.empty(); } catch (Throwable ex) { + // This ate the NPE and cause skip of macros + // Why are we expecting exception here? Simply be defensive? + if (JextractTaskImpl.VERBOSE) { + System.err.println("Failed to handle macro " + macroName); + ex.printStackTrace(System.err); + } return Optional.empty(); } } MacroResult reparse(String snippet) { - return reparser.reparse(snippet) + MacroResult rv = reparser.reparse(snippet) .filter(c -> c.kind() == CursorKind.VarDecl && c.spelling().contains("jextract$")) .map(c -> compute(c)) .findAny().get(); + typeMaker.resolveTypeReferences(); + return rv; } private Integer toNumber(String str) { diff --git a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/Parser.java b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/Parser.java index b7b4999f392..7c168f4a6cd 100644 --- a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/Parser.java +++ b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/Parser.java @@ -103,7 +103,10 @@ public Declaration.Scoped parse(Path path, Collection<String> args) { } }); - return treeMaker.createHeader(tuCursor, decls); + Declaration.Scoped rv = treeMaker.createHeader(tuCursor, decls); + treeMaker.freeze(); + index.close(); + return rv; } private boolean isMacro(Cursor c) { diff --git a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/PrettyPrinter.java b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/PrettyPrinter.java index 9aafde2b156..dc0ab93aea8 100644 --- a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/PrettyPrinter.java +++ b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/PrettyPrinter.java @@ -28,6 +28,7 @@ import jdk.incubator.foreign.MemoryLayout; import jdk.incubator.jextract.Declaration; +import jdk.incubator.jextract.Position; import jdk.incubator.jextract.Type; import java.util.stream.Collectors; @@ -137,4 +138,14 @@ public String visitType(Type t, Void aVoid) { return "Unknown type: " + t.getClass().getName(); } }; + + public static String type(Type type) { + return type.accept(typeVisitor, null); + } + + public static String position(Position pos) { + return String.format("%s:%d:%d", + pos.path() == null ? "N/A" : pos.path().toString(), + pos.line(), pos.col()); + } } diff --git a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TreeMaker.java b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TreeMaker.java index 2ced3a1f26d..8aa2fc53a56 100644 --- a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TreeMaker.java +++ b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TreeMaker.java @@ -53,6 +53,10 @@ public TreeMaker() {} TypeMaker typeMaker = new TypeMaker(this); + public void freeze() { + typeMaker.resolveTypeReferences(); + } + private <T extends Declaration> T checkCache(Cursor c, Class<T> clazz, Supplier<Declaration> factory) { // The supplier function may lead to adding some other type, which will cause CME using computeIfAbsent. // This implementation relax the constraint a bit only check for same key @@ -121,24 +125,31 @@ Position toPos(Cursor cursor) { static class CursorPosition implements Position { private final Cursor cursor; + private final Path path; + private final int line; + private final int column; CursorPosition(Cursor cursor) { this.cursor = cursor; + SourceLocation.Location loc = cursor.getSourceLocation().getFileLocation(); + this.path = loc.path(); + this.line = loc.line(); + this.column = loc.column(); } @Override public Path path() { - return cursor.getSourceLocation().getFileLocation().path(); + return path; } @Override public int line() { - return cursor.getSourceLocation().getFileLocation().line(); + return line; } @Override public int col() { - return cursor.getSourceLocation().getFileLocation().column(); + return column; } public Cursor cursor() { diff --git a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeImpl.java b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeImpl.java index 85495c1db72..bee66c9b10b 100644 --- a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeImpl.java +++ b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeImpl.java @@ -26,14 +26,13 @@ package jdk.internal.jextract.impl; -import jdk.incubator.foreign.MemoryLayout; -import jdk.incubator.jextract.Declaration; -import jdk.incubator.jextract.Type; - import java.util.List; import java.util.Optional; import java.util.OptionalLong; import java.util.function.Supplier; +import jdk.incubator.foreign.MemoryLayout; +import jdk.incubator.jextract.Declaration; +import jdk.incubator.jextract.Type; public abstract class TypeImpl implements Type { @@ -137,7 +136,6 @@ public Type type() { } public static class PointerImpl extends DelegatedBase { - private final Supplier<Type> pointeeFactory; public PointerImpl(Supplier<Type> pointeeFactory) { @@ -145,6 +143,10 @@ public PointerImpl(Supplier<Type> pointeeFactory) { this.pointeeFactory = pointeeFactory; } + public PointerImpl(Type pointee) { + this(() -> pointee); + } + @Override public Type type() { return pointeeFactory.get(); @@ -246,4 +248,9 @@ public Kind kind() { return kind; } } + + @Override + public String toString() { + return PrettyPrinter.type(this); + } } diff --git a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeMaker.java b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeMaker.java index e04d91e5667..5aa0953cbe7 100644 --- a/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeMaker.java +++ b/src/jdk.incubator.jextract/share/classes/jdk/internal/jextract/impl/TypeMaker.java @@ -27,23 +27,86 @@ package jdk.internal.jextract.impl; +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; import jdk.incubator.jextract.Declaration; import jdk.incubator.jextract.Type; -import jdk.incubator.jextract.Type.Primitive; import jdk.incubator.jextract.Type.Delegated; - -import java.util.ArrayList; -import java.util.List; +import jdk.incubator.jextract.Type.Primitive; class TypeMaker { TreeMaker treeMaker; + private final Map<jdk.internal.clang.Type, Type> typeCache = new HashMap<>(); + private List<ClangTypeReference> unresolved = new ArrayList<>(); + + private class ClangTypeReference implements Supplier<Type> { + jdk.internal.clang.Type origin; + Type derived; + + private ClangTypeReference(jdk.internal.clang.Type origin) { + this.origin = origin; + derived = typeCache.get(origin); + } + + public boolean isUnresolved() { + return null == derived; + } + + public void resolve() { + derived = makeType(origin); + Objects.requireNonNull(derived, "Clang type cannot be resolved: " + origin.spelling()); + } + + public Type get() { + Objects.requireNonNull(derived, "Type is not yet resolved."); + return derived; + } + } + + private ClangTypeReference reference(jdk.internal.clang.Type type) { + ClangTypeReference ref = new ClangTypeReference(type); + if (ref.isUnresolved()) { + unresolved.add(ref); + } + return ref; + } public TypeMaker(TreeMaker treeMaker) { this.treeMaker = treeMaker; } + /** + * Resolve all type references. This method should be called before discard clang cursors/types + */ + void resolveTypeReferences() { + List<ClangTypeReference> resolving = unresolved; + unresolved = new ArrayList<>(); + while (! resolving.isEmpty()) { + resolving.forEach(ClangTypeReference::resolve); + resolving = unresolved; + unresolved = new ArrayList<>(); + } + } + Type makeType(jdk.internal.clang.Type t) { + Type rv = typeCache.get(t); + if (rv != null) { + return rv; + } + rv = makeTypeInternal(t); + if (null != rv && typeCache.put(t, rv) != null) { + throw new ConcurrentModificationException(); + } + return rv; + } + + Type makeTypeInternal(jdk.internal.clang.Type t) { switch(t.kind()) { case Auto: return makeType(t.canonicalType()); @@ -119,12 +182,16 @@ Type makeType(jdk.internal.clang.Type t) { } case Enum: case Record: { - return Type.declared((Declaration.Scoped)treeMaker.createTree(t.getDeclarationCursor())); + if (treeMaker == null) { + // Macro evaluation, type is meaningless as this can only be pointer and we only care value + return Type.void_(); + } + return Type.declared((Declaration.Scoped) treeMaker.createTree(t.getDeclarationCursor())); } case BlockPointer: case Pointer: { - jdk.internal.clang.Type __type = t.getPointeeType(); - return Type.pointer(() -> makeType(__type)); + // TODO: We can always erase type for macro evaluation, should we? + return new TypeImpl.PointerImpl(reference(t.getPointeeType())); } case Typedef: { Type __type = makeType(t.canonicalType()); @@ -156,7 +223,7 @@ private Type lowerFunctionType(jdk.internal.clang.Type t) { private Type.Visitor<Type, Void> lowerFunctionType = new Type.Visitor<>() { @Override public Type visitArray(Type.Array t, Void aVoid) { - return Type.pointer(() -> t.elementType()); + return Type.pointer(t.elementType()); } @Override diff --git a/test/jdk/java/jextract/JextractApiTestBase.java b/test/jdk/java/jextract/JextractApiTestBase.java new file mode 100644 index 00000000000..e7486912e83 --- /dev/null +++ b/test/jdk/java/jextract/JextractApiTestBase.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.function.Predicate; +import jdk.incubator.jextract.Declaration; +import jdk.incubator.jextract.JextractTask; +import jdk.incubator.jextract.Type; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +public class JextractApiTestBase { + + public static Declaration.Scoped parse(String headerFilename, String... parseOptions) { + Path header = Paths.get(System.getProperty("test.src.path", "."), headerFilename); + var task = JextractTask.newTask(false, header); + Path builtinInc = Paths.get(System.getProperty("java.home"), "conf", "jextract"); + return task.parse(parseOptions); + } + + public static Declaration.Scoped checkStruct(Declaration.Scoped toplevel, String name, String... fields) { + Declaration.Scoped struct = findDecl(toplevel, name, Declaration.Scoped.class); + assertEquals(struct.members().size(), fields.length); + for (int i = 0 ; i < fields.length ; i++) { + assertEquals(struct.members().get(i).name(), fields[i]); + } + return struct; + } + + public static Declaration.Variable checkConstant(Declaration.Scoped scope, String name, Type type) { + Declaration.Variable var = findDecl(scope, name, Declaration.Variable.class); + assertTypeEquals(type, var.type()); + return var; + } + + public static Declaration.Variable checkGlobal(Declaration.Scoped toplevel, String name, Type type) { + Declaration.Variable global = checkConstant(toplevel, name, type); + assertEquals(global.kind(), Declaration.Variable.Kind.GLOBAL); + return global; + } + + public static Declaration.Variable checkField(Declaration.Scoped record, String name, Type type) { + Declaration.Variable global = checkConstant(record, name, type); + assertEquals(global.kind(), Declaration.Variable.Kind.FIELD); + return global; + } + public static Declaration.Function checkFunction(Declaration.Scoped toplevel, String name, Type ret, Type... params) { + Declaration.Function function = findDecl(toplevel, name, Declaration.Function.class); + assertTypeEquals(ret, function.type().returnType()); + assertEquals(function.parameters().size(), params.length); + for (int i = 0 ; i < params.length ; i++) { + assertTypeEquals(params[i], function.type().argumentTypes().get(i)); + Type paramType = function.parameters().get(i).type(); + if (paramType instanceof Type.Array) { + assertTypeEquals(params[i], Type.pointer(((Type.Array) paramType).elementType())); + } else { + assertTypeEquals(params[i], function.parameters().get(i).type()); + } + } + return function; + } + + public static Declaration.Constant checkConstant(Declaration.Scoped toplevel, String name, Type type, Object value) { + Declaration.Constant constant = findDecl(toplevel, name, Declaration.Constant.class); + assertTypeEquals(type, constant.type()); + assertEquals(value, constant.value()); + return constant; + } + + public static Predicate<Declaration> byName(final String name) { + return d -> d.name().equals(name); + } + + public static Predicate<Declaration> byNameAndType(final String name, Class<? extends Declaration> declType) { + return d -> declType.isAssignableFrom(d.getClass()) && d.name().equals(name); + } + + public static Optional<Declaration> findDecl(Declaration.Scoped toplevel, Predicate<Declaration> filter) { + return toplevel.members().stream().filter(filter).findAny(); + } + + @SuppressWarnings("unchecked") + public static <D extends Declaration> D findDecl(Declaration.Scoped toplevel, String name, Class<D> declType) { + Optional<Declaration> d = findDecl(toplevel, byNameAndType(name, declType)); + if (d.isEmpty()) { + fail("No declaration with name " + name + " found in " + toplevel); + return null; + } + return (D) d.get(); + } + + public static void assertTypeEquals(Type expected, Type found) { + assertEquals(expected.getClass(), found.getClass()); + if (expected instanceof Type.Primitive) { + assertEquals(((Type.Primitive)expected).kind(), ((Type.Primitive)found).kind()); + assertEquals(((Type.Primitive)expected).layout(), ((Type.Primitive)found).layout()); + } else if (expected instanceof Type.Delegated) { + assertEquals(((Type.Delegated)expected).kind(), ((Type.Delegated)found).kind()); + assertTypeEquals(((Type.Delegated)expected).type(), ((Type.Delegated)found).type()); + } else if (expected instanceof Type.Array) { + assertEquals(((Type.Array)expected).kind(), ((Type.Array)found).kind()); + assertEquals(((Type.Array)expected).elementCount(), ((Type.Array)found).elementCount()); + assertTypeEquals(((Type.Array)expected).elementType(), ((Type.Array)found).elementType()); + } else if (expected instanceof Type.Declared) { + assertEquals(((Type.Declared)expected).tree(), ((Type.Declared)found).tree()); + } else if (expected instanceof Type.Function) { + assertTypeEquals(((Type.Function)expected).returnType(), ((Type.Function)found).returnType()); + assertEquals(((Type.Function)expected).argumentTypes().size(), ((Type.Function)found).argumentTypes().size()); + assertEquals(((Type.Function)expected).varargs(), ((Type.Function)found).varargs()); + for (int i = 0 ; i < ((Type.Function)expected).argumentTypes().size() ; i++) { + assertTypeEquals(((Type.Function)expected).argumentTypes().get(i), ((Type.Function)found).argumentTypes().get(i)); + } + } + } +} diff --git a/test/jdk/java/jextract/SmokeTest.java b/test/jdk/java/jextract/SmokeTest.java index a8261ac8f6a..06dbeb992a3 100644 --- a/test/jdk/java/jextract/SmokeTest.java +++ b/test/jdk/java/jextract/SmokeTest.java @@ -1,54 +1,44 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. * */ /* * @test + * @build JextractApiTestBase * @run testng SmokeTest */ -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; -import java.util.function.Predicate; import jdk.incubator.jextract.Declaration; -import jdk.incubator.jextract.JextractTask; import jdk.incubator.jextract.Type; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -public class SmokeTest { +public class SmokeTest extends JextractApiTestBase { @Test public void testParser() { - Path header = Paths.get(System.getProperty("test.src.path", "."), "smoke.h"); - var task = JextractTask.newTask(false, header); - Declaration.Scoped d = task.parse(""); + Declaration.Scoped d = parse("smoke.h"); Declaration.Scoped pointDecl = checkStruct(d, "Point", "x", "y"); Type intType = ((Declaration.Variable)pointDecl.members().get(0)).type(); checkGlobal(d, "p", Type.declared(pointDecl)); @@ -57,110 +47,4 @@ public void testParser() { checkFunction(d, "pointers", ch_ptr_ptr.type(), ch_ptr_ptr.type(), ch_ptr_ptr.type()); checkConstant(d, "ZERO", intType, 0L); } - - @Test - public void test8238712() { - Path header = Paths.get(System.getProperty("test.src.path", "."), "Test8238712.h"); - var task = JextractTask.newTask(false, header); - Declaration.Scoped d = task.parse(); - Declaration.Scoped structFoo = checkStruct(d, "foo", "n", "ptr"); - Type intType = ((Declaration.Variable) structFoo.members().get(0)).type(); - Type fooType = Type.declared(structFoo); - checkFunction(d, "withRecordTypeArg", intType, intType, fooType); - checkFunction(d, "returnRecordType", fooType); - // Opaque struct, have no field - Declaration.Scoped structBar = checkStruct(d, "bar"); - assertTrue(structBar.layout().isEmpty()); - Type barType = Type.declared(structBar); - // Function with opaque struct won't work but should have cursor for tool to handle - checkFunction(d, "returnBar", barType); - checkFunction(d, "withBar", Type.void_(), barType); - // Function use pointer to opaque struct should be OK - Type barPointer = Type.pointer(barType); - checkFunction(d, "nextBar", barPointer, barPointer); - } - - Declaration.Scoped checkStruct(Declaration.Scoped toplevel, String name, String... fields) { - Declaration.Scoped struct = findDecl(toplevel, name, Declaration.Scoped.class); - assertEquals(struct.members().size(), fields.length); - for (int i = 0 ; i < fields.length ; i++) { - assertEquals(struct.members().get(i).name(), fields[i]); - } - return struct; - } - - Declaration.Variable checkGlobal(Declaration.Scoped toplevel, String name, Type type) { - Declaration.Variable global = findDecl(toplevel, name, Declaration.Variable.class); - assertTypeEquals(type, global.type()); - return global; - } - - Declaration.Function checkFunction(Declaration.Scoped toplevel, String name, Type ret, Type... params) { - Declaration.Function function = findDecl(toplevel, name, Declaration.Function.class); - assertTypeEquals(ret, function.type().returnType()); - assertEquals(function.parameters().size(), params.length); - for (int i = 0 ; i < params.length ; i++) { - assertTypeEquals(params[i], function.type().argumentTypes().get(i)); - Type paramType = function.parameters().get(i).type(); - if (paramType instanceof Type.Array) { - assertTypeEquals(params[i], Type.pointer(((Type.Array) paramType).elementType())); - } else { - assertTypeEquals(params[i], function.parameters().get(i).type()); - } - } - return function; - } - - Declaration.Constant checkConstant(Declaration.Scoped toplevel, String name, Type type, Object value) { - Declaration.Constant constant = findDecl(toplevel, name, Declaration.Constant.class); - assertTypeEquals(type, constant.type()); - assertEquals(value, constant.value()); - return constant; - } - - Predicate<Declaration> byName(final String name) { - return d -> d.name().equals(name); - } - - Predicate<Declaration> byNameAndType(final String name, Class<? extends Declaration> declType) { - return d -> declType.isAssignableFrom(d.getClass()) && d.name().equals(name); - } - - Optional<Declaration> findDecl(Declaration.Scoped toplevel, Predicate<Declaration> filter) { - return toplevel.members().stream().filter(filter).findAny(); - } - - @SuppressWarnings("unchecked") - <D extends Declaration> D findDecl(Declaration.Scoped toplevel, String name, Class<D> declType) { - Optional<Declaration> d = findDecl(toplevel, byNameAndType(name, declType)); - if (d.isEmpty()) { - fail("No declaration with name " + name + " found in " + toplevel); - return null; - } - return (D) d.get(); - } - - void assertTypeEquals(Type expected, Type found) { - assertEquals(expected.getClass(), found.getClass()); - if (expected instanceof Type.Primitive) { - assertEquals(((Type.Primitive)expected).kind(), ((Type.Primitive)found).kind()); - assertEquals(((Type.Primitive)expected).layout(), ((Type.Primitive)found).layout()); - } else if (expected instanceof Type.Delegated) { - assertEquals(((Type.Delegated)expected).kind(), ((Type.Delegated)found).kind()); - assertTypeEquals(((Type.Delegated)expected).type(), ((Type.Delegated)found).type()); - } else if (expected instanceof Type.Array) { - assertEquals(((Type.Array)expected).kind(), ((Type.Array)found).kind()); - assertEquals(((Type.Array)expected).elementCount(), ((Type.Array)found).elementCount()); - assertTypeEquals(((Type.Array)expected).elementType(), ((Type.Array)found).elementType()); - } else if (expected instanceof Type.Declared) { - assertEquals(((Type.Declared)expected).tree(), ((Type.Declared)found).tree()); - } else if (expected instanceof Type.Function) { - assertTypeEquals(((Type.Function)expected).returnType(), ((Type.Function)found).returnType()); - assertEquals(((Type.Function)expected).argumentTypes().size(), ((Type.Function)found).argumentTypes().size()); - assertEquals(((Type.Function)expected).varargs(), ((Type.Function)found).varargs()); - for (int i = 0 ; i < ((Type.Function)expected).argumentTypes().size() ; i++) { - assertTypeEquals(((Type.Function)expected).argumentTypes().get(i), ((Type.Function)found).argumentTypes().get(i)); - } - } - } } diff --git a/test/jdk/java/jextract/Test8238712.java b/test/jdk/java/jextract/Test8238712.java new file mode 100644 index 00000000000..28e28a54fbd --- /dev/null +++ b/test/jdk/java/jextract/Test8238712.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @build JextractApiTestBase + * @run testng Test8238712 + */ + +import jdk.incubator.jextract.Declaration; +import jdk.incubator.jextract.Type; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertTrue; + +public class Test8238712 extends JextractApiTestBase { + @Test + public void test8238712() { + Declaration.Scoped d = parse("Test8238712.h"); + Declaration.Scoped structFoo = checkStruct(d, "foo", "n", "ptr"); + Type intType = ((Declaration.Variable) structFoo.members().get(0)).type(); + Type fooType = Type.declared(structFoo); + checkFunction(d, "withRecordTypeArg", intType, intType, fooType); + checkFunction(d, "returnRecordType", fooType); + // Opaque struct, have no field + Declaration.Scoped structBar = checkStruct(d, "bar"); + assertTrue(structBar.layout().isEmpty()); + Type barType = Type.declared(structBar); + // Function with opaque struct won't work but should have cursor for tool to handle + checkFunction(d, "returnBar", barType); + checkFunction(d, "withBar", Type.void_(), barType); + // Function use pointer to opaque struct should be OK + Type barPointer = Type.pointer(barType); + checkFunction(d, "nextBar", barPointer, barPointer); + } +} diff --git a/test/jdk/java/jextract/TestMacros.java b/test/jdk/java/jextract/TestMacros.java new file mode 100644 index 00000000000..cbc9be02419 --- /dev/null +++ b/test/jdk/java/jextract/TestMacros.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @bug 8239128 + * @build JextractApiTestBase + * @run testng TestMacros + */ + +import java.nio.file.Path; +import java.nio.file.Paths; +import jdk.incubator.foreign.MemoryLayouts; +import jdk.incubator.jextract.Declaration; +import jdk.incubator.jextract.Type; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class TestMacros extends JextractApiTestBase { + Declaration.Scoped badMacro; + Declaration.Scoped foo; + Declaration.Scoped bar; + private final static Type C_INT = Type.primitive(Type.Primitive.Kind.Int, MemoryLayouts.C_INT); + + @BeforeClass + public void parse() { + // We need stdint.h for pointer macro, otherwise evaluation failed and Constant declaration is not created + Path builtinInc = Paths.get(System.getProperty("java.home"), "conf", "jextract"); + badMacro = parse("badMacros.h", "-I", builtinInc.toString()); + + foo = checkStruct(badMacro, "foo", "ptrFoo", "ptrBar"); + bar = checkStruct(badMacro, "bar", "ptrFoo", "arFooPtr"); + } + + @Test + public void testBadMacros() { + checkConstant(badMacro, "INVALID_INT_CONSUMER", + Type.pointer(Type.function(false, Type.void_(), C_INT)), + 0L); + // Record type in macro definition are erased to void + checkConstant(badMacro, "NO_FOO", Type.pointer(Type.void_()), 0L); + checkConstant(badMacro, "INVALID_INT_ARRAY_PTR", Type.pointer(Type.pointer(C_INT)), 0L); + } + + @Test + public void verifyFunctions() { + checkFunction(badMacro, "func", Type.void_(), + Type.pointer(Type.declared(bar)), Type.pointer(Type.declared(foo))); + checkFunction(badMacro, "withArray", Type.void_(), + Type.pointer(Type.typedef("foo_t", Type.pointer(Type.declared(foo))))); + } + + @Test + public void verifyGlobals() { + checkGlobal(badMacro, "op", Type.pointer( + Type.function(false, Type.void_(), C_INT, Type.pointer(C_INT)))); + } + + @Test + public void verifyFields() { + Type foo_t = Type.typedef("foo_t", Type.pointer(Type.declared(foo))); + checkField(foo, "ptrFoo", foo_t); + checkField(foo, "ptrBar", Type.pointer(Type.declared(bar))); + checkField(bar, "ptrFoo", foo_t); + checkField(bar, "arFooPtr", Type.pointer(foo_t)); + } +} diff --git a/test/jdk/java/jextract/badMacros.h b/test/jdk/java/jextract/badMacros.h new file mode 100644 index 00000000000..1a67007957c --- /dev/null +++ b/test/jdk/java/jextract/badMacros.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +// Macro of constant function pointer +#define INVALID_INT_CONSUMER (void (*)(int))0 + +struct foo; +typedef struct foo *foo_t; +struct bar; + +// Macro of constant struct pointer +#define NO_FOO ((foo_t)0) + +// Cases where resolving introduce new type references +// Pointer to pointer in macro +#define INVALID_INT_ARRAY_PTR (int**) 0 +// Function pointer with pointer type argument +void (*op)(int cnt, int* operands); +void func(struct bar *pBar, struct foo *pFoo); + +// Cyclic struct pointer within struct definitions +struct foo { + foo_t ptrFoo; + struct bar *ptrBar; +}; + +struct bar { + foo_t ptrFoo; + foo_t *arFooPtr; +}; + +// Function with array to pointer +void withArray(foo_t ar[2]);