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