/* * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javac.jvm; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.tools.FileObject; import javax.tools.JavaFileManager; import javax.tools.JavaFileManager.Location; import javax.tools.StandardLocation; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.ModuleSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; import com.sun.tools.javac.util.Pair; import static com.sun.tools.javac.main.Option.*; import static com.sun.tools.javac.code.Kinds.Kind.*; import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; /** This class provides operations to write native header files for classes. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public class JNIWriter { protected static final Context.Key<JNIWriter> jniWriterKey = new Context.Key<>(); /** Access to files. */ private final JavaFileManager fileManager; Types types; Symtab syms; /** The log to use for verbose output. */ private final Log log; /** Switch: verbose output. */ private boolean verbose; /** Switch: check all nested classes of top level class */ private boolean checkAll; /** * If true, class files will be written in module-specific subdirectories * of the NATIVE_HEADER_OUTPUT location. */ public boolean multiModuleMode; private Context context; private static final boolean isWindows = System.getProperty("os.name").startsWith("Windows"); /** Get the ClassWriter instance for this context. */ public static JNIWriter instance(Context context) { JNIWriter instance = context.get(jniWriterKey); if (instance == null) instance = new JNIWriter(context); return instance; } /** Construct a class writer, given an options table. */ private JNIWriter(Context context) { context.put(jniWriterKey, this); fileManager = context.get(JavaFileManager.class); log = Log.instance(context); Options options = Options.instance(context); verbose = options.isSet(VERBOSE); checkAll = options.isSet("javah:full"); this.context = context; // for lazyInit() } private void lazyInit() { if (types == null) types = Types.instance(context); if (syms == null) syms = Symtab.instance(context); } static boolean isSynthetic(Symbol s) { return hasFlag(s, Flags.SYNTHETIC); } static boolean isStatic(Symbol s) { return hasFlag(s, Flags.STATIC); } static boolean isFinal(Symbol s) { return hasFlag(s, Flags.FINAL); } static boolean isNative(Symbol s) { return hasFlag(s, Flags.NATIVE); } static private boolean hasFlag(Symbol m, int flag) { return (m.flags() & flag) != 0; } public boolean needsHeader(ClassSymbol c) { lazyInit(); if (c.isLocal() || isSynthetic(c)) return false; return (checkAll) ? needsHeader(c.outermostClass(), true) : needsHeader(c, false); } private boolean needsHeader(ClassSymbol c, boolean checkNestedClasses) { if (c.isLocal() || isSynthetic(c)) return false; for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) { if (sym.kind == MTH && isNative(sym)) return true; for (Attribute.Compound a: sym.getDeclarationAttributes()) { if (a.type.tsym == syms.nativeHeaderType.tsym) return true; } } if (checkNestedClasses) { for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) { if ((sym.kind == TYP) && needsHeader(((ClassSymbol) sym), true)) return true; } } return false; } /** Emit a class file for a given class. * @param c The class from which a class file is generated. */ public FileObject write(ClassSymbol c) throws IOException { String className = c.flatName().toString(); Location outLocn; if (multiModuleMode) { ModuleSymbol msym = c.owner.kind == MDL ? (ModuleSymbol) c.owner : c.packge().modle; outLocn = fileManager.getLocationForModule(StandardLocation.NATIVE_HEADER_OUTPUT, msym.name.toString()); } else { outLocn = StandardLocation.NATIVE_HEADER_OUTPUT; } FileObject outFile = fileManager.getFileForOutput(outLocn, "", className.replaceAll("[.$]", "_") + ".h", null); PrintWriter out = new PrintWriter(outFile.openWriter()); try { write(out, c); if (verbose) log.printVerbose("wrote.file", outFile); out.close(); out = null; } finally { if (out != null) { // if we are propogating an exception, delete the file out.close(); outFile.delete(); outFile = null; } } return outFile; // may be null if write failed } public void write(PrintWriter out, ClassSymbol sym) throws IOException { lazyInit(); try { String cname = encode(sym.fullname, EncoderType.CLASS); fileTop(out); includes(out); guardBegin(out, cname); cppGuardBegin(out); writeStatics(out, sym); writeMethods(out, sym, cname); cppGuardEnd(out); guardEnd(out); } catch (TypeSignature.SignatureException e) { throw new IOException(e); } } protected void writeStatics(PrintWriter out, ClassSymbol sym) throws IOException { List<ClassSymbol> clist = new ArrayList<>(); for (ClassSymbol cd = sym; cd != null; cd = (ClassSymbol) cd.getSuperclass().tsym) { clist.add(cd); } /* * list needs to be super-class, base-class1, base-class2 and so on, * so we reverse class hierarchy */ Collections.reverse(clist); for (ClassSymbol cd : clist) { for (Symbol i : cd.getEnclosedElements()) { // consider only final, static and fields with ConstantExpressions if (isFinal(i) && i.isStatic() && i.kind == VAR) { VarSymbol v = (VarSymbol) i; if (v.getConstantValue() != null) { Pair<ClassSymbol, VarSymbol> p = new Pair<>(sym, v); printStaticDefines(out, p); } } } } } static void printStaticDefines(PrintWriter out, Pair<ClassSymbol, VarSymbol> p) { ClassSymbol cls = p.fst; VarSymbol f = p.snd; Object value = f.getConstantValue(); String valueStr = null; switch (f.asType().getKind()) { case BOOLEAN: valueStr = (((Boolean) value) ? "1L" : "0L"); break; case BYTE: case SHORT: case INT: valueStr = value.toString() + "L"; break; case LONG: // Visual C++ supports the i64 suffix, not LL. valueStr = value.toString() + ((isWindows) ? "i64" : "LL"); break; case CHAR: Character ch = (Character) value; valueStr = String.valueOf(((int) ch) & 0xffff) + "L"; break; case FLOAT: // bug compatible float fv = ((Float) value).floatValue(); valueStr = (Float.isInfinite(fv)) ? ((fv < 0) ? "-" : "") + "Inff" : value.toString() + "f"; break; case DOUBLE: // bug compatible double d = ((Double) value).doubleValue(); valueStr = (Double.isInfinite(d)) ? ((d < 0) ? "-" : "") + "InfD" : value.toString(); break; default: valueStr = null; } if (valueStr != null) { out.print("#undef "); String cname = encode(cls.getQualifiedName(), EncoderType.CLASS); String fname = encode(f.getSimpleName(), EncoderType.FIELDSTUB); out.println(cname + "_" + fname); out.print("#define " + cname + "_"); out.println(fname + " " + valueStr); } } protected void writeMethods(PrintWriter out, ClassSymbol sym, String cname) throws IOException, TypeSignature.SignatureException { List<Symbol> classmethods = sym.getEnclosedElements(); for (Symbol md : classmethods) { if (isNative(md)) { TypeSignature newtypesig = new TypeSignature(types); CharSequence methodName = md.getSimpleName(); boolean isOverloaded = false; for (Symbol md2 : classmethods) { if ((md2 != md) && (methodName.equals(md2.getSimpleName())) && isNative(md2)) { isOverloaded = true; } } out.println("/*"); out.println(" * Class: " + cname); out.println(" * Method: " + encode(methodName, EncoderType.FIELDSTUB)); out.println(" * Signature: " + newtypesig.getSignature(md.type)); out.println(" */"); out.println("JNIEXPORT " + jniType(types.erasure(md.type.getReturnType())) + " JNICALL " + encodeMethod(md, sym, isOverloaded)); out.print(" (JNIEnv *, "); out.print((md.isStatic()) ? "jclass" : "jobject"); for (Type arg : types.erasure(md.type.getParameterTypes())) { out.print(", "); out.print(jniType(arg)); } out.println(");"); out.println(); } } } @SuppressWarnings("fallthrough") protected final String jniType(Type t) { switch (t.getKind()) { case ARRAY: { Type ct = ((Type.ArrayType)t).getComponentType(); switch (ct.getKind()) { case BOOLEAN: return "jbooleanArray"; case BYTE: return "jbyteArray"; case CHAR: return "jcharArray"; case SHORT: return "jshortArray"; case INT: return "jintArray"; case LONG: return "jlongArray"; case FLOAT: return "jfloatArray"; case DOUBLE: return "jdoubleArray"; case ARRAY: case DECLARED: return "jobjectArray"; default: throw new Error(ct.toString()); } } case VOID: return "void"; case BOOLEAN: return "jboolean"; case BYTE: return "jbyte"; case CHAR: return "jchar"; case SHORT: return "jshort"; case INT: return "jint"; case LONG: return "jlong"; case FLOAT: return "jfloat"; case DOUBLE: return "jdouble"; case DECLARED: { if (t.tsym.type == syms.stringType) { return "jstring"; } else if (types.isAssignable(t, syms.throwableType)) { return "jthrowable"; } else if (types.isAssignable(t, syms.classType)) { return "jclass"; } else { return "jobject"; } } } Assert.check(false, "jni unknown type"); return null; /* dead code. */ } protected void fileTop(PrintWriter out) { out.println("/* DO NOT EDIT THIS FILE - it is machine generated */"); } protected void includes(PrintWriter out) { out.println("#include <jni.h>"); } /* * Deal with the C pre-processor. */ protected void cppGuardBegin(PrintWriter out) { out.println("#ifdef __cplusplus"); out.println("extern \"C\" {"); out.println("#endif"); } protected void cppGuardEnd(PrintWriter out) { out.println("#ifdef __cplusplus"); out.println("}"); out.println("#endif"); } protected void guardBegin(PrintWriter out, String cname) { out.println("/* Header for class " + cname + " */"); out.println(); out.println("#ifndef _Included_" + cname); out.println("#define _Included_" + cname); } protected void guardEnd(PrintWriter out) { out.println("#endif"); } String encodeMethod(Symbol msym, ClassSymbol clazz, boolean isOverloaded) throws TypeSignature.SignatureException { StringBuilder result = new StringBuilder(100); result.append("Java_"); /* JNI */ result.append(encode(clazz.flatname.toString(), EncoderType.JNI)); result.append('_'); result.append(encode(msym.getSimpleName(), EncoderType.JNI)); if (isOverloaded) { TypeSignature typeSig = new TypeSignature(types); StringBuilder sig = typeSig.getParameterSignature(msym.type); result.append("__").append(encode(sig, EncoderType.JNI)); } return result.toString(); } static enum EncoderType { CLASS, FIELDSTUB, FIELD, JNI, SIGNATURE } @SuppressWarnings("fallthrough") static String encode(CharSequence name, EncoderType mtype) { StringBuilder result = new StringBuilder(100); int length = name.length(); for (int i = 0; i < length; i++) { char ch = name.charAt(i); if (isalnum(ch)) { result.append(ch); continue; } switch (mtype) { case CLASS: switch (ch) { case '.': case '_': result.append("_"); break; case '$': result.append("__"); break; default: result.append(encodeChar(ch)); } break; case JNI: switch (ch) { case '/': case '.': result.append("_"); break; case '_': result.append("_1"); break; case ';': result.append("_2"); break; case '[': result.append("_3"); break; default: result.append(encodeChar(ch)); } break; case SIGNATURE: result.append(isprint(ch) ? ch : encodeChar(ch)); break; case FIELDSTUB: result.append(ch == '_' ? ch : encodeChar(ch)); break; default: result.append(encodeChar(ch)); } } return result.toString(); } static String encodeChar(char ch) { String s = Integer.toHexString(ch); int nzeros = 5 - s.length(); char[] result = new char[6]; result[0] = '_'; for (int i = 1; i <= nzeros; i++) { result[i] = '0'; } for (int i = nzeros + 1, j = 0; i < 6; i++, j++) { result[i] = s.charAt(j); } return new String(result); } /* Warning: Intentional ASCII operation. */ private static boolean isalnum(char ch) { return ch <= 0x7f && /* quick test */ ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')); } /* Warning: Intentional ASCII operation. */ private static boolean isprint(char ch) { return ch >= 32 && ch <= 126; } private static class TypeSignature { static class SignatureException extends Exception { private static final long serialVersionUID = 1L; SignatureException(String reason) { super(reason); } } JavacElements elems; Types types; /* Signature Characters */ private static final String SIG_VOID = "V"; private static final String SIG_BOOLEAN = "Z"; private static final String SIG_BYTE = "B"; private static final String SIG_CHAR = "C"; private static final String SIG_SHORT = "S"; private static final String SIG_INT = "I"; private static final String SIG_LONG = "J"; private static final String SIG_FLOAT = "F"; private static final String SIG_DOUBLE = "D"; private static final String SIG_ARRAY = "["; private static final String SIG_CLASS = "L"; public TypeSignature(Types types) { this.types = types; } StringBuilder getParameterSignature(Type mType) throws SignatureException { StringBuilder result = new StringBuilder(); for (Type pType : mType.getParameterTypes()) { result.append(getJvmSignature(pType)); } return result; } StringBuilder getReturnSignature(Type mType) throws SignatureException { return getJvmSignature(mType.getReturnType()); } StringBuilder getSignature(Type mType) throws SignatureException { StringBuilder sb = new StringBuilder(); sb.append("(").append(getParameterSignature(mType)).append(")"); sb.append(getReturnSignature(mType)); return sb; } /* * Returns jvm internal signature. */ static class JvmTypeVisitor extends JNIWriter.SimpleTypeVisitor<Type, StringBuilder> { @Override public Type visitClassType(Type.ClassType t, StringBuilder s) { setDeclaredType(t, s); return null; } @Override public Type visitArrayType(Type.ArrayType t, StringBuilder s) { s.append("["); return t.getComponentType().accept(this, s); } @Override public Type visitType(Type t, StringBuilder s) { if (t.isPrimitiveOrVoid()) { s.append(getJvmPrimitiveSignature(t)); return null; } return t.accept(this, s); } private void setDeclaredType(Type t, StringBuilder s) { String classname = t.tsym.getQualifiedName().toString(); classname = classname.replace('.', '/'); s.append("L").append(classname).append(";"); } private String getJvmPrimitiveSignature(Type t) { switch (t.getKind()) { case VOID: return SIG_VOID; case BOOLEAN: return SIG_BOOLEAN; case BYTE: return SIG_BYTE; case CHAR: return SIG_CHAR; case SHORT: return SIG_SHORT; case INT: return SIG_INT; case LONG: return SIG_LONG; case FLOAT: return SIG_FLOAT; case DOUBLE: return SIG_DOUBLE; default: Assert.error("unknown type: should not happen"); } return null; } } StringBuilder getJvmSignature(Type type) { Type t = types.erasure(type); StringBuilder sig = new StringBuilder(); JvmTypeVisitor jv = new JvmTypeVisitor(); jv.visitType(t, sig); return sig; } } static class SimpleTypeVisitor<R, P> implements Type.Visitor<R, P> { protected final R DEFAULT_VALUE; protected SimpleTypeVisitor() { DEFAULT_VALUE = null; } protected SimpleTypeVisitor(R defaultValue) { DEFAULT_VALUE = defaultValue; } protected R defaultAction(Type t, P p) { return DEFAULT_VALUE; } @Override public R visitClassType(Type.ClassType t, P p) { return defaultAction(t, p); } @Override public R visitWildcardType(Type.WildcardType t, P p) { return defaultAction(t, p); } @Override public R visitArrayType(Type.ArrayType t, P p) { return defaultAction(t, p); } @Override public R visitMethodType(Type.MethodType t, P p) { return defaultAction(t, p); } @Override public R visitPackageType(Type.PackageType t, P p) { return defaultAction(t, p); } @Override public R visitTypeVar(Type.TypeVar t, P p) { return defaultAction(t, p); } @Override public R visitCapturedType(Type.CapturedType t, P p) { return defaultAction(t, p); } @Override public R visitForAll(Type.ForAll t, P p) { return defaultAction(t, p); } @Override public R visitUndetVar(Type.UndetVar t, P p) { return defaultAction(t, p); } @Override public R visitErrorType(Type.ErrorType t, P p) { return defaultAction(t, p); } @Override public R visitType(Type t, P p) { return defaultAction(t, p); } @Override public R visitModuleType(Type.ModuleType t, P p) { return defaultAction(t, p); } } }