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                 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,
232                  (mtype, desc) -> FunctionInfo.ofFunctionPointer(mtype, getMethodType(func, true), desc, func.parameterNames()))
233                  .map(fInfo -> currentBuilder.addFunctionalInterface(Utils.javaSafeIdentifier(name), fInfo))
234                  .orElse(null);
235     }
236 
237     @Override
238     public Void visitFunction(Declaration.Function funcTree, Declaration parent) {
239         if (functionSeen(funcTree) ||
240                 !includeHelper.isIncluded(funcTree)) {
241             return null;
242         }
243 
244         String mhName = Utils.javaSafeIdentifier(funcTree.name());
245         //generate static wrapper for function
246         List<String> paramNames = funcTree.parameters()
247                                           .stream()
248                                           .map(Declaration.Variable::name)
249                                           .map(p -> !p.isEmpty() ? Utils.javaSafeIdentifier(p) : p)
250                                           .collect(Collectors.toList());
251 
252         Optional<FunctionInfo> functionInfo = functionInfo(funcTree.type(), funcTree.name(), true,
253                 (mtype, desc) -> FunctionInfo.ofFunction(mtype, desc, funcTree.type().varargs(), paramNames));
254 
255         if (functionInfo.isPresent()) {
256             int i = 0;
257             for (Declaration.Variable param : funcTree.parameters()) {
258                 Type.Function f = getAsFunctionPointer(param.type());
259                 if (f != null) {
260                     String name = funcTree.name() + "$" + (param.name().isEmpty() ? "x" + i : param.name());
261                     if (generateFunctionalInterface(f, name) == null) {
262                         return null;
263                     }
264                     i++;
265                 }
266             }
267 
268             toplevelBuilder.addFunction(mhName, funcTree.name(), functionInfo.get());
269         }
270 
271         return null;
272     }
273 
274     Optional<String> getAsFunctionPointerTypedef(Type type) {
275         if (type instanceof Type.Delegated delegated &&
276                 delegated.kind() == Type.Delegated.Kind.TYPEDEF &&
277                 functionTypedefSeen(delegated)) {
278             return Optional.of(functionTypedefName(delegated));
279         } else {
280             return Optional.empty();
281         }
282     }
283 
284     Type.Function getAsFunctionPointer(Type type) {
285         if (type instanceof Type.Function) {
286             /*
287              * // pointer to function declared as function like this
288              *
289              * typedef void CB(int);
290              * void func(CB cb);
291              */
292             return (Type.Function) type;
293         } else if (Utils.isPointerType(type)) {
294             return getAsFunctionPointer(((Type.Delegated)type).type());
295         } else {
296             return null;
297         }
298     }
299 
300     @Override
301     public Void visitTypedef(Declaration.Typedef tree, Declaration parent) {
302         if (!includeHelper.isIncluded(tree)) {
303             return null;
304         }
305         Type type = tree.type();
306         if (type instanceof Type.Declared) {
307             Declaration.Scoped s = ((Type.Declared) type).tree();
308             if (!s.name().equals(tree.name())) {
309                 switch (s.kind()) {
310                     case STRUCT, UNION -> {
311                         if (s.name().isEmpty()) {
312                             visitScoped(s, tree);
313                         } else {
314                             /*
315                              * If typedef is seen after the struct/union definition, we can generate subclass
316                              * right away. If not, we've to save it and revisit after all the declarations are
317                              * seen. This is to support forward declaration of typedefs.
318                              *
319                              * typedef struct Foo Bar;
320                              *
321                              * struct Foo {
322                              *     int x, y;
323                              * };
324                              */
325                             if (structDefinitionSeen(s)) {
326                                 toplevelBuilder.addTypedef(tree.name(), structDefinitionName(s), tree.type());
327                             } else {
328                                 /*
329                                  * Definition of typedef'ed struct/union not seen yet. May be the definition comes later.
330                                  * Save it to visit at the end of all declarations.
331                                  */
332                                 unresolvedStructTypedefs.add(tree);
333                             }
334                         }
335                     }
336                     default -> visitScoped(s, tree);
337                 }
338             }
339         } else if (type instanceof Type.Primitive) {
340              toplevelBuilder.addTypedef(tree.name(), null, type);
341         } else {
342             Type.Function func = getAsFunctionPointer(type);
343             if (func != null) {
344                 String funcIntfName = generateFunctionalInterface(func, tree.name());
345                 if (funcIntfName != null) {
346                     addFunctionTypedef(Type.typedef(tree.name(), tree.type()), funcIntfName);
347                 }
348             } else if (((TypeImpl)type).isPointer()) {
349                 toplevelBuilder.addTypedef(tree.name(), null, type);
350             }
351         }
352         return null;
353     }
354 
355     @Override
356     public Void visitVariable(Declaration.Variable tree, Declaration parent) {
357         if (parent == null && (variableSeen(tree) || !includeHelper.isIncluded(tree))) {
358             return null;
359         }
360 
361         String fieldName = tree.name();
362         String symbol = tree.name();
363         assert !symbol.isEmpty();
364         assert !fieldName.isEmpty();
365         fieldName = Utils.javaSafeIdentifier(fieldName);
366 
367         Type type = tree.type();
368 
369         if (type instanceof Type.Declared) {
370             // declared type - visit declaration recursively
371             ((Type.Declared) type).tree().accept(this, tree);
372         }
373         MemoryLayout layout = tree.layout().orElse(Type.layoutFor(type).orElse(null));
374         if (layout == null) {
375             //no layout - abort
376             return null;
377         }
378 
379         String unsupportedType = UnsupportedLayouts.firstUnsupportedType(type);
380         if (unsupportedType != null) {
381             String name = parent != null? parent.name() + "." : "";
382             name += fieldName;
383             warn("skipping " + name + " because of unsupported type usage: " +
384                     unsupportedType);
385             return null;
386         }
387 
388         Class<?> clazz = getJavaType(type);
389         if (clazz == null) {
390             String name = parent != null? parent.name() + "." : "";
391             name += fieldName;
392             warn("skipping " + name + " because of unsupported type usage");
393             return null;
394         }
395 
396 
397         VarInfo varInfo = VarInfo.ofVar(clazz, layout);
398         Type.Function func = getAsFunctionPointer(type);
399         String fiName;
400         if (func != null) {
401             fiName = generateFunctionalInterface(func, fieldName);
402             if (fiName != null) {
403                 varInfo = VarInfo.ofFunctionalPointerVar(clazz, layout, fiName);
404             }
405         } else {
406             Optional<String> funcTypedef = getAsFunctionPointerTypedef(type);
407             if (funcTypedef.isPresent()) {
408                 varInfo = VarInfo.ofFunctionalPointerVar(clazz, layout, Utils.javaSafeIdentifier(funcTypedef.get()));
409             }
410         }
411 
412         if (tree.kind() == Declaration.Variable.Kind.BITFIELD ||
413                 (layout instanceof ValueLayout && layout.byteSize() > 8)) {
414             //skip
415             return null;
416         }
417 
418         boolean sizeAvailable;
419         try {
420             layout.byteSize();
421             sizeAvailable = true;
422         } catch (Exception ignored) {
423             sizeAvailable = false;
424         }
425         if (sizeAvailable) {
426             currentBuilder.addVar(fieldName, tree.name(), varInfo);
427         } else {
428             warn("Layout size not available for " + fieldName);
429         }
430 
431         return null;
432     }
433 
434     private Optional<FunctionInfo> functionInfo(Type.Function funcPtr, String nativeName, boolean downcall,
435                                                 BiFunction<MethodType, FunctionDescriptor, FunctionInfo> functionInfoFactory) {
436         FunctionDescriptor descriptor = Type.descriptorFor(funcPtr).orElse(null);
437         if (descriptor == null) {
438             //abort
439             return Optional.empty();
440         }
441 
442         //generate functional interface
443         if (!downcall && funcPtr.varargs() && !funcPtr.argumentTypes().isEmpty()) {
444             warn("varargs in callbacks is not supported: " + funcPtr);
445             return Optional.empty();
446         }
447 
448         String unsupportedType = UnsupportedLayouts.firstUnsupportedType(funcPtr);
449         if (unsupportedType != null) {
450             warn("skipping " + nativeName + " because of unsupported type usage: " +
451                     unsupportedType);
452             return Optional.empty();
453         }
454 
455         MethodType mtype = getMethodType(funcPtr, downcall);
456         return mtype != null ?
457                 Optional.of(functionInfoFactory.apply(mtype, descriptor)) :
458                 Optional.empty();
459     }
460 
461     protected static MemoryLayout layoutFor(Declaration decl) {
462         if (decl instanceof Declaration.Typedef) {
463             Declaration.Typedef alias = (Declaration.Typedef) decl;
464             return Type.layoutFor(alias.type()).orElseThrow();
465         } else if (decl instanceof Declaration.Scoped) {
466             return ((Declaration.Scoped) decl).layout().orElseThrow();
467         } else {
468             throw new IllegalArgumentException("Unexpected parent declaration");
469         }
470         // case like `typedef struct { ... } Foo`
471     }
472 
473     static void warn(String msg) {
474         System.err.println("WARNING: " + msg);
475     }
476 
477     private Class<?> getJavaType(Type type) {
478         try {
479             return typeTranslator.getJavaType(type, false);
480         } catch (UnsupportedOperationException uoe) {
481             warn(uoe.toString());
482             if (JextractTool.DEBUG) {
483                 uoe.printStackTrace();
484             }
485             return null;
486         }
487     }
488 
489     private MethodType getMethodType(Type.Function type, boolean downcall) {
490         try {
491             return typeTranslator.getMethodType(type, downcall);
492         } catch (UnsupportedOperationException uoe) {
493             warn(uoe.toString());
494             if (JextractTool.DEBUG) {
495                 uoe.printStackTrace();
496             }
497             return null;
498         }
499     }
500 }