1/*
2 * Copyright (c) 1999, 2016, 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
26package com.sun.tools.javac.jvm;
27
28import java.io.IOException;
29import java.io.PrintWriter;
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.List;
33
34import javax.tools.FileObject;
35import javax.tools.JavaFileManager;
36import javax.tools.JavaFileManager.Location;
37import javax.tools.StandardLocation;
38
39import com.sun.tools.javac.code.Attribute;
40import com.sun.tools.javac.code.Flags;
41import com.sun.tools.javac.code.Symbol;
42import com.sun.tools.javac.code.Symbol.ClassSymbol;
43import com.sun.tools.javac.code.Symbol.ModuleSymbol;
44import com.sun.tools.javac.code.Symbol.VarSymbol;
45import com.sun.tools.javac.code.Symtab;
46import com.sun.tools.javac.code.Type;
47import com.sun.tools.javac.code.Types;
48import com.sun.tools.javac.model.JavacElements;
49import com.sun.tools.javac.util.Assert;
50import com.sun.tools.javac.util.Context;
51import com.sun.tools.javac.util.Log;
52import com.sun.tools.javac.util.Options;
53import com.sun.tools.javac.util.Pair;
54
55import static com.sun.tools.javac.main.Option.*;
56import static com.sun.tools.javac.code.Kinds.Kind.*;
57import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
58
59/** This class provides operations to write native header files for classes.
60 *
61 *  <p><b>This is NOT part of any supported API.
62 *  If you write code that depends on this, you do so at your own risk.
63 *  This code and its internal interfaces are subject to change or
64 *  deletion without notice.</b>
65 */
66public class JNIWriter {
67    protected static final Context.Key<JNIWriter> jniWriterKey = new Context.Key<>();
68
69    /** Access to files. */
70    private final JavaFileManager fileManager;
71
72    Types      types;
73    Symtab     syms;
74
75    /** The log to use for verbose output.
76     */
77    private final Log log;
78
79    /** Switch: verbose output.
80     */
81    private boolean verbose;
82
83    /** Switch: check all nested classes of top level class
84     */
85    private boolean checkAll;
86
87    /**
88     * If true, class files will be written in module-specific subdirectories
89     * of the NATIVE_HEADER_OUTPUT location.
90     */
91    public boolean multiModuleMode;
92
93    private Context context;
94
95    private static final boolean isWindows =
96        System.getProperty("os.name").startsWith("Windows");
97
98    /** Get the ClassWriter instance for this context. */
99    public static JNIWriter instance(Context context) {
100        JNIWriter instance = context.get(jniWriterKey);
101        if (instance == null)
102            instance = new JNIWriter(context);
103        return instance;
104    }
105
106    /** Construct a class writer, given an options table.
107     */
108    private JNIWriter(Context context) {
109        context.put(jniWriterKey, this);
110        fileManager = context.get(JavaFileManager.class);
111        log = Log.instance(context);
112
113        Options options = Options.instance(context);
114        verbose = options.isSet(VERBOSE);
115        checkAll = options.isSet("javah:full");
116
117        this.context = context; // for lazyInit()
118    }
119
120    private void lazyInit() {
121        if (types == null)
122            types = Types.instance(context);
123        if (syms == null)
124            syms = Symtab.instance(context);
125
126    }
127
128    static boolean isSynthetic(Symbol s) {
129        return hasFlag(s, Flags.SYNTHETIC);
130    }
131    static boolean isStatic(Symbol s) {
132        return hasFlag(s, Flags.STATIC);
133    }
134    static boolean isFinal(Symbol s) {
135        return hasFlag(s, Flags.FINAL);
136    }
137    static boolean isNative(Symbol s) {
138        return hasFlag(s, Flags.NATIVE);
139    }
140    static private boolean hasFlag(Symbol m, int flag) {
141        return (m.flags() & flag) != 0;
142    }
143
144    public boolean needsHeader(ClassSymbol c) {
145        lazyInit();
146        if (c.isLocal() || isSynthetic(c))
147            return false;
148        return (checkAll)
149                ? needsHeader(c.outermostClass(), true)
150                : needsHeader(c, false);
151    }
152
153    private boolean needsHeader(ClassSymbol c, boolean checkNestedClasses) {
154        if (c.isLocal() || isSynthetic(c))
155            return false;
156
157        for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) {
158            if (sym.kind == MTH && isNative(sym))
159                return true;
160            for (Attribute.Compound a: sym.getDeclarationAttributes()) {
161                if (a.type.tsym == syms.nativeHeaderType.tsym)
162                    return true;
163            }
164        }
165        if (checkNestedClasses) {
166            for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) {
167                if ((sym.kind == TYP) && needsHeader(((ClassSymbol) sym), true))
168                    return true;
169            }
170        }
171        return false;
172    }
173
174    /** Emit a class file for a given class.
175     *  @param c      The class from which a class file is generated.
176     */
177    public FileObject write(ClassSymbol c) throws IOException {
178        String className = c.flatName().toString();
179        Location outLocn;
180        if (multiModuleMode) {
181            ModuleSymbol msym = c.owner.kind == MDL ? (ModuleSymbol) c.owner : c.packge().modle;
182            outLocn = fileManager.getLocationForModule(StandardLocation.NATIVE_HEADER_OUTPUT, msym.name.toString());
183        } else {
184            outLocn = StandardLocation.NATIVE_HEADER_OUTPUT;
185        }
186        FileObject outFile
187            = fileManager.getFileForOutput(outLocn,
188                "", className.replaceAll("[.$]", "_") + ".h", null);
189        PrintWriter out = new PrintWriter(outFile.openWriter());
190        try {
191            write(out, c);
192            if (verbose)
193                log.printVerbose("wrote.file", outFile);
194            out.close();
195            out = null;
196        } finally {
197            if (out != null) {
198                // if we are propogating an exception, delete the file
199                out.close();
200                outFile.delete();
201                outFile = null;
202            }
203        }
204        return outFile; // may be null if write failed
205    }
206
207    public void write(PrintWriter out, ClassSymbol sym) throws IOException {
208        lazyInit();
209        try {
210            String cname = encode(sym.fullname, EncoderType.CLASS);
211            fileTop(out);
212            includes(out);
213            guardBegin(out, cname);
214            cppGuardBegin(out);
215
216            writeStatics(out, sym);
217            writeMethods(out, sym, cname);
218
219            cppGuardEnd(out);
220            guardEnd(out);
221        } catch (TypeSignature.SignatureException e) {
222            throw new IOException(e);
223        }
224    }
225    protected void writeStatics(PrintWriter out, ClassSymbol sym) throws IOException {
226        List<ClassSymbol> clist = new ArrayList<>();
227        for (ClassSymbol cd = sym; cd != null;
228                cd = (ClassSymbol) cd.getSuperclass().tsym) {
229            clist.add(cd);
230        }
231        /*
232         * list needs to be super-class, base-class1, base-class2 and so on,
233         * so we reverse class hierarchy
234         */
235        Collections.reverse(clist);
236        for (ClassSymbol cd : clist) {
237            for (Symbol i : cd.getEnclosedElements()) {
238                // consider only final, static and fields with ConstantExpressions
239                if (isFinal(i) && i.isStatic() && i.kind == VAR) {
240                    VarSymbol v = (VarSymbol) i;
241                    if (v.getConstantValue() != null) {
242                        Pair<ClassSymbol, VarSymbol> p = new Pair<>(sym, v);
243                        printStaticDefines(out, p);
244                    }
245                }
246            }
247        }
248    }
249    static void printStaticDefines(PrintWriter out, Pair<ClassSymbol, VarSymbol> p) {
250        ClassSymbol cls = p.fst;
251        VarSymbol f = p.snd;
252        Object value = f.getConstantValue();
253        String valueStr = null;
254        switch (f.asType().getKind()) {
255            case BOOLEAN:
256                valueStr = (((Boolean) value) ? "1L" : "0L");
257                break;
258            case BYTE: case SHORT: case INT:
259                valueStr = value.toString() + "L";
260                break;
261            case LONG:
262                // Visual C++ supports the i64 suffix, not LL.
263                valueStr = value.toString() + ((isWindows) ? "i64" : "LL");
264                break;
265            case CHAR:
266                Character ch = (Character) value;
267                valueStr = String.valueOf(((int) ch) & 0xffff) + "L";
268                break;
269            case FLOAT:
270                // bug compatible
271                float fv = ((Float) value).floatValue();
272                valueStr = (Float.isInfinite(fv))
273                        ? ((fv < 0) ? "-" : "") + "Inff"
274                        : value.toString() + "f";
275                break;
276            case DOUBLE:
277                // bug compatible
278                double d = ((Double) value).doubleValue();
279                valueStr = (Double.isInfinite(d))
280                        ? ((d < 0) ? "-" : "") + "InfD"
281                        : value.toString();
282                break;
283            default:
284                valueStr = null;
285        }
286        if (valueStr != null) {
287            out.print("#undef ");
288            String cname = encode(cls.getQualifiedName(), EncoderType.CLASS);
289            String fname = encode(f.getSimpleName(), EncoderType.FIELDSTUB);
290            out.println(cname + "_" + fname);
291            out.print("#define " + cname + "_");
292            out.println(fname + " " + valueStr);
293        }
294    }
295    protected void writeMethods(PrintWriter out, ClassSymbol sym, String cname)
296            throws IOException, TypeSignature.SignatureException {
297        List<Symbol> classmethods = sym.getEnclosedElements();
298        for (Symbol md : classmethods) {
299            if (isNative(md)) {
300                TypeSignature newtypesig = new TypeSignature(types);
301                CharSequence methodName = md.getSimpleName();
302                boolean isOverloaded = false;
303                for (Symbol md2 : classmethods) {
304                    if ((md2 != md)
305                            && (methodName.equals(md2.getSimpleName()))
306                            && isNative(md2)) {
307                        isOverloaded = true;
308                    }
309                }
310                out.println("/*");
311                out.println(" * Class:     " + cname);
312                out.println(" * Method:    " + encode(methodName, EncoderType.FIELDSTUB));
313                out.println(" * Signature: " + newtypesig.getSignature(md.type));
314                out.println(" */");
315                out.println("JNIEXPORT " + jniType(types.erasure(md.type.getReturnType()))
316                        + " JNICALL " + encodeMethod(md, sym, isOverloaded));
317                out.print("  (JNIEnv *, ");
318                out.print((md.isStatic())
319                        ? "jclass"
320                        : "jobject");
321                for (Type arg : types.erasure(md.type.getParameterTypes())) {
322                    out.print(", ");
323                    out.print(jniType(arg));
324                }
325                out.println(");");
326                out.println();
327            }
328        }
329    }
330    @SuppressWarnings("fallthrough")
331    protected final String jniType(Type t) {
332        switch (t.getKind()) {
333            case ARRAY: {
334                Type ct = ((Type.ArrayType)t).getComponentType();
335                switch (ct.getKind()) {
336                    case BOOLEAN:  return "jbooleanArray";
337                    case BYTE:     return "jbyteArray";
338                    case CHAR:     return "jcharArray";
339                    case SHORT:    return "jshortArray";
340                    case INT:      return "jintArray";
341                    case LONG:     return "jlongArray";
342                    case FLOAT:    return "jfloatArray";
343                    case DOUBLE:   return "jdoubleArray";
344                    case ARRAY:
345                    case DECLARED: return "jobjectArray";
346                    default: throw new Error(ct.toString());
347                }
348            }
349
350            case VOID:     return "void";
351            case BOOLEAN:  return "jboolean";
352            case BYTE:     return "jbyte";
353            case CHAR:     return "jchar";
354            case SHORT:    return "jshort";
355            case INT:      return "jint";
356            case LONG:     return "jlong";
357            case FLOAT:    return "jfloat";
358            case DOUBLE:   return "jdouble";
359            case DECLARED: {
360                if (t.tsym.type == syms.stringType) {
361                    return "jstring";
362                } else if (types.isAssignable(t, syms.throwableType)) {
363                    return "jthrowable";
364                } else if (types.isAssignable(t, syms.classType)) {
365                    return "jclass";
366                } else {
367                    return "jobject";
368                }
369            }
370        }
371
372        Assert.check(false, "jni unknown type");
373        return null; /* dead code. */
374    }
375
376    protected void  fileTop(PrintWriter out) {
377        out.println("/* DO NOT EDIT THIS FILE - it is machine generated */");
378    }
379
380    protected void includes(PrintWriter out) {
381        out.println("#include <jni.h>");
382    }
383
384    /*
385     * Deal with the C pre-processor.
386     */
387    protected void cppGuardBegin(PrintWriter out) {
388        out.println("#ifdef __cplusplus");
389        out.println("extern \"C\" {");
390        out.println("#endif");
391    }
392
393    protected void cppGuardEnd(PrintWriter out) {
394        out.println("#ifdef __cplusplus");
395        out.println("}");
396        out.println("#endif");
397    }
398
399    protected void guardBegin(PrintWriter out, String cname) {
400        out.println("/* Header for class " + cname + " */");
401        out.println();
402        out.println("#ifndef _Included_" + cname);
403        out.println("#define _Included_" + cname);
404    }
405
406    protected void guardEnd(PrintWriter out) {
407        out.println("#endif");
408    }
409
410    String encodeMethod(Symbol msym, ClassSymbol clazz,
411            boolean isOverloaded) throws TypeSignature.SignatureException {
412        StringBuilder result = new StringBuilder(100);
413        result.append("Java_");
414        /* JNI */
415        result.append(encode(clazz.flatname.toString(), EncoderType.JNI));
416        result.append('_');
417        result.append(encode(msym.getSimpleName(), EncoderType.JNI));
418        if (isOverloaded) {
419            TypeSignature typeSig = new TypeSignature(types);
420            StringBuilder sig = typeSig.getParameterSignature(msym.type);
421            result.append("__").append(encode(sig, EncoderType.JNI));
422        }
423        return result.toString();
424    }
425
426    static enum EncoderType {
427        CLASS,
428        FIELDSTUB,
429        FIELD,
430        JNI,
431        SIGNATURE
432    }
433    @SuppressWarnings("fallthrough")
434    static String encode(CharSequence name, EncoderType mtype) {
435        StringBuilder result = new StringBuilder(100);
436        int length = name.length();
437
438        for (int i = 0; i < length; i++) {
439            char ch = name.charAt(i);
440            if (isalnum(ch)) {
441                result.append(ch);
442                continue;
443            }
444            switch (mtype) {
445                case CLASS:
446                    switch (ch) {
447                        case '.':
448                        case '_':
449                            result.append("_");
450                            break;
451                        case '$':
452                            result.append("__");
453                            break;
454                        default:
455                            result.append(encodeChar(ch));
456                    }
457                    break;
458                case JNI:
459                    switch (ch) {
460                        case '/':
461                        case '.':
462                            result.append("_");
463                            break;
464                        case '_':
465                            result.append("_1");
466                            break;
467                        case ';':
468                            result.append("_2");
469                            break;
470                        case '[':
471                            result.append("_3");
472                            break;
473                        default:
474                            result.append(encodeChar(ch));
475                    }
476                    break;
477                case SIGNATURE:
478                    result.append(isprint(ch) ? ch : encodeChar(ch));
479                    break;
480                case FIELDSTUB:
481                    result.append(ch == '_' ? ch : encodeChar(ch));
482                    break;
483                default:
484                    result.append(encodeChar(ch));
485            }
486        }
487        return result.toString();
488    }
489
490    static String encodeChar(char ch) {
491        String s = Integer.toHexString(ch);
492        int nzeros = 5 - s.length();
493        char[] result = new char[6];
494        result[0] = '_';
495        for (int i = 1; i <= nzeros; i++) {
496            result[i] = '0';
497        }
498        for (int i = nzeros + 1, j = 0; i < 6; i++, j++) {
499            result[i] = s.charAt(j);
500        }
501        return new String(result);
502    }
503
504    /* Warning: Intentional ASCII operation. */
505    private static boolean isalnum(char ch) {
506        return ch <= 0x7f && /* quick test */
507                ((ch >= 'A' && ch <= 'Z')  ||
508                 (ch >= 'a' && ch <= 'z')  ||
509                 (ch >= '0' && ch <= '9'));
510    }
511
512    /* Warning: Intentional ASCII operation. */
513    private static boolean isprint(char ch) {
514        return ch >= 32 && ch <= 126;
515    }
516
517    private static class TypeSignature {
518        static class SignatureException extends Exception {
519            private static final long serialVersionUID = 1L;
520            SignatureException(String reason) {
521                super(reason);
522            }
523        }
524
525        JavacElements elems;
526        Types    types;
527
528        /* Signature Characters */
529        private static final String SIG_VOID                   = "V";
530        private static final String SIG_BOOLEAN                = "Z";
531        private static final String SIG_BYTE                   = "B";
532        private static final String SIG_CHAR                   = "C";
533        private static final String SIG_SHORT                  = "S";
534        private static final String SIG_INT                    = "I";
535        private static final String SIG_LONG                   = "J";
536        private static final String SIG_FLOAT                  = "F";
537        private static final String SIG_DOUBLE                 = "D";
538        private static final String SIG_ARRAY                  = "[";
539        private static final String SIG_CLASS                  = "L";
540
541        public TypeSignature(Types types) {
542            this.types = types;
543        }
544
545        StringBuilder getParameterSignature(Type mType)
546                throws SignatureException {
547            StringBuilder result = new StringBuilder();
548            for (Type pType : mType.getParameterTypes()) {
549                result.append(getJvmSignature(pType));
550            }
551            return result;
552        }
553
554        StringBuilder getReturnSignature(Type mType)
555                throws SignatureException {
556            return getJvmSignature(mType.getReturnType());
557        }
558
559        StringBuilder getSignature(Type mType) throws SignatureException {
560            StringBuilder sb = new StringBuilder();
561            sb.append("(").append(getParameterSignature(mType)).append(")");
562            sb.append(getReturnSignature(mType));
563            return sb;
564        }
565
566        /*
567         * Returns jvm internal signature.
568         */
569        static class JvmTypeVisitor extends JNIWriter.SimpleTypeVisitor<Type, StringBuilder> {
570
571            @Override
572            public Type visitClassType(Type.ClassType t, StringBuilder s) {
573                setDeclaredType(t, s);
574                return null;
575            }
576
577            @Override
578            public Type visitArrayType(Type.ArrayType t, StringBuilder s) {
579                s.append("[");
580                return t.getComponentType().accept(this, s);
581            }
582
583            @Override
584            public Type visitType(Type t, StringBuilder s) {
585                if (t.isPrimitiveOrVoid()) {
586                    s.append(getJvmPrimitiveSignature(t));
587                    return null;
588                }
589                return t.accept(this, s);
590            }
591            private void setDeclaredType(Type t, StringBuilder s) {
592                    String classname = t.tsym.getQualifiedName().toString();
593                    classname = classname.replace('.', '/');
594                    s.append("L").append(classname).append(";");
595            }
596            private String getJvmPrimitiveSignature(Type t) {
597                switch (t.getKind()) {
598                    case VOID:      return SIG_VOID;
599                    case BOOLEAN:   return SIG_BOOLEAN;
600                    case BYTE:      return SIG_BYTE;
601                    case CHAR:      return SIG_CHAR;
602                    case SHORT:     return SIG_SHORT;
603                    case INT:       return SIG_INT;
604                    case LONG:      return SIG_LONG;
605                    case FLOAT:     return SIG_FLOAT;
606                    case DOUBLE:    return SIG_DOUBLE;
607                    default:
608                        Assert.error("unknown type: should not happen");
609                }
610                return null;
611            }
612        }
613
614        StringBuilder getJvmSignature(Type type) {
615            Type t = types.erasure(type);
616            StringBuilder sig = new StringBuilder();
617            JvmTypeVisitor jv = new JvmTypeVisitor();
618            jv.visitType(t, sig);
619            return sig;
620        }
621    }
622
623    static class SimpleTypeVisitor<R, P> implements Type.Visitor<R, P> {
624
625        protected final R DEFAULT_VALUE;
626
627        protected SimpleTypeVisitor() {
628            DEFAULT_VALUE = null;
629        }
630
631        protected SimpleTypeVisitor(R defaultValue) {
632            DEFAULT_VALUE = defaultValue;
633        }
634
635        protected R defaultAction(Type t, P p) {
636            return DEFAULT_VALUE;
637        }
638
639        @Override
640        public R visitClassType(Type.ClassType t, P p) {
641            return defaultAction(t, p);
642        }
643
644        @Override
645        public R visitWildcardType(Type.WildcardType t, P p) {
646            return defaultAction(t, p);
647        }
648
649        @Override
650        public R visitArrayType(Type.ArrayType t, P p) {
651            return defaultAction(t, p);
652        }
653
654        @Override
655        public R visitMethodType(Type.MethodType t, P p) {
656            return defaultAction(t, p);
657        }
658
659        @Override
660        public R visitPackageType(Type.PackageType t, P p) {
661            return defaultAction(t, p);
662        }
663
664        @Override
665        public R visitTypeVar(Type.TypeVar t, P p) {
666            return defaultAction(t, p);
667        }
668
669        @Override
670        public R visitCapturedType(Type.CapturedType t, P p) {
671            return defaultAction(t, p);
672        }
673
674        @Override
675        public R visitForAll(Type.ForAll t, P p) {
676            return defaultAction(t, p);
677        }
678
679        @Override
680        public R visitUndetVar(Type.UndetVar t, P p) {
681            return defaultAction(t, p);
682        }
683
684        @Override
685        public R visitErrorType(Type.ErrorType t, P p) {
686            return defaultAction(t, p);
687        }
688
689        @Override
690        public R visitType(Type t, P p) {
691            return defaultAction(t, p);
692        }
693
694        @Override
695        public R visitModuleType(Type.ModuleType t, P p) {
696            return defaultAction(t, p);
697        }
698    }
699}
700