1 /*
   2  * Copyright (c) 2014, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package com.sun.tools.jextract;
  24 
  25 import java.foreign.layout.Layout;
  26 import java.nio.file.Path;
  27 import java.util.Collections;
  28 import java.util.HashMap;
  29 import java.util.HashSet;
  30 import java.util.LinkedHashSet;
  31 import java.util.List;
  32 import java.util.Map;
  33 import java.util.Set;
  34 import java.util.logging.Level;
  35 import java.util.logging.Logger;
  36 
  37 import com.sun.tools.jextract.parser.MacroParser;
  38 import jdk.internal.clang.SourceLocation;
  39 import jdk.internal.clang.Type;
  40 import jdk.internal.org.objectweb.asm.AnnotationVisitor;
  41 import jdk.internal.org.objectweb.asm.ClassVisitor;
  42 import jdk.internal.org.objectweb.asm.ClassWriter;
  43 import jdk.internal.org.objectweb.asm.MethodVisitor;
  44 import com.sun.tools.jextract.tree.EnumTree;
  45 import com.sun.tools.jextract.tree.FieldTree;
  46 import com.sun.tools.jextract.tree.FunctionTree;
  47 import com.sun.tools.jextract.tree.MacroTree;
  48 import com.sun.tools.jextract.tree.SimpleTreeVisitor;
  49 import com.sun.tools.jextract.tree.StructTree;
  50 import com.sun.tools.jextract.tree.Tree;
  51 import com.sun.tools.jextract.tree.TypedefTree;
  52 import com.sun.tools.jextract.tree.VarTree;
  53 
  54 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
  55 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ANNOTATION;
  56 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE;
  57 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  58 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  59 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS;
  60 import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN;
  61 import static jdk.internal.org.objectweb.asm.Opcodes.DRETURN;
  62 import static jdk.internal.org.objectweb.asm.Opcodes.FRETURN;
  63 import static jdk.internal.org.objectweb.asm.Opcodes.I2B;
  64 import static jdk.internal.org.objectweb.asm.Opcodes.I2C;
  65 import static jdk.internal.org.objectweb.asm.Opcodes.I2S;
  66 import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
  67 import static jdk.internal.org.objectweb.asm.Opcodes.LRETURN;
  68 import static jdk.internal.org.objectweb.asm.Opcodes.V1_8;
  69 
  70 /**
  71  * Scan a header file and generate classes for entities defined in that header
  72  * file. Tree visitor visit methods return true/false depending on whether a
  73  * particular Tree is processed or skipped.
  74  */
  75 class AsmCodeFactory extends SimpleTreeVisitor<Boolean, JType> {
  76     private static final String ANNOTATION_PKG_PREFIX = "Ljava/foreign/annotations/";
  77     private static final String NATIVE_CALLBACK = ANNOTATION_PKG_PREFIX + "NativeCallback;";
  78     private static final String NATIVE_HEADER = ANNOTATION_PKG_PREFIX + "NativeHeader;";
  79     private static final String NATIVE_LOCATION = ANNOTATION_PKG_PREFIX + "NativeLocation;";
  80     private static final String NATIVE_STRUCT = ANNOTATION_PKG_PREFIX + "NativeStruct;";
  81     private static final String NATIVE_FUNCTION = ANNOTATION_PKG_PREFIX + "NativeFunction;";
  82     private static final String NATIVE_GETTER = ANNOTATION_PKG_PREFIX + "NativeGetter;";
  83     private static final String NATIVE_SETTER = ANNOTATION_PKG_PREFIX + "NativeSetter;";
  84     private static final String NATIVE_ADDRESSOF = ANNOTATION_PKG_PREFIX + "NativeAddressof;";
  85     private static final String NATIVE_NUM_CONST = ANNOTATION_PKG_PREFIX + "NativeNumericConstant;";
  86     private static final String NATIVE_STR_CONST = ANNOTATION_PKG_PREFIX + "NativeStringConstant;";
  87 
  88     private final ClassWriter global_cw;
  89     private final Set<Layout> global_layouts = new LinkedHashSet<>();
  90     protected final String headerClassName;
  91     protected final HeaderFile headerFile;
  92     protected final Map<String, byte[]> types;
  93     protected final List<String> libraryNames;
  94     protected final List<String> libraryPaths;
  95     protected final boolean noNativeLocations;
  96 
  97     protected final Log log;
  98 
  99     AsmCodeFactory(Context ctx, HeaderFile header) {
 100         this.log = ctx.log;
 101         log.print(Level.INFO, () -> "Instantiate AsmCodeFactory for " + header.path);
 102         this.headerFile = header;
 103         this.headerClassName = Utils.toInternalName(headerFile.pkgName, headerFile.headerClsName);
 104         this.global_cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 105         this.types = new HashMap<>();
 106         this.libraryNames = ctx.options.libraryNames;
 107         this.libraryPaths = ctx.options.recordLibraryPath? ctx.options.libraryPaths : null;
 108         this.noNativeLocations = ctx.options.noNativeLocations;
 109         global_cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
 110                 headerClassName,
 111                 null, "java/lang/Object", null);
 112     }
 113 
 114     public Map<String, byte[]> generateNativeHeader(List<Tree> decls) {
 115         //generate all decls
 116         decls.forEach(this::generateDecl);
 117         //generate functional interfaces
 118         headerFile.dictionary().functionalInterfaces()
 119                 .forEach(fi -> createFunctionalInterface((JType.FunctionalInterfaceType)fi));
 120 
 121         //generate header intf
 122         AnnotationVisitor av = global_cw.visitAnnotation(NATIVE_HEADER, true);
 123         av.visit("path", headerFile.path.toAbsolutePath().toString());
 124         if (!libraryNames.isEmpty()) {
 125             AnnotationVisitor libNames = av.visitArray("libraries");
 126             for (String name : libraryNames) {
 127                 libNames.visit(null, name);
 128             }
 129             libNames.visitEnd();
 130             if (libraryPaths != null && !libraryPaths.isEmpty()) {
 131                 AnnotationVisitor libPaths = av.visitArray("libraryPaths");
 132                 for (String path : libraryPaths) {
 133                     libPaths.visit(null, path);
 134                 }
 135                 libPaths.visitEnd();
 136             }
 137         }
 138 
 139         AnnotationVisitor resolutionContext = av.visitArray("resolutionContext");
 140         headerFile.dictionary().resolutionRoots()
 141                 .forEach(jt -> resolutionContext.visit(null,
 142                         jdk.internal.org.objectweb.asm.Type.getObjectType(jt.clsName)));
 143         resolutionContext.visitEnd();
 144         AnnotationVisitor globals = av.visitArray("globals");
 145         global_layouts.stream().map(Layout::toString).forEach(s -> globals.visit(null, s));
 146         globals.visitEnd();
 147         av.visitEnd();
 148         global_cw.visitEnd();
 149         addClassIfNeeded(headerClassName, global_cw.toByteArray());
 150         return Collections.unmodifiableMap(types);
 151     }
 152 
 153     private void handleException(Exception ex) {
 154         log.printError("cannot.write.class.file", headerFile.pkgName + "." + headerFile.headerClsName, ex);
 155         log.printStackTrace(ex);
 156     }
 157 
 158     private void annotateNativeLocation(ClassVisitor cw, Tree tree) {
 159         if (! noNativeLocations) {
 160             AnnotationVisitor av = cw.visitAnnotation(NATIVE_LOCATION, true);
 161             SourceLocation src = tree.location();
 162             SourceLocation.Location loc = src.getFileLocation();
 163             Path p = loc.path();
 164             av.visit("file", p == null ? "<builtin>" : p.toAbsolutePath().toString());
 165             av.visit("line", loc.line());
 166             av.visit("column", loc.column());
 167             av.visitEnd();
 168         }
 169     }
 170 
 171     private void addClassIfNeeded(String clsName, byte[] bytes) {
 172         if (null != types.put(clsName, bytes)) {
 173             log.printWarning("warn.class.overwritten", clsName);
 174         }
 175     }
 176 
 177     private static boolean isBitField(Tree tree) {
 178         return tree instanceof FieldTree && ((FieldTree)tree).isBitField();
 179     }
 180 
 181     /**
 182      *
 183      * @param cw ClassWriter for the struct
 184      * @param tree The Tree
 185      * @param parentType The struct type
 186      */
 187     private boolean addField(ClassVisitor cw, Tree tree, Type parentType) {
 188         String fieldName = tree.name();
 189         assert !fieldName.isEmpty();
 190         Type type = tree.type();
 191         JType jt = headerFile.dictionary().lookup(type);
 192         assert (jt != null);
 193         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$get",
 194                 "()" + jt.getDescriptor(), "()" + jt.getSignature(false), null);
 195         jt.visitInner(cw);
 196 
 197         if (! noNativeLocations) {
 198             AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true);
 199             SourceLocation src = tree.location();
 200             SourceLocation.Location loc = src.getFileLocation();
 201             Path p = loc.path();
 202             av.visit("file", p == null ? "<builtin>" : p.toAbsolutePath().toString());
 203             av.visit("line", loc.line());
 204             av.visit("column", loc.column());
 205             av.visitEnd();
 206         }
 207 
 208         AnnotationVisitor av = mv.visitAnnotation(NATIVE_GETTER, true);
 209         av.visit("value", fieldName);
 210         av.visitEnd();
 211 
 212         mv.visitEnd();
 213         mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$set",
 214                 "(" + jt.getDescriptor() + ")V",
 215                 "(" + jt.getSignature(true) + ")V", null);
 216         jt.visitInner(cw);
 217         av = mv.visitAnnotation(NATIVE_SETTER, true);
 218         av.visit("value", fieldName);
 219         av.visitEnd();
 220         mv.visitEnd();
 221 
 222         if (tree instanceof VarTree || !isBitField(tree)) {
 223             JType ptrType = JType.GenericType.ofPointer(jt);
 224             mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$ptr",
 225                     "()" + ptrType.getDescriptor(), "()" + ptrType.getSignature(false), null);
 226             ptrType.visitInner(cw);
 227             av = mv.visitAnnotation(NATIVE_ADDRESSOF, true);
 228             av.visit("value", fieldName);
 229             av.visitEnd();
 230             mv.visitEnd();
 231         }
 232 
 233         return true;
 234     }
 235 
 236     @Override
 237     public Boolean visitVar(VarTree varTree, JType jt) {
 238         global_layouts.add(varTree.layout().withAnnotation(Layout.NAME, varTree.name()));
 239         return addField(global_cw, varTree, null);
 240     }
 241 
 242     private void addConstant(ClassWriter cw, SourceLocation src, String name, JType type, Object value) {
 243         String desc = "()" + type.getDescriptor();
 244         String sig = "()" + type.getSignature(false);
 245         MethodVisitor mv = global_cw.visitMethod(ACC_ABSTRACT | ACC_PUBLIC, name, desc, sig, null);
 246         type.visitInner(cw);
 247 
 248         if (! noNativeLocations) {
 249             AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true);
 250             SourceLocation.Location loc = src.getFileLocation();
 251             Path p = loc.path();
 252             av.visit("file", p == null ? "<builtin>" : p.toAbsolutePath().toString());
 253             av.visit("line", loc.line());
 254             av.visit("column", loc.column());
 255             av.visitEnd();
 256         }
 257 
 258         if (value instanceof String) {
 259             AnnotationVisitor av = mv.visitAnnotation(NATIVE_STR_CONST, true);
 260             av.visit("value", value);
 261             av.visitEnd();
 262         } else {
 263             //numeric (int, long or double)
 264             final long longValue;
 265             if (value instanceof Integer) {
 266                 longValue = (Integer)value;
 267             } else if (value instanceof Long) {
 268                 longValue = (Long)value;
 269             } else if (value instanceof Double) {
 270                 longValue = Double.doubleToRawLongBits((Double)value);
 271             } else {
 272                 throw new IllegalStateException("Unexpected constant: " + value);
 273             }
 274             AnnotationVisitor av = mv.visitAnnotation(NATIVE_NUM_CONST, true);
 275             av.visit("value", longValue);
 276             av.visitEnd();
 277         }
 278         mv.visitEnd();
 279     }
 280 
 281     @Override
 282     public Boolean visitStruct(StructTree structTree, JType jt) {
 283         //generate nested structs recursively
 284         structTree.nestedTypes().forEach(this::generateDecl);
 285 
 286         if (structTree.isAnonymous()) {
 287             //skip anonymous
 288             return false;
 289         }
 290         String nativeName = structTree.name();
 291         Type type = structTree.type();
 292         log.print(Level.FINE, () -> "Create struct: " + nativeName);
 293 
 294         String intf = Utils.toClassName(nativeName);
 295         String name = headerClassName + "$" + intf;
 296 
 297         log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName);
 298         /* FIXME: Member interface is implicit static, also ASM.CheckClassAdapter is not
 299          * taking static as a valid flag, so comment this out during development.
 300          */
 301         global_cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 302         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 303         cw.visit(V1_8, ACC_PUBLIC /*| ACC_STATIC*/ | ACC_INTERFACE | ACC_ABSTRACT,
 304                 name, "Ljava/lang/Object;Ljava/foreign/memory/Struct<L" + name + ";>;",
 305                 "java/lang/Object", new String[] {"java/foreign/memory/Struct"});
 306         annotateNativeLocation(cw, structTree);
 307 
 308         AnnotationVisitor av = cw.visitAnnotation(NATIVE_STRUCT, true);
 309         Layout structLayout = structTree.layout();
 310         av.visit("value", structLayout.toString());
 311         av.visitEnd();
 312         cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 313 
 314         // fields
 315         structTree.fields().forEach(fieldTree -> addField(cw, fieldTree, type));
 316         // Write class
 317         cw.visitEnd();
 318         addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray());
 319         return true;
 320     }
 321 
 322     @Override
 323     public Boolean visitEnum(EnumTree enumTree, JType jt) {
 324         // define enum constants in global_cw
 325         enumTree.constants().forEach(constant -> addConstant(global_cw,
 326                 constant.location(),
 327                 constant.name(),
 328                 headerFile.dictionary().lookup(constant.type()),
 329                 constant.enumConstant().get()));
 330 
 331         if (enumTree.name().isEmpty()) {
 332             // We are done with anonymous enum
 333             return true;
 334         }
 335 
 336         // generate annotation class for named enum
 337         createAnnotationCls(enumTree);
 338         return true;
 339     }
 340 
 341     private void createAnnotationCls(Tree tree) {
 342         String nativeName = tree.name();
 343         log.print(Level.FINE, () -> "Create annotation for: " + nativeName);
 344 
 345         String intf = Utils.toClassName(nativeName);
 346         String name = headerClassName + "$" + intf;
 347 
 348         log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName);
 349         global_cw.visitInnerClass(name, headerClassName, intf,
 350                 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION);
 351         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 352         String[] superAnno = { "java/lang/annotation/Annotation" };
 353         cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION,
 354                 name, null, "java/lang/Object", superAnno);
 355         annotateNativeLocation(cw, tree);
 356         AnnotationVisitor av = cw.visitAnnotation("Ljava/lang/annotation/Target;", true);
 357         av.visitEnum("value", "Ljava/lang/annotation/ElementType;", "TYPE_USE");
 358         av.visitEnd();
 359         av = cw.visitAnnotation("Ljava/lang/annotation/Retention;", true);
 360         av.visitEnum("value", "Ljava/lang/annotation/RetentionPolicy;", "RUNTIME");
 361         av.visitEnd();
 362         cw.visitInnerClass(name, headerClassName, intf,
 363                 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION);
 364         // Write class
 365         cw.visitEnd();
 366         addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray());
 367     }
 368 
 369     private void createFunctionalInterface(JType.FunctionalInterfaceType fnif) {
 370         JType.Function fn = fnif.getFunction();
 371         String intf;
 372         String nativeName;
 373         String nDesc = fnif.getFunction().getNativeDescriptor();
 374         intf = fnif.getSimpleName();
 375         nativeName = "anonymous function";
 376         log.print(Level.FINE, () -> "Create FunctionalInterface " + intf);
 377 
 378         final String name = headerClassName + "$" + intf;
 379 
 380         log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName + nDesc);
 381         global_cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 382         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 383         cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
 384                 name, "Ljava/lang/Object;",
 385                 "java/lang/Object", new String[0]);
 386         AnnotationVisitor av = cw.visitAnnotation(
 387                 "Ljava/lang/FunctionalInterface;", true);
 388         av.visitEnd();
 389         av = cw.visitAnnotation(NATIVE_CALLBACK, true);
 390         av.visit("value", nDesc);
 391         av.visitEnd();
 392         cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 393 
 394         // add the method
 395 
 396         int flags = ACC_PUBLIC | ACC_ABSTRACT;
 397         if (fn.isVarArgs) {
 398             flags |= ACC_VARARGS;
 399         }
 400         MethodVisitor mv = cw.visitMethod(flags, "fn",
 401                 fn.getDescriptor(), fn.getSignature(false), null);
 402         fn.visitInner(cw);
 403         mv.visitEnd();
 404         // Write class
 405         cw.visitEnd();
 406         addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray());
 407     }
 408 
 409     @Override
 410     public Boolean visitTypedef(TypedefTree typedefTree, JType jt) {
 411         createAnnotationCls(typedefTree);
 412         return true;
 413     }
 414 
 415     @Override
 416     public Boolean visitTree(Tree tree, JType jt) {
 417         log.print(Level.WARNING, () -> "Unsupported declaration tree:");
 418         log.print(Level.WARNING, () -> tree.toString());
 419         return true;
 420     }
 421 
 422     @Override
 423     public Boolean visitFunction(FunctionTree funcTree, JType jt) {
 424         assert (jt instanceof JType.Function);
 425         JType.Function fn = (JType.Function)jt;
 426         log.print(Level.FINE, () -> "Add method: " + fn.getSignature(false));
 427         int flags = ACC_PUBLIC | ACC_ABSTRACT;
 428         if (fn.isVarArgs) {
 429             flags |= ACC_VARARGS;
 430         }
 431         MethodVisitor mv = global_cw.visitMethod(flags,
 432                 funcTree.name(), fn.getDescriptor(), fn.getSignature(false), null);
 433         jt.visitInner(global_cw);
 434         final int arg_cnt = funcTree.numParams();
 435         for (int i = 0; i < arg_cnt; i++) {
 436             String name = funcTree.paramName(i);
 437             final int tmp = i;
 438             log.print(Level.FINER, () -> "  arg " + tmp + ": " + name);
 439             mv.visitParameter(name, 0);
 440         }
 441 
 442         if (! noNativeLocations) {
 443             AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true);
 444             SourceLocation src = funcTree.location();
 445             SourceLocation.Location loc = src.getFileLocation();
 446             Path p = loc.path();
 447             av.visit("file", p == null ? "<builtin>" : p.toAbsolutePath().toString());
 448             av.visit("line", loc.line());
 449             av.visit("column", loc.column());
 450             av.visitEnd();
 451         }
 452 
 453         Type type = funcTree.type();
 454         final String descStr = Utils.getFunction(type).toString();
 455 
 456         AnnotationVisitor av = mv.visitAnnotation(NATIVE_FUNCTION, true);
 457         av.visit("value", descStr);
 458         av.visitEnd();
 459 
 460         mv.visitEnd();
 461         return true;
 462     }
 463 
 464     private AsmCodeFactory generateDecl(Tree tree) {
 465         try {
 466             log.print(Level.FINE, () -> "Process tree " + tree.name());
 467             tree.accept(this, tree.isPreprocessing() ? null : headerFile.dictionary().lookup(tree.type()));
 468         } catch (Exception ex) {
 469             handleException(ex);
 470             log.print(Level.WARNING, () -> "Tree causing above exception is: " + tree.name());
 471             log.print(Level.WARNING, () -> tree.toString());
 472         }
 473         return this;
 474     }
 475 
 476     @Override
 477     public Boolean visitMacro(MacroTree macroTree, JType jt) {
 478         if (!macroTree.isConstant()) {
 479             log.print(Level.FINE, () -> "Skipping unrecognized object-like macro " + macroTree.name());
 480             return false;
 481         }
 482         String name = macroTree.name();
 483         MacroParser.Macro macro = macroTree.macro().get();
 484         log.print(Level.FINE, () -> "Adding macro " + name);
 485 
 486         addConstant(global_cw, macroTree.location(), Utils.toMacroName(name), macro.type(), macro.value());
 487 
 488         return true;
 489     }
 490 }