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