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 }