1 /*
   2  * Copyright (c) 2017, 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 
  26 package jdk.experimental.value;
  27 
  28 import jdk.experimental.bytecode.BytePoolHelper;
  29 import jdk.experimental.bytecode.BasicTypeHelper;
  30 import jdk.experimental.bytecode.ClassBuilder;
  31 import jdk.experimental.bytecode.CodeBuilder;
  32 import jdk.experimental.bytecode.Flag;
  33 import jdk.experimental.bytecode.MethodBuilder;
  34 import jdk.experimental.bytecode.TypeHelper;
  35 import jdk.experimental.bytecode.TypeTag;
  36 import jdk.experimental.bytecode.TypedCodeBuilder;
  37 import jdk.experimental.value.MethodHandleBuilder.IsolatedMethodBuilder.IsolatedMethodPoolHelper;
  38 import jdk.internal.misc.Unsafe;
  39 import sun.security.action.GetPropertyAction;
  40 
  41 import java.io.IOException;
  42 import java.io.OutputStream;
  43 import java.io.UncheckedIOException;
  44 import java.lang.invoke.MethodHandle;
  45 import java.lang.invoke.MethodHandles.Lookup;
  46 import java.lang.invoke.MethodType;
  47 import java.nio.file.Files;
  48 import java.nio.file.Path;
  49 import java.nio.file.Paths;
  50 import java.util.*;
  51 import java.util.function.Consumer;
  52 import java.util.function.Function;
  53 
  54 /**
  55  * Utility class for building method handles.
  56  */
  57 public class MethodHandleBuilder {
  58 
  59     static final Unsafe UNSAFE = Unsafe.getUnsafe();
  60 
  61     public static final boolean ENABLE_POOL_PATCHES;
  62     public static final String DUMP_CLASS_FILES_DIR;
  63 
  64     static {
  65         Properties props = GetPropertyAction.privilegedGetProperties();
  66         ENABLE_POOL_PATCHES = Boolean.parseBoolean(
  67                 props.getProperty("valhalla.enablePoolPatches"));
  68         DUMP_CLASS_FILES_DIR = props.getProperty("valhalla.DUMP_CLASS_FILES_DIR");
  69     }
  70 
  71     public static MethodHandle loadCode(Lookup lookup, String name, MethodType type, Consumer<? super MethodHandleCodeBuilder<?>> builder) {
  72         return loadCode(lookup, name, name, type, builder);
  73     }
  74 
  75     public static MethodHandle loadCode(Lookup lookup, String className, String methodName, MethodType type, Consumer<? super MethodHandleCodeBuilder<?>> builder) {
  76         String descriptor = type.toMethodDescriptorString();
  77         return loadCode(lookup, className, methodName, descriptor, MethodHandleCodeBuilder::new,
  78                     clazz -> {
  79                         try {
  80                             return lookup.findStatic(clazz, methodName, MethodType.fromMethodDescriptorString(descriptor, lookup.lookupClass().getClassLoader()));
  81                         } catch (ReflectiveOperationException ex) {
  82                             throw new IllegalStateException(ex);
  83                         }
  84                     },
  85                     builder);
  86     }
  87 
  88     protected static <Z, C extends CodeBuilder<Class<?>, String, byte[], ?>> Z loadCode(
  89             Lookup lookup, String className, String methodName, String type,
  90             Function<MethodBuilder<Class<?>, String, byte[]>, ? extends C> builderFunc,
  91             Function<Class<?>, Z> resFunc, Consumer<? super C> builder) {
  92 
  93         IsolatedMethodBuilder isolatedMethodBuilder = new IsolatedMethodBuilder(className, lookup);
  94         isolatedMethodBuilder
  95                 .withSuperclass(Object.class)
  96                 .withMajorVersion(58)
  97                 .withMinorVersion(0)
  98                 .withFlags(Flag.ACC_PUBLIC)
  99                 .withMethod(methodName, type, M ->
 100                         M.withFlags(Flag.ACC_STATIC, Flag.ACC_PUBLIC)
 101                                 .withCode(builderFunc, builder));
 102 
 103         try {
 104             byte[] barr = isolatedMethodBuilder.build();
 105             maybeDump(className, barr);
 106             Class<?> clazz = UNSAFE.defineAnonymousClass(lookup.lookupClass(), barr, isolatedMethodBuilder.patches());
 107             UNSAFE.ensureClassInitialized(clazz);
 108             return resFunc.apply(clazz);
 109         } catch (Throwable e) {
 110              throw new IllegalStateException(e);
 111         }
 112     }
 113 
 114     public static class IsolatedMethodBuilder extends ClassBuilder<Class<?>, String, IsolatedMethodBuilder> {
 115 
 116         private static final Class<?> THIS_CLASS = new Object() { }.getClass();
 117 
 118         public IsolatedMethodBuilder(String clazz, Lookup lookup) {
 119             super(ENABLE_POOL_PATCHES ?
 120                             new IsolatedMethodPatchingPoolHelper(clazz) :
 121                             new IsolatedMethodPoolHelper(clazz),
 122                   new IsolatedMethodTypeHelper(lookup));
 123             withThisClass(THIS_CLASS);
 124         }
 125 
 126         public Class<?> thisClass() {
 127             return THIS_CLASS;
 128         }
 129 
 130         Object[] patches() {
 131             return ((IsolatedMethodPoolHelper)poolHelper).patches();
 132         }
 133 
 134         static String classToInternalName(Class<?> c) {
 135             return c.getName().replace('.', '/');
 136         }
 137 
 138         static class IsolatedMethodTypeHelper implements TypeHelper<Class<?>, String> {
 139 
 140             BasicTypeHelper basicTypeHelper = new BasicTypeHelper();
 141             Lookup lookup;
 142 
 143             IsolatedMethodTypeHelper(Lookup lookup) {
 144                 this.lookup = lookup;
 145             }
 146 
 147             @Override
 148             public String elemtype(String s) {
 149                 return basicTypeHelper.elemtype(s);
 150             }
 151 
 152             @Override
 153             public String arrayOf(String s) {
 154                 return basicTypeHelper.arrayOf(s);
 155             }
 156 
 157             @Override
 158             public Iterator<String> parameterTypes(String s) {
 159                 return basicTypeHelper.parameterTypes(s);
 160             }
 161 
 162             @Override
 163             public String fromTag(TypeTag tag) {
 164                 return basicTypeHelper.fromTag(tag);
 165             }
 166 
 167             @Override
 168             public String returnType(String s) {
 169                 return basicTypeHelper.returnType(s);
 170             }
 171 
 172             @Override
 173             public String type(Class<?> aClass) {
 174                 if (aClass.isArray()) {
 175                     return classToInternalName(aClass);
 176                 } else {
 177                     return (aClass.isInlineClass() ? "Q" : "L") + classToInternalName(aClass) + ";";
 178                 }
 179             }
 180 
 181             @Override
 182             public boolean isValue(String desc) {
 183                 Class<?> aClass = symbol(desc);
 184                 return aClass != null && aClass.isInlineClass();
 185             }
 186 
 187             @Override
 188             public Class<?> symbol(String desc) {
 189                 try {
 190                     if (desc.startsWith("[")) {
 191                         return Class.forName(desc.replaceAll("/", "."), true, lookup.lookupClass().getClassLoader());
 192                     } else {
 193                         return Class.forName(basicTypeHelper.symbol(desc).replaceAll("/", "."), true, lookup.lookupClass().getClassLoader());
 194                     }
 195                 } catch (ReflectiveOperationException ex) {
 196                     throw new AssertionError(ex);
 197                 }
 198             }
 199 
 200             @Override
 201             public TypeTag tag(String s) {
 202                 return basicTypeHelper.tag(s);
 203             }
 204 
 205             @Override
 206             public Class<?> symbolFrom(String s) {
 207                 return symbol(s);
 208             }
 209 
 210             @Override
 211             public String commonSupertype(String t1, String t2) {
 212                 return basicTypeHelper.commonSupertype(t1, t2);
 213             }
 214 
 215             @Override
 216             public String nullType() {
 217                 return basicTypeHelper.nullType();
 218             }
 219         }
 220 
 221         static class IsolatedMethodPoolHelper extends BytePoolHelper<Class<?>, String> {
 222             final String clazz;
 223 
 224             IsolatedMethodPoolHelper(String clazz) {
 225                 super(c -> from(c, clazz), s->s);
 226                 this.clazz = clazz;
 227             }
 228 
 229             Object[] patches() {
 230                 return null;
 231             }
 232 
 233             static String from(Class<?> c, String clazz) {
 234                 return c == THIS_CLASS ? clazz.replace('.', '/')
 235                                        : classToInternalName(c);
 236             }
 237         }
 238 
 239         @Override
 240         public byte[] build() {
 241             return super.build();
 242         }
 243     }
 244 
 245     static class IsolatedMethodPatchingPoolHelper extends IsolatedMethodPoolHelper {
 246 
 247         public IsolatedMethodPatchingPoolHelper(String clazz) {
 248             super(clazz);
 249         }
 250 
 251         Map<Object, CpPatch> cpPatches = new HashMap<>();
 252         int cph = 0;  // for counting constant placeholders
 253 
 254         static class CpPatch {
 255 
 256             final int index;
 257             final String placeholder;
 258             final Object value;
 259 
 260             CpPatch(int index, String placeholder, Object value) {
 261                 this.index = index;
 262                 this.placeholder = placeholder;
 263                 this.value = value;
 264             }
 265 
 266             public String toString() {
 267                 return "CpPatch/index="+index+",placeholder="+placeholder+",value="+value;
 268             }
 269         }
 270 
 271         @Override
 272         public int putValue(Object v) {
 273             if (v instanceof String || v instanceof Integer || v instanceof Float || v instanceof Double || v instanceof Long) {
 274                 return super.putValue(v);
 275             }
 276             assert (!v.getClass().isPrimitive()) : v;
 277             return patchPoolEntry(v); // CP patching support
 278         }
 279 
 280         int patchPoolEntry(Object v) {
 281             String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++;
 282             if (cpPatches.containsKey(cpPlaceholder)) {
 283                 throw new InternalError("observed CP placeholder twice: " + cpPlaceholder);
 284             }
 285             // insert placeholder in CP and remember the patch
 286             int index = super.putValue(cpPlaceholder);  // TODO check if already in the constant pool
 287             cpPatches.put(cpPlaceholder, new CpPatch(index, cpPlaceholder, v));
 288             return index;
 289         }
 290 
 291         @Override
 292         Object[] patches() {
 293             int size = size();
 294             Object[] res = new Object[size];
 295             for (CpPatch p : cpPatches.values()) {
 296                 if (p.index >= size)
 297                     throw new InternalError("bad cp patch index");
 298                 res[p.index] = p.value;
 299             }
 300             return res;
 301         }
 302 
 303         private static String debugString(Object arg) {
 304             // @@@ Cannot crack open a MH like with InvokerByteCodeGenerator.debugString
 305             return arg.toString();
 306         }
 307     }
 308 
 309     public static class MethodHandleCodeBuilder<T extends MethodHandleCodeBuilder<T>> extends TypedCodeBuilder<Class<?>, String, byte[], T> {
 310 
 311         BasicTypeHelper basicTypeHelper = new BasicTypeHelper();
 312 
 313         public MethodHandleCodeBuilder(jdk.experimental.bytecode.MethodBuilder<Class<?>, String, byte[]> methodBuilder) {
 314             super(methodBuilder);
 315         }
 316 
 317         TypeTag getTagType(String s) {
 318             return basicTypeHelper.tag(s);
 319         }
 320 
 321         public T ifcmp(String s, CondKind cond, CharSequence label) {
 322             return super.ifcmp(getTagType(s), cond, label);
 323         }
 324 
 325         public T return_(String s) {
 326             return super.return_(getTagType(s));
 327         }
 328     }
 329 
 330     static void maybeDump(final String className, final byte[] classFile) {
 331         if (DUMP_CLASS_FILES_DIR != null) {
 332             java.security.AccessController.doPrivileged(
 333                 new java.security.PrivilegedAction<>() {
 334                     public Void run() {
 335                         String dumpName = className.replace('.','/');
 336                         Path dumpFile = Paths.get(DUMP_CLASS_FILES_DIR, dumpName + ".class");
 337                         try {
 338                             Files.createDirectories(dumpFile.getParent());
 339                         } catch (IOException e) {
 340                             throw new UncheckedIOException(e);
 341                         }
 342 
 343                         System.out.println("dump: " + dumpFile);
 344                         try (OutputStream os = Files.newOutputStream(dumpFile)) {
 345                             os.write(classFile);
 346                         } catch (IOException ex) {
 347                             throw new UncheckedIOException(ex);
 348                         }
 349                         return null;
 350                     }
 351                 });
 352         }
 353     }
 354 }