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