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.io.PrintWriter;
  27 import java.nio.file.Path;
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.HashMap;
  31 import java.util.HashSet;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.Set;
  35 import java.util.logging.Logger;
  36 
  37 import jdk.internal.clang.SourceLocation;
  38 import jdk.internal.clang.Type;
  39 import jdk.internal.org.objectweb.asm.AnnotationVisitor;
  40 import jdk.internal.org.objectweb.asm.ClassVisitor;
  41 import jdk.internal.org.objectweb.asm.ClassWriter;
  42 import jdk.internal.org.objectweb.asm.MethodVisitor;
  43 import com.sun.tools.jextract.tree.EnumTree;
  44 import com.sun.tools.jextract.tree.FieldTree;
  45 import com.sun.tools.jextract.tree.FunctionTree;
  46 import com.sun.tools.jextract.tree.MacroTree;
  47 import com.sun.tools.jextract.tree.SimpleTreeVisitor;
  48 import com.sun.tools.jextract.tree.StructTree;
  49 import com.sun.tools.jextract.tree.Tree;
  50 import com.sun.tools.jextract.tree.TypedefTree;
  51 import com.sun.tools.jextract.tree.VarTree;
  52 
  53 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
  54 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ANNOTATION;
  55 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE;
  56 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  57 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  58 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS;
  59 import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN;
  60 import static jdk.internal.org.objectweb.asm.Opcodes.DRETURN;
  61 import static jdk.internal.org.objectweb.asm.Opcodes.FRETURN;
  62 import static jdk.internal.org.objectweb.asm.Opcodes.I2C;
  63 import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
  64 import static jdk.internal.org.objectweb.asm.Opcodes.LRETURN;
  65 import static jdk.internal.org.objectweb.asm.Opcodes.V1_8;
  66 
  67 /**
  68  * Scan a header file and generate classes for entities defined in that header
  69  * file. Tree visitor visit methods return true/false depending on whether a
  70  * particular Tree is processed or skipped.
  71  */
  72 class AsmCodeFactory extends SimpleTreeVisitor<Boolean, JType> {
  73     private static final String ANNOTATION_PKG_PREFIX = "Ljava/foreign/annotations/";
  74     private static final String NATIVE_CALLBACK = ANNOTATION_PKG_PREFIX + "NativeCallback;";
  75     private static final String NATIVE_HEADER = ANNOTATION_PKG_PREFIX + "NativeHeader;";
  76     private static final String NATIVE_LOCATION = ANNOTATION_PKG_PREFIX + "NativeLocation;";
  77     private static final String NATIVE_STRUCT = ANNOTATION_PKG_PREFIX + "NativeStruct;";
  78 
  79     private final ClassWriter global_cw;
  80     // to avoid duplicate generation of methods, field accessors, macros
  81     private final Set<String> global_methods = new HashSet<>();
  82     private final Set<String> global_fields = new HashSet<>();
  83     private final Set<String> global_macros = new HashSet<>();
  84     private final List<String> headerDeclarations = new ArrayList<>();
  85     protected final String headerClassName;
  86     protected final HeaderFile headerFile;
  87     protected final Map<String, byte[]> types;
  88     protected final List<String> libraryNames;
  89     protected final List<String> libraryPaths;
  90     protected final PrintWriter err;
  91     protected final boolean noNativeLocations;
  92     protected final Logger logger = Logger.getLogger(getClass().getPackage().getName());
  93 
  94     AsmCodeFactory(Context ctx, HeaderFile header) {
  95         logger.info(() -> "Instantiate AsmCodeFactory for " + header.path);
  96         this.headerFile = header;
  97         this.headerClassName = Utils.toInternalName(headerFile.pkgName, headerFile.clsName);
  98         this.global_cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
  99         this.types = new HashMap<>();
 100         this.libraryNames = ctx.libraryNames;
 101         this.libraryPaths = ctx.libraryPaths;
 102         this.err = ctx.err;
 103         this.noNativeLocations = ctx.noNativeLocations;
 104         global_cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
 105                 headerClassName,
 106                 null, "java/lang/Object", null);
 107     }
 108 
 109     public Map<String, byte[]> generateNativeHeader(List<Tree> decls) {
 110         //generate all decls
 111         decls.forEach(this::generateDecl);
 112         //generate functional interfaces
 113         headerFile.dictionary().functionalInterfaces()
 114                 .forEach(fi -> createFunctionalInterface((JType.FunctionalInterfaceType)fi));
 115 
 116         //generate header intf
 117         AnnotationVisitor av = global_cw.visitAnnotation(NATIVE_HEADER, true);
 118         av.visit("path", headerFile.path.toAbsolutePath().toString());
 119         if (!libraryNames.isEmpty()) {
 120             AnnotationVisitor libNames = av.visitArray("libraries");
 121             for (String name : libraryNames) {
 122                 libNames.visit(null, name);
 123             }
 124             libNames.visitEnd();
 125             if (!libraryPaths.isEmpty()) {
 126                 AnnotationVisitor libPaths = av.visitArray("libraryPaths");
 127                 for (String path : libraryPaths) {
 128                     libPaths.visit(null, path);
 129                 }
 130                 libPaths.visitEnd();
 131             }
 132         }
 133         av.visit("declarations", String.join(" ", headerDeclarations));
 134         av.visitEnd();
 135         global_cw.visitEnd();
 136         addClassIfNeeded(headerClassName, global_cw.toByteArray());
 137         return Collections.unmodifiableMap(types);
 138     }
 139 
 140     private void handleException(Exception ex) {
 141         err.println(Main.format("cannot.write.class.file", headerFile.pkgName + "." + headerFile.clsName, ex));
 142         if (Main.DEBUG) {
 143             ex.printStackTrace(err);
 144         }
 145     }
 146 
 147     private void annotateNativeLocation(ClassVisitor cw, Tree tree) {
 148         if (! noNativeLocations) {
 149             AnnotationVisitor av = cw.visitAnnotation(NATIVE_LOCATION, true);
 150             SourceLocation src = tree.location();
 151             SourceLocation.Location loc = src.getFileLocation();
 152             Path p = loc.path();
 153             av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString());
 154             av.visit("line", loc.line());
 155             av.visit("column", loc.column());
 156             av.visitEnd();
 157         }
 158     }
 159 
 160     private void addClassIfNeeded(String clsName, byte[] bytes) {
 161         if (null != types.put(clsName, bytes)) {
 162             err.println(Main.format("warn.class.overwritten", clsName));
 163         }
 164     }
 165 
 166     private static boolean isBitField(Tree tree) {
 167         return tree instanceof FieldTree && ((FieldTree)tree).isBitField();
 168     }
 169 
 170     /**
 171      *
 172      * @param cw ClassWriter for the struct
 173      * @param tree The Tree
 174      * @param parentType The struct type
 175      */
 176     private boolean addField(ClassVisitor cw, Tree tree, Type parentType) {
 177         String fieldName = tree.name();
 178         assert !fieldName.isEmpty();
 179         Type type = tree.type();
 180         JType jt = headerFile.dictionary().lookup(type);
 181         assert (jt != null);
 182         if (cw == global_cw) {
 183             String uniqueName = fieldName + "." + jt.getDescriptor();
 184             if (! global_fields.add(uniqueName)) {
 185                 return false; // added already
 186             }
 187         }
 188         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$get",
 189                 "()" + jt.getDescriptor(), "()" + jt.getSignature(false), null);
 190 
 191         if (! noNativeLocations) {
 192             AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true);
 193             SourceLocation src = tree.location();
 194             SourceLocation.Location loc = src.getFileLocation();
 195             Path p = loc.path();
 196             av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString());
 197             av.visit("line", loc.line());
 198             av.visit("column", loc.column());
 199             av.visitEnd();
 200         }
 201 
 202         mv.visitEnd();
 203         mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$set",
 204                 "(" + jt.getDescriptor() + ")V",
 205                 "(" + jt.getSignature(true) + ")V", null);
 206         mv.visitEnd();
 207         if (tree instanceof VarTree || !isBitField(tree)) {
 208             JType ptrType = JType.GenericType.ofPointer(jt);
 209             mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$ptr",
 210                     "()" + ptrType.getDescriptor(), "()" + ptrType.getSignature(false), null);
 211             mv.visitEnd();
 212         }
 213 
 214         return true;
 215     }
 216 
 217     @Override
 218     public Boolean visitVar(VarTree varTree, JType jt) {
 219         if (addField(global_cw, varTree, null)) {
 220             Layout layout = varTree.layout();
 221             String descStr = decorateAsAccessor(varTree, layout).toString();
 222             addHeaderDecl(varTree.name(), descStr);
 223             return true;
 224         } else {
 225             return false;
 226         }
 227     }
 228 
 229     private void addHeaderDecl(String symbol, String desc) {
 230         headerDeclarations.add(String.format("%s=%s", symbol, desc));
 231     }
 232 
 233     private void addConstant(ClassWriter cw, FieldTree fieldTree) {
 234         assert (fieldTree.isEnumConstant());
 235         String name = fieldTree.name();
 236         String desc = headerFile.dictionary().lookup(fieldTree.type()).getDescriptor();
 237         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, "()" + desc, null, null);
 238         mv.visitCode();
 239         if (desc.length() != 1) {
 240             throw new AssertionError("expected single char descriptor: " + desc);
 241         }
 242         switch (desc.charAt(0)) {
 243             case 'J':
 244                 long lvalue = fieldTree.enumConstant().get();
 245                 mv.visitLdcInsn(lvalue);
 246                 mv.visitInsn(LRETURN);
 247                 break;
 248             case 'I':
 249                 int ivalue = fieldTree.enumConstant().get().intValue();
 250                 mv.visitLdcInsn(ivalue);
 251                 mv.visitInsn(IRETURN);
 252                 break;
 253             default:
 254                 throw new AssertionError("should not reach here");
 255         }
 256         mv.visitMaxs(1, 1);
 257         mv.visitEnd();
 258     }
 259 
 260     @Override
 261     public Boolean visitStruct(StructTree structTree, JType jt) {
 262         //generate nested structs recursively
 263         structTree.nestedTypes().forEach(this::generateDecl);
 264 
 265         if (structTree.isAnonymous()) {
 266             //skip anonymous
 267             return false;
 268         }
 269         String nativeName = structTree.name();
 270         Type type = structTree.type();
 271         logger.fine(() -> "Create struct: " + nativeName);
 272 
 273         String intf = Utils.toClassName(nativeName);
 274         String name = headerClassName + "$" + intf;
 275 
 276         logger.fine(() -> "Define class " + name + " for native type " + nativeName);
 277         /* FIXME: Member interface is implicit static, also ASM.CheckClassAdapter is not
 278          * taking static as a valid flag, so comment this out during development.
 279          */
 280         global_cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 281         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 282         cw.visit(V1_8, ACC_PUBLIC /*| ACC_STATIC*/ | ACC_INTERFACE | ACC_ABSTRACT,
 283                 name, "Ljava/lang/Object;Ljava/foreign/memory/Struct<L" + name + ";>;",
 284                 "java/lang/Object", new String[] {"java/foreign/memory/Struct"});
 285         annotateNativeLocation(cw, structTree);
 286 
 287         AnnotationVisitor av = cw.visitAnnotation(NATIVE_STRUCT, true);
 288         Layout structLayout = structTree.layout(this::decorateAsAccessor);
 289         av.visit("value", structLayout.toString());
 290         av.visitEnd();
 291         cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 292 
 293         // fields
 294         structTree.fields().forEach(fieldTree -> addField(cw, fieldTree, type));
 295         // Write class
 296         cw.visitEnd();
 297         addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray());
 298         return true;
 299     }
 300 
 301     Layout addGetterSetterName(Layout layout, String accessorName) {
 302         return layout
 303             .withAnnotation("get", accessorName + "$get")
 304             .withAnnotation("set", accessorName + "$set");
 305     }
 306 
 307     Layout decorateAsAccessor(VarTree varTree, Layout layout) {
 308         return addGetterSetterName(layout, varTree.name()).
 309             withAnnotation("ptr", varTree.name() + "$ptr");
 310     }
 311 
 312     Layout decorateAsAccessor(FieldTree fieldTree, Layout layout) {
 313         layout = addGetterSetterName(layout, fieldTree.name());
 314         if (!fieldTree.isBitField()) {
 315             //no pointer accessors for bitfield!
 316             layout = layout.withAnnotation("ptr", fieldTree.name() + "$ptr");
 317         }
 318         return layout;
 319     }
 320 
 321     @Override
 322     public Boolean visitEnum(EnumTree enumTree, JType jt) {
 323         // define enum constants in global_cw
 324         enumTree.constants().forEach(constant -> addConstant(global_cw, constant));
 325 
 326         if (enumTree.name().isEmpty()) {
 327             // We are done with anonymous enum
 328             return true;
 329         }
 330 
 331         // generate annotation class for named enum
 332         createAnnotationCls(enumTree);
 333         return true;
 334     }
 335 
 336     private void createAnnotationCls(Tree tree) {
 337         String nativeName = tree.name();
 338         logger.fine(() -> "Create annotation for: " + nativeName);
 339 
 340         String intf = Utils.toClassName(nativeName);
 341         String name = headerClassName + "$" + intf;
 342 
 343         logger.fine(() -> "Define class " + name + " for native type " + nativeName);
 344         global_cw.visitInnerClass(name, headerClassName, intf,
 345                 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION);
 346         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 347         String[] superAnno = { "java/lang/annotation/Annotation" };
 348         cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION,
 349                 name, null, "java/lang/Object", superAnno);
 350         annotateNativeLocation(cw, tree);
 351         AnnotationVisitor av = cw.visitAnnotation("Ljava/lang/annotation/Target;", true);
 352         av.visitEnum("value", "Ljava/lang/annotation/ElementType;", "TYPE_USE");
 353         av.visitEnd();
 354         av = cw.visitAnnotation("Ljava/lang/annotation/Retention;", true);
 355         av.visitEnum("value", "Ljava/lang/annotation/RetentionPolicy;", "RUNTIME");
 356         av.visitEnd();
 357         cw.visitInnerClass(name, headerClassName, intf,
 358                 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION);
 359         // Write class
 360         cw.visitEnd();
 361         addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray());
 362     }
 363 
 364     private void createFunctionalInterface(JType.FunctionalInterfaceType fnif) {
 365         JType.Function fn = fnif.getFunction();
 366         String intf;
 367         String nativeName;
 368         String nDesc = fnif.getFunction().getNativeDescriptor();
 369         intf = fnif.getSimpleName();
 370         nativeName = "anonymous function";
 371         logger.fine(() -> "Create FunctionalInterface " + intf);
 372 
 373         final String name = headerClassName + "$" + intf;
 374 
 375         logger.fine(() -> "Define class " + name + " for native type " + nativeName + nDesc);
 376         global_cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 377         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 378         cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
 379                 name, "Ljava/lang/Object;",
 380                 "java/lang/Object", new String[0]);
 381         AnnotationVisitor av = cw.visitAnnotation(
 382                 "Ljava/lang/FunctionalInterface;", true);
 383         av.visitEnd();
 384         av = cw.visitAnnotation(NATIVE_CALLBACK, true);
 385         av.visit("value", nDesc);
 386         av.visitEnd();
 387         cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 388 
 389         // add the method
 390 
 391         int flags = ACC_PUBLIC | ACC_ABSTRACT;
 392         if (fn.isVarArgs) {
 393             flags |= ACC_VARARGS;
 394         }
 395         MethodVisitor mv = cw.visitMethod(flags, "fn",
 396                 fn.getDescriptor(), fn.getSignature(false), null);
 397         mv.visitEnd();
 398         // Write class
 399         cw.visitEnd();
 400         addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray());
 401     }
 402 
 403     @Override
 404     public Boolean visitTypedef(TypedefTree typedefTree, JType jt) {
 405         createAnnotationCls(typedefTree);
 406         return true;
 407     }
 408 
 409     @Override
 410     public Boolean visitTree(Tree tree, JType jt) {
 411         logger.warning(() -> "Unsupported declaration tree:");
 412         logger.warning(() -> tree.toString());
 413         return true;
 414     }
 415 
 416     @Override
 417     public Boolean visitFunction(FunctionTree funcTree, JType jt) {
 418         assert (jt instanceof JType.Function);
 419         JType.Function fn = (JType.Function)jt;
 420         String uniqueName = funcTree.name() + "." + fn.getDescriptor();
 421         if (! global_methods.add(uniqueName)) {
 422             return false; // added already
 423         }
 424         logger.fine(() -> "Add method: " + fn.getSignature(false));
 425         int flags = ACC_PUBLIC | ACC_ABSTRACT;
 426         if (fn.isVarArgs) {
 427             flags |= ACC_VARARGS;
 428         }
 429         MethodVisitor mv = global_cw.visitMethod(flags,
 430                 funcTree.name(), fn.getDescriptor(), fn.getSignature(false), null);
 431         final int arg_cnt = funcTree.numParams();
 432         for (int i = 0; i < arg_cnt; i++) {
 433             String name = funcTree.paramName(i);
 434             final int tmp = i;
 435             logger.finer(() -> "  arg " + tmp + ": " + name);
 436             mv.visitParameter(name, 0);
 437         }
 438 
 439         if (! noNativeLocations) {
 440             AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true);
 441             SourceLocation src = funcTree.location();
 442             SourceLocation.Location loc = src.getFileLocation();
 443             Path p = loc.path();
 444             av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString());
 445             av.visit("line", loc.line());
 446             av.visit("column", loc.column());
 447             av.visitEnd();
 448         }
 449 
 450         Type type = funcTree.type();
 451         final String descStr = Utils.getFunction(type).toString();
 452         addHeaderDecl(funcTree.name(), descStr);
 453 
 454         mv.visitEnd();
 455         return true;
 456     }
 457 
 458     private AsmCodeFactory generateDecl(Tree tree) {
 459         try {
 460             logger.fine(() -> "Process tree " + tree.name());
 461             tree.accept(this, tree.isPreprocessing() ? null : headerFile.dictionary().lookup(tree.type()));
 462         } catch (Exception ex) {
 463             handleException(ex);
 464             logger.warning("Tree causing above exception is: " + tree.name());
 465             logger.warning(() -> tree.toString());
 466         }
 467         return this;
 468     }
 469 
 470     @Override
 471     public Boolean visitMacro(MacroTree macroTree, JType jt) {
 472         if (!macroTree.isConstant()) {
 473             logger.fine(() -> "Skipping unrecognized object-like macro " + macroTree.name());
 474             return false;
 475         }
 476         String name = macroTree.name();
 477         Object value = macroTree.value().get();
 478         if (! global_macros.add(name)) {
 479             return false; // added already
 480         }
 481         logger.fine(() -> "Adding macro " + name);
 482         Class<?> macroType = Utils.unboxIfNeeded(value.getClass());
 483 
 484         String sig = jdk.internal.org.objectweb.asm.Type.getMethodDescriptor(jdk.internal.org.objectweb.asm.Type.getType(macroType));
 485         MethodVisitor mv = global_cw.visitMethod(ACC_PUBLIC, name, sig, sig, null);
 486 
 487         if (! noNativeLocations) {
 488             AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true);
 489             SourceLocation src = macroTree.location();
 490             SourceLocation.Location loc = src.getFileLocation();
 491             Path p = loc.path();
 492             av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString());
 493             av.visit("line", loc.line());
 494             av.visit("column", loc.column());
 495             av.visitEnd();
 496         }
 497 
 498         mv.visitCode();
 499 
 500         mv.visitLdcInsn(value);
 501         if (macroType.equals(char.class)) {
 502             mv.visitInsn(I2C);
 503             mv.visitInsn(IRETURN);
 504         } else if (macroType.equals(int.class)) {
 505             mv.visitInsn(IRETURN);
 506         } else if (macroType.equals(float.class)) {
 507             mv.visitInsn(FRETURN);
 508         } else if (macroType.equals(long.class)) {
 509             mv.visitInsn(LRETURN);
 510         } else if (macroType.equals(double.class)) {
 511             mv.visitInsn(DRETURN);
 512         } else if (macroType.equals(String.class)) {
 513             mv.visitInsn(ARETURN);
 514         }
 515         mv.visitMaxs(0, 0);
 516         mv.visitEnd();
 517         return true;
 518     }
 519 }