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]);