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  */
 26 
 27 package jdk.internal.jextract.impl;
 28 
 29 import jdk.incubator.foreign.MemoryAddress;
 30 import jdk.incubator.foreign.VaList;
 31 import jdk.incubator.foreign.ValueLayout;
 32 import jdk.incubator.jextract.Type.Delegated;
 33 import jdk.internal.clang.Cursor;
 34 import jdk.internal.clang.CursorKind;
 35 import jdk.internal.clang.SourceLocation;
 36 import jdk.internal.clang.Type;
 37 
 38 import javax.lang.model.SourceVersion;
 39 import javax.tools.JavaFileObject;
 40 import javax.tools.SimpleJavaFileObject;
 41 import java.io.IOException;
 42 import java.net.URI;
 43 import java.nio.file.Files;
 44 import java.nio.file.Path;
 45 import java.util.ArrayList;
 46 import java.util.Arrays;
 47 import java.util.List;
 48 import java.util.Optional;
 49 import java.util.stream.Stream;
 50 
 51 /**
 52  * General utility functions
 53  */
 54 class Utils {
 55     public static String qualifiedClassName(String packageName, String simpleName) {
 56         return (packageName.isEmpty() ? "" : packageName + ".") + simpleName;
 57     }
 58 
 59     private static URI fileName(String pkgName, String clsName, String extension) {
 60         String pkgPrefix = pkgName.isEmpty() ? "" : pkgName.replaceAll("\\.", "/") + "/";
 61         return URI.create(pkgPrefix + clsName + extension);
 62     }
 63 
 64     static JavaFileObject fileFromString(String pkgName, String clsName, String contents) {
 65         return new SimpleJavaFileObject(fileName(pkgName, clsName, ".java"), JavaFileObject.Kind.SOURCE) {
 66             @Override
 67             public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
 68                 return contents;
 69             }
 70         };
 71     }
 72 
 73     static String javaSafeIdentifier(String name) {
 74         return javaSafeIdentifier(name, false);
 75     }
 76 
 77     static String javaSafeIdentifier(String name, boolean checkAllChars) {
 78         if (checkAllChars) {
 79             StringBuilder buf = new StringBuilder();
 80             char[] chars = name.toCharArray();
 81             if (Character.isJavaIdentifierStart(chars[0])) {
 82                 buf.append(chars[0]);
 83             } else {
 84                 buf.append('_');
 85             }
 86             if (chars.length > 1) {
 87                 for (int i = 1; i < chars.length; i++) {
 88                     char ch = chars[i];
 89                     if (Character.isJavaIdentifierPart(ch)) {
 90                         buf.append(ch);
 91                     } else {
 92                         buf.append('_');
 93                     }
 94                 }
 95             }
 96             return buf.toString();
 97         } else {
 98             // We never get the problem of Java non-identifiers (like 123, ab-xy) as
 99             // C identifiers. But we may have a java keyword used as a C identifier.
100             assert SourceVersion.isIdentifier(name);
101 
102             return SourceVersion.isKeyword(name) || isRestrictedTypeName(name) || isJavaTypeName(name)? (name + "_") : name;
103         }
104     }
105 
106     private static boolean isRestrictedTypeName(String name) {
107         return switch (name) {
108             case "var", "yield", "record",
109                 "sealed", "permits" -> true;
110             default -> false;
111         };
112     }
113 
114     private static boolean isJavaTypeName(String name) {
115         // Java types that are used unqualified in the generated code
116         return switch (name) {
117             case "String", "MethodHandle",
118                 "VarHandle", "ByteOrder",
119                 "FunctionDescriptor", "LibraryLookup",
120                 "MemoryAddress", "MemoryLayout",
121                 "MemorySegment", "ValueLayout",
122                 "RuntimeHelper" -> true;
123             default -> false;
124         };
125     }
126 
127     static void validSimpleIdentifier(String name) {
128         int length = name.length();
129         if (length == 0) {
130             throw new IllegalArgumentException();
131         }
132 
133         int ch = name.codePointAt(0);
134         if (length == 1 && ch == '_') {
135             throw new IllegalArgumentException("'_' is no longer valid identifier.");
136         }
137 
138         if (!Character.isJavaIdentifierStart(ch)) {
139             throw new IllegalArgumentException("Invalid start character for an identifier: " + ch);
140         }
141 
142         for (int i = 1; i < length; i++) {
143             ch = name.codePointAt(i);
144             if (!Character.isJavaIdentifierPart(ch)) {
145                 throw new IllegalArgumentException("Invalid character for an identifier: " + ch);
146             }
147         }
148     }
149 
150     static void validPackageName(String name) {
151         if (name.isEmpty()) {
152             throw new IllegalArgumentException();
153         }
154         int idx = name.lastIndexOf('.');
155         if (idx == -1) {
156            validSimpleIdentifier(name);
157         } else {
158             validSimpleIdentifier(name.substring(idx + 1));
159             validPackageName(name.substring(0, idx));
160         }
161     }
162 
163     static String toJavaIdentifier(String str) {
164         final int size = str.length();
165         StringBuilder sb = new StringBuilder(size);
166         if (! Character.isJavaIdentifierStart(str.charAt(0))) {
167             sb.append('_');
168         }
169         for (int i = 0; i < size; i++) {
170             char ch = str.charAt(i);
171             if (Character.isJavaIdentifierPart(ch)) {
172                 sb.append(ch);
173             } else {
174                 sb.append('_');
175             }
176         }
177         return sb.toString();
178     }
179 
180     static String toSafeName(String name) {
181         StringBuilder sb = new StringBuilder(name.length());
182         name = toJavaIdentifier(name);
183         sb.append(name);
184         if (SourceVersion.isKeyword(name)) {
185             sb.append("$");
186         }
187         return sb.toString();
188     }
189 
190     static String toClassName(String cname) {
191         return toSafeName(cname);
192     }
193 
194     static String toMacroName(String mname) {
195         return toSafeName(mname);
196     }
197 
198     static String toInternalName(String pkg, String name, String... nested) {
199         if ((pkg == null || pkg.isEmpty()) && nested == null) {
200             return name;
201         }
202 
203         StringBuilder sb = new StringBuilder();
204         if (pkg != null && ! pkg.isEmpty()) {
205             sb.append(pkg.replace('.', '/'));
206             if (sb.charAt(sb.length() - 1) != '/') {
207                 sb.append('/');
208             }
209         }
210         sb.append(name);
211         for (String n: nested) {
212             sb.append('$');
213             sb.append(n);
214         }
215         return sb.toString();
216     }
217 
218     static Stream<Cursor> flattenableChildren(Cursor c) {
219         return c.children()
220                 .filter(cx -> cx.isAnonymousStruct() || cx.kind() == CursorKind.FieldDecl);
221     }
222 
223     // return builtin Record types accessible from the given Type
224     static Stream<Cursor> getBuiltinRecordTypes(Type type) {
225         List<Cursor> recordTypes = new ArrayList<>();
226         fillBuiltinRecordTypes(type, recordTypes);
227         return recordTypes.stream().distinct();
228     }
229 
230     private static void fillBuiltinRecordTypes(Type type, List<Cursor> recordTypes) {
231         Type canonicalType = type.canonicalType();
232         switch (canonicalType.kind()) {
233             case ConstantArray:
234             case IncompleteArray:
235                 fillBuiltinRecordTypes(canonicalType.getElementType(), recordTypes);
236                 break;
237 
238             case FunctionProto:
239             case FunctionNoProto: {
240                 final int numArgs = canonicalType.numberOfArgs();
241                 for (int i = 0; i < numArgs; i++) {
242                     fillBuiltinRecordTypes(canonicalType.argType(i), recordTypes);
243                 }
244                 fillBuiltinRecordTypes(canonicalType.resultType(), recordTypes);
245             }
246             break;
247 
248             case Record: {
249                 Cursor c = canonicalType.getDeclarationCursor();
250                 if (c.isDefinition()) {
251                     SourceLocation sloc = c.getSourceLocation();
252                     if (sloc != null && sloc.getFileLocation().path() == null) {
253                         recordTypes.add(c);
254                     }
255                 }
256             }
257             break;
258 
259             case BlockPointer:
260             case Pointer:
261                 fillBuiltinRecordTypes(canonicalType.getPointeeType(), recordTypes);
262                 break;
263 
264             case Unexposed:
265                 if (! canonicalType.equalType(type)) {
266                     fillBuiltinRecordTypes(canonicalType, recordTypes);
267                 }
268                 break;
269 
270             case Elaborated:
271             case Typedef:
272                 fillBuiltinRecordTypes(canonicalType, recordTypes);
273                 break;
274 
275             default: // nothing to do
276         }
277     }
278 
279     // return the absolute path of the library of given name by searching
280     // in the given array of paths.
281     static Optional<Path> findLibraryPath(Path[] paths, String libName) {
282         return Arrays.stream(paths).
283                 map(p -> p.resolve(System.mapLibraryName(libName))).
284                 filter(Files::isRegularFile).map(Path::toAbsolutePath).findFirst();
285     }
286 
287     /*
288      * FIXME: when we add jdk.compiler dependency from jdk.jextract module, revisit
289      * the following. The following methods 'quote', 'quote' and 'isPrintableAscii'
290      * are from javac source. See also com.sun.tools.javac.util.Convert.java.
291      */
292 
293     /**
294      * Escapes each character in a string that has an escape sequence or
295      * is non-printable ASCII.  Leaves non-ASCII characters alone.
296      */
297     static String quote(String s) {
298         StringBuilder buf = new StringBuilder();
299         for (int i = 0; i < s.length(); i++) {
300             buf.append(quote(s.charAt(i)));
301         }
302         return buf.toString();
303     }
304 
305     /**
306      * Escapes a character if it has an escape sequence or is
307      * non-printable ASCII.  Leaves non-ASCII characters alone.
308      */
309     static String quote(char ch) {
310         switch (ch) {
311         case '\b':  return "\\b";
312         case '\f':  return "\\f";
313         case '\n':  return "\\n";
314         case '\r':  return "\\r";
315         case '\t':  return "\\t";
316         case '\'':  return "\\'";
317         case '\"':  return "\\\"";
318         case '\\':  return "\\\\";
319         default:
320             return (isPrintableAscii(ch))
321                 ? String.valueOf(ch)
322                 : String.format("\\u%04x", (int) ch);
323         }
324     }
325 
326     static boolean isPointerType(jdk.incubator.jextract.Type type) {
327         if (type instanceof Delegated) {
328             Delegated delegated = (Delegated) type;
329             return delegated.kind() == Delegated.Kind.POINTER;
330         } else {
331             return false;
332         }
333     }
334 
335     /**
336      * Is a character printable ASCII?
337      */
338     private static boolean isPrintableAscii(char ch) {
339         return ch >= ' ' && ch <= '~';
340     }
341 }