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.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package jdk.internal.jextract.impl;
 26 
 27 import jdk.incubator.foreign.*;
 28 import jdk.incubator.jextract.Declaration;
 29 import jdk.incubator.jextract.JextractTool;
 30 import jdk.incubator.jextract.Type;
 31 
 32 import jdk.internal.jextract.impl.JavaSourceBuilder.VarInfo;
 33 import jdk.internal.jextract.impl.JavaSourceBuilder.FunctionInfo;
 34 
 35 import javax.tools.JavaFileObject;
 36 import java.io.File;
 37 import java.io.IOException;
 38 import java.io.UncheckedIOException;
 39 import java.lang.constant.ClassDesc;
 40 import java.lang.invoke.MethodType;
 41 import java.net.URI;
 42 import java.net.URL;
 43 import java.net.URISyntaxException;
 44 import java.nio.file.Files;
 45 import java.nio.file.Paths;
 46 import java.util.ArrayList;
 47 import java.util.HashMap;
 48 import java.util.HashSet;
 49 import java.util.List;
 50 import java.util.Map;
 51 import java.util.Optional;
 52 import java.util.Set;
 53 import java.util.function.BiFunction;
 54 import java.util.stream.Collectors;
 55 
 56 /*
 57  * Scan a header file and generate Java source items for entities defined in that header
 58  * file. Tree visitor visit methods return true/false depending on whether a
 59  * particular Tree is processed or skipped.
 60  */
 61 public class OutputFactory implements Declaration.Visitor<Void, Declaration> {
 62     // internal symbol used by clang for "va_list"
 63     private static final String VA_LIST_TAG = "__va_list_tag";
 64     private final Set<String> constants = new HashSet<>();
 65     // To detect duplicate Variable and Function declarations.
 66     private final Set<String> variables = new HashSet<>();
 67     private final Set<Declaration.Function> functions = new HashSet<>();
 68 
 69     protected final ToplevelBuilder toplevelBuilder;
 70     protected JavaSourceBuilder currentBuilder;
 71     protected final TypeTranslator typeTranslator = new TypeTranslator();
 72     private final String pkgName;
 73     private final Map<Declaration, String> structClassNames = new HashMap<>();
 74     private final Set<Declaration.Typedef> unresolvedStructTypedefs = new HashSet<>();
 75     private final Map<Type, String> functionTypeDefNames = new HashMap<>();
 76     private final IncludeHelper includeHelper;
 77 
 78     private void addStructDefinition(Declaration decl, String name) {
 79         structClassNames.put(decl, name);
 80     }
 81 
 82     private boolean structDefinitionSeen(Declaration decl) {
 83         return structClassNames.containsKey(decl);
 84     }
 85 
 86     private String structDefinitionName(Declaration decl) {
 87         return structClassNames.get(decl);
 88     }
 89 
 90     private void addFunctionTypedef(Type.Delegated typedef, String name) {
 91         functionTypeDefNames.put(typedef, name);
 92     }
 93 
 94     private boolean functionTypedefSeen(Type.Delegated typedef) {
 95         return functionTypeDefNames.containsKey(typedef);
 96     }
 97 
 98     private String functionTypedefName(Type.Delegated decl) {
 99         return functionTypeDefNames.get(decl);
100     }
101 
102     // have we seen this Variable earlier?
103     protected boolean variableSeen(Declaration.Variable tree) {
104         return !variables.add(tree.name());
105     }
106 
107     // have we seen this Function earlier?
108     protected boolean functionSeen(Declaration.Function tree) {
109         return !functions.add(tree);
110     }
111 
112     public static JavaFileObject[] generateWrapped(Declaration.Scoped decl, String headerName,
113                 String pkgName, IncludeHelper includeHelper, List<String> libraryNames) {
114         String clsName = Utils.javaSafeIdentifier(headerName.replace(".h", "_h"), true);
115         ToplevelBuilder toplevelBuilder = new ToplevelBuilder(pkgName, clsName);
116         return new OutputFactory(pkgName, toplevelBuilder, includeHelper).generate(decl, libraryNames.toArray(new String[0]));
117     }
118 
119     private OutputFactory(String pkgName, ToplevelBuilder toplevelBuilder, IncludeHelper includeHelper) {
120         this.pkgName = pkgName;
121         this.toplevelBuilder = toplevelBuilder;
122         this.currentBuilder = toplevelBuilder;
123         this.includeHelper = includeHelper;
124     }
125 
126     JavaFileObject[] generate(Declaration.Scoped decl, String[] libs) {
127         //generate all decls
128         decl.members().forEach(this::generateDecl);
129         // check if unresolved typedefs can be resolved now!
130         for (Declaration.Typedef td : unresolvedStructTypedefs) {
131             Declaration.Scoped structDef = ((Type.Declared) td.type()).tree();
132             toplevelBuilder.addTypedef(td.name(),
133                     structDefinitionSeen(structDef) ? structDefinitionName(structDef) : null, td.type());
134         }
135         try {
136             List<JavaFileObject> files = new ArrayList<>(toplevelBuilder.toFiles());
137             files.add(jfoFromString(pkgName,"RuntimeHelper", getRuntimeHelperSource(libs)));
138             return files.toArray(new JavaFileObject[0]);
139         } catch (IOException ex) {
140             throw new UncheckedIOException(ex);
141         } catch (URISyntaxException ex2) {
142             throw new RuntimeException(ex2);
143         }
144     }
145 
146     private String getRuntimeHelperSource(String[] libraries) throws URISyntaxException, IOException {
147         URL runtimeHelper = OutputFactory.class.getResource("resources/RuntimeHelper.java.template");
148         String template = (pkgName.isEmpty()? "" : "package " + pkgName + ";\n") +
149                         String.join("\n", Files.readAllLines(Paths.get(runtimeHelper.toURI())));
150         List<String> loadLibrariesStr = new ArrayList<>();
151         for (String lib : libraries) {
152             String quotedLibName = quoteLibraryName(lib);
153             if (quotedLibName.indexOf(File.separatorChar) != -1) {
154                 loadLibrariesStr.add("System.load(\"" + quotedLibName + "\");");
155             } else {
156                 loadLibrariesStr.add("System.loadLibrary(\"" + quotedLibName + "\");");
157             }
158         }
159         return template.replace("#LOAD_LIBRARIES#", loadLibrariesStr.stream().collect(Collectors.joining(" ")));
160     }
161 
162     private String quoteLibraryName(String lib) {
163         return lib.replace("\\", "\\\\"); // double up slashes
164     }
165 
166     private void generateDecl(Declaration tree) {
167         try {
168             tree.accept(this, null);
169         } catch (Exception ex) {
170             ex.printStackTrace();
171         }
172     }
173 
174     private JavaFileObject jfoFromString(String pkgName, String clsName, String contents) {
175         String pkgPrefix = pkgName.isEmpty() ? "" : pkgName.replaceAll("\\.", "/") + "/";
176         return InMemoryJavaCompiler.jfoFromString(URI.create(pkgPrefix + clsName + ".java"), contents);
177     }
178 
179     @Override
180     public Void visitConstant(Declaration.Constant constant, Declaration parent) {
181         if (!constants.add(constant.name()) || !includeHelper.isIncluded(constant)) {
182             //skip
183             return null;
184         }
185 
186         Class<?> clazz = getJavaType(constant.type());
187         if (clazz == null) {
188             warn("skipping " + constant.name() + " because of unsupported type usage");
189             return null;
190         }
191         toplevelBuilder.addConstant(Utils.javaSafeIdentifier(constant.name()),
192                 constant.value() instanceof String ? MemorySegment.class :
193                 typeTranslator.getJavaType(constant.type()), constant.value());
194         return null;
195     }
196 
197     @Override
198     public Void visitScoped(Declaration.Scoped d, Declaration parent) {
199         if (d.layout().isEmpty() || structDefinitionSeen(d)) {
200             //skip decl
201             return null;
202         }
203         boolean isStructKind = switch (d.kind()) {
204             case STRUCT, UNION -> true;
205             default -> false;
206         };
207         StructBuilder structBuilder = null;
208         if (isStructKind) {
209             String className = d.name();
210             if (!className.isEmpty() && !includeHelper.isIncluded(d)) {
211                 return null;
212             }
213             GroupLayout layout = (GroupLayout) layoutFor(d);
214             currentBuilder = structBuilder = currentBuilder.addStruct(className, parent, layout, Type.declared(d));
215             structBuilder.classBegin();
216             if (!className.isEmpty()) {
217                 addStructDefinition(d, structBuilder.fullName());
218             }
219         }
220         try {
221             d.members().forEach(fieldTree -> fieldTree.accept(this, d));
222         } finally {
223             if (isStructKind) {
224                 currentBuilder = structBuilder.classEnd();
225             }
226         }
227         return null;
228     }
229 
230     private String generateFunctionalInterface(Type.Function func, String name) {
231         return functionInfo(func, name, false, FunctionInfo::ofFunctionPointer)
232                 .map(fInfo -> currentBuilder.addFunctionalInterface(Utils.javaSafeIdentifier(name), fInfo))
233                 .orElse(null);
234     }
235 
236     @Override
237     public Void visitFunction(Declaration.Function funcTree, Declaration parent) {
238         if (functionSeen(funcTree) ||
239                 !includeHelper.isIncluded(funcTree)) {
240             return null;
241         }
242 
243         String mhName = Utils.javaSafeIdentifier(funcTree.name());
244         //generate static wrapper for function
245         List<String> paramNames = funcTree.parameters()
246                                           .stream()
247                                           .map(Declaration.Variable::name)
248                                           .map(p -> !p.isEmpty() ? Utils.javaSafeIdentifier(p) : p)
249                                           .collect(Collectors.toList());
250 
251         Optional<FunctionInfo> functionInfo = functionInfo(funcTree.type(), funcTree.name(), true,
252                 (mtype, desc) -> FunctionInfo.ofFunction(mtype, desc, funcTree.type().varargs(), paramNames));
253 
254         if (functionInfo.isPresent()) {
255             int i = 0;
256             for (Declaration.Variable param : funcTree.parameters()) {
257                 Type.Function f = getAsFunctionPointer(param.type());
258                 if (f != null) {
259                     String name = funcTree.name() + "$" + (param.name().isEmpty() ? "x" + i : param.name());
260                     if (generateFunctionalInterface(f, name) == null) {
261                         return null;
262                     }
263                     i++;
264                 }
265             }
266 
267             toplevelBuilder.addFunction(mhName, funcTree.name(), functionInfo.get());
268         }
269 
270         return null;
271     }
272 
273     Optional<String> getAsFunctionPointerTypedef(Type type) {
274         if (type instanceof Type.Delegated delegated &&
275                 delegated.kind() == Type.Delegated.Kind.TYPEDEF &&
276                 functionTypedefSeen(delegated)) {
277             return Optional.of(functionTypedefName(delegated));
278         } else {
279             return Optional.empty();
280         }
281     }
282 
283     Type.Function getAsFunctionPointer(Type type) {
284         if (type instanceof Type.Delegated) {
285             Type.Delegated delegated = (Type.Delegated) type;
286             return (delegated.kind() == Type.Delegated.Kind.POINTER) ?
287                     getAsFunctionPointer(delegated.type()) : null;
288         } else if (type instanceof Type.Function) {
289             /*
290              * // pointer to function declared as function like this
291              *
292              * typedef void CB(int);
293              * void func(CB cb);
294              */
295             return (Type.Function) type;
296         } else {
297             return null;
298         }
299     }
300 
301     @Override
302     public Void visitTypedef(Declaration.Typedef tree, Declaration parent) {
303         if (!includeHelper.isIncluded(tree)) {
304             return null;
305         }
306         Type type = tree.type();
307         if (type instanceof Type.Declared) {
308             Declaration.Scoped s = ((Type.Declared) type).tree();
309             if (!s.name().equals(tree.name())) {
310                 switch (s.kind()) {
311                     case STRUCT, UNION -> {
312                         if (s.name().isEmpty()) {
313                             visitScoped(s, tree);
314                         } else {
315                             /*
316                              * If typedef is seen after the struct/union definition, we can generate subclass
317                              * right away. If not, we've to save it and revisit after all the declarations are
318                              * seen. This is to support forward declaration of typedefs.
319                              *
320                              * typedef struct Foo Bar;
321                              *
322                              * struct Foo {
323                              *     int x, y;
324                              * };
325                              */
326                             if (structDefinitionSeen(s)) {
327                                 toplevelBuilder.addTypedef(tree.name(), structDefinitionName(s), tree.type());
328                             } else {
329                                 /*
330                                  * Definition of typedef'ed struct/union not seen yet. May be the definition comes later.
331                                  * Save it to visit at the end of all declarations.
332                                  */
333                                 unresolvedStructTypedefs.add(tree);
334                             }
335                         }
336                     }
337                     default -> visitScoped(s, tree);
338                 }
339             }
340         } else if (type instanceof Type.Primitive) {
341              toplevelBuilder.addTypedef(tree.name(), null, type);
342         } else {
343             Type.Function func = getAsFunctionPointer(type);
344             if (func != null) {
345                 String funcIntfName = generateFunctionalInterface(func, tree.name());
346                 if (funcIntfName != null) {
347                     addFunctionTypedef(Type.typedef(tree.name(), tree.type()), funcIntfName);
348                 }
349             } else if (((TypeImpl)type).isPointer()) {
350                 toplevelBuilder.addTypedef(tree.name(), null, type);
351             }
352         }
353         return null;
354     }
355 
356     @Override
357     public Void visitVariable(Declaration.Variable tree, Declaration parent) {
358         if (parent == null && (variableSeen(tree) || !includeHelper.isIncluded(tree))) {
359             return null;
360         }
361 
362         String fieldName = tree.name();
363         String symbol = tree.name();
364         assert !symbol.isEmpty();
365         assert !fieldName.isEmpty();
366         fieldName = Utils.javaSafeIdentifier(fieldName);
367 
368         Type type = tree.type();
369 
370         if (type instanceof Type.Declared) {
371             // declared type - visit declaration recursively
372             ((Type.Declared) type).tree().accept(this, tree);
373         }
374         MemoryLayout layout = tree.layout().orElse(Type.layoutFor(type).orElse(null));
375         if (layout == null) {
376             //no layout - abort
377             return null;
378         }
379 
380         String unsupportedType = UnsupportedLayouts.firstUnsupportedType(type);
381         if (unsupportedType != null) {
382             String name = parent != null? parent.name() + "." : "";
383             name += fieldName;
384             warn("skipping " + name + " because of unsupported type usage: " +
385                     unsupportedType);
386             return null;
387         }
388 
389         Class<?> clazz = getJavaType(type);
390         if (clazz == null) {
391             String name = parent != null? parent.name() + "." : "";
392             name += fieldName;
393             warn("skipping " + name + " because of unsupported type usage");
394             return null;
395         }
396 
397 
398         VarInfo varInfo = VarInfo.ofVar(clazz, layout);
399         Type.Function func = getAsFunctionPointer(type);
400         String fiName;
401         if (func != null) {
402             fiName = generateFunctionalInterface(func, fieldName);
403             if (fiName != null) {
404                 varInfo = VarInfo.ofFunctionalPointerVar(clazz, layout, fiName);
405             }
406         } else {
407             Optional<String> funcTypedef = getAsFunctionPointerTypedef(type);
408             if (funcTypedef.isPresent()) {
409                 varInfo = VarInfo.ofFunctionalPointerVar(clazz, layout, Utils.javaSafeIdentifier(funcTypedef.get()));
410             }
411         }
412 
413         if (tree.kind() == Declaration.Variable.Kind.BITFIELD ||
414                 (layout instanceof ValueLayout && layout.byteSize() > 8)) {
415             //skip
416             return null;
417         }
418 
419         boolean sizeAvailable;
420         try {
421             layout.byteSize();
422             sizeAvailable = true;
423         } catch (Exception ignored) {
424             sizeAvailable = false;
425         }
426         if (sizeAvailable) {
427             currentBuilder.addVar(fieldName, tree.name(), varInfo);
428         } else {
429             warn("Layout size not available for " + fieldName);
430         }
431 
432         return null;
433     }
434 
435     private Optional<FunctionInfo> functionInfo(Type.Function funcPtr, String nativeName, boolean allowVarargs,
436                                                 BiFunction<MethodType, FunctionDescriptor, FunctionInfo> functionInfoFactory) {
437         FunctionDescriptor descriptor = Type.descriptorFor(funcPtr).orElse(null);
438         if (descriptor == null) {
439             //abort
440             return Optional.empty();
441         }
442 
443         //generate functional interface
444         if (!allowVarargs && funcPtr.varargs() && !funcPtr.argumentTypes().isEmpty()) {
445             warn("varargs in callbacks is not supported: " + funcPtr);
446             return Optional.empty();
447         }
448 
449         String unsupportedType = UnsupportedLayouts.firstUnsupportedType(funcPtr);
450         if (unsupportedType != null) {
451             warn("skipping " + nativeName + " because of unsupported type usage: " +
452                     unsupportedType);
453             return Optional.empty();
454         }
455 
456         MethodType mtype = getMethodType(funcPtr, allowVarargs);
457         return mtype != null ?
458                 Optional.of(functionInfoFactory.apply(mtype, descriptor)) :
459                 Optional.empty();
460     }
461 
462     protected static MemoryLayout layoutFor(Declaration decl) {
463         if (decl instanceof Declaration.Typedef) {
464             Declaration.Typedef alias = (Declaration.Typedef) decl;
465             return Type.layoutFor(alias.type()).orElseThrow();
466         } else if (decl instanceof Declaration.Scoped) {
467             return ((Declaration.Scoped) decl).layout().orElseThrow();
468         } else {
469             throw new IllegalArgumentException("Unexpected parent declaration");
470         }
471         // case like `typedef struct { ... } Foo`
472     }
473 
474     static void warn(String msg) {
475         System.err.println("WARNING: " + msg);
476     }
477 
478     private Class<?> getJavaType(Type type) {
479         try {
480             return typeTranslator.getJavaType(type);
481         } catch (UnsupportedOperationException uoe) {
482             warn(uoe.toString());
483             if (JextractTool.DEBUG) {
484                 uoe.printStackTrace();
485             }
486             return null;
487         }
488     }
489 
490     private MethodType getMethodType(Type.Function type) {
491         try {
492             return typeTranslator.getMethodType(type);
493         } catch (UnsupportedOperationException uoe) {
494             warn(uoe.toString());
495             if (JextractTool.DEBUG) {
496                 uoe.printStackTrace();
497             }
498             return null;
499         }
500     }
501 
502     private MethodType getMethodType(Type.Function type, boolean varargsCheck) {
503         try {
504             return typeTranslator.getMethodType(type, varargsCheck);
505         } catch (UnsupportedOperationException uoe) {
506             warn(uoe.toString());
507             if (JextractTool.DEBUG) {
508                 uoe.printStackTrace();
509             }
510             return null;
511         }
512     }
513 }