1 /*
   2  * Copyright (c) 2016, 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 package valhalla.shady;
  26 
  27 import jdk.experimental.bytecode.BasicClassBuilder;
  28 import jdk.internal.misc.Unsafe;
  29 import jdk.internal.misc.VM;
  30 import sun.security.action.GetPropertyAction;
  31 
  32 import java.lang.annotation.Annotation;
  33 import java.io.BufferedOutputStream;
  34 import java.io.File;
  35 import java.io.IOException;
  36 import java.io.OutputStream;
  37 import java.nio.file.Files;
  38 import java.nio.file.Path;
  39 import java.nio.file.Paths;
  40 import java.security.ProtectionDomain;
  41 import java.util.Properties;
  42 import java.util.concurrent.ConcurrentHashMap;
  43 
  44 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  45 
  46 import jdk.internal.misc.JavaLangAccess;
  47 import jdk.internal.misc.SharedSecrets;
  48 public class MinimalValueTypes_1_0 {
  49 
  50     public static final int V53_1 = 1 << 16 | 53;
  51     public static final int ACC_VALUE = ACC_NATIVE;
  52     public static final String OBJECT_CLASS_DESC = "java/lang/Object";
  53     public static final String VALUE_CLASS_DESC = "java/lang/__Value";
  54 
  55     public static final String DERIVE_VALUE_TYPE_DESC = "Ljdk/incubator/mvt/ValueCapableClass;";
  56     public static final String DERIVE_VT_CLASSNAME_POSTFIX = "$Value";
  57     public static final int    DERIVE_VT_CLASS_ACCESS = ACC_PUBLIC|ACC_SUPER|ACC_FINAL|ACC_VALUE|ACC_SYNTHETIC;
  58 
  59     public static final boolean DUMP_CLASS_FILES;
  60     private static final boolean VALUE_TYPE_ENABLED;
  61     private static final JavaLangAccess JLA;
  62 
  63     static {
  64         // Use same property as in j.l.invoke.MethodHandleStatics
  65         Properties props = GetPropertyAction.privilegedGetProperties();
  66         DUMP_CLASS_FILES = Boolean.parseBoolean(
  67             props.getProperty("java.lang.invoke.MethodHandle.DUMP_CLASS_FILES"));
  68 
  69         VALUE_TYPE_ENABLED = Boolean.parseBoolean(
  70             props.getProperty("valhalla.enableValueType"));
  71 
  72         JLA = SharedSecrets.getJavaLangAccess();
  73     }
  74 
  75     private static final ConcurrentHashMap<Class<?>, ValueTypeHolder<?>> BOX_TO_VT
  76         = new ConcurrentHashMap<>();
  77 
  78     /**
  79      * Returns the {@code ValueTypeHolder} representing the value type
  80      * for the given value capable type.
  81      *
  82      * @throws UnsupportedOperationException if MVT is not enabled
  83      * @throws IllegalArgumentException if the given class is not value capable class
  84      */
  85     @SuppressWarnings("unchecked")
  86     public static <T> ValueTypeHolder<T> getValueFor(Class<T> vcc) {
  87         if (!MinimalValueTypes_1_0.isValueTypeEnabled()) {
  88             throw new UnsupportedOperationException("MVT is not enabled");
  89         }
  90 
  91         if (!MinimalValueTypes_1_0.isValueCapable(vcc)) {
  92             throw new IllegalArgumentException("Class " + vcc + " not a value capable class");
  93         }
  94 
  95         ValueTypeHolder<T> vt = (ValueTypeHolder<T>) BOX_TO_VT.get(vcc);
  96         if (vt != null) {
  97             return vt;
  98         }
  99 
 100         Class<T> valueClass = (Class<T>) MinimalValueTypes_1_0.getValueTypeClass(vcc);
 101         vt = new ValueTypeHolder<T>(vcc, valueClass);
 102         ValueTypeHolder<T> old = (ValueTypeHolder<T>) BOX_TO_VT.putIfAbsent(vcc, vt);
 103         if (old != null) {
 104             vt = old;
 105         }
 106         return vt;
 107     }
 108 
 109     /**
 110      * Returns the {@code ValueTypeHolder} representing the value type
 111      * for the given class is a derived value type; otherwise {@code null}.
 112      */
 113     @SuppressWarnings("unchecked")
 114     public static <T> ValueTypeHolder<T> findValueType(Class<T> c) {
 115         if (MinimalValueTypes_1_0.isValueType(c)) {
 116             return (ValueTypeHolder<T>) getValueFor(MinimalValueTypes_1_0.getValueCapableClass(c));
 117         } else {
 118             return null;
 119         }
 120     }
 121 
 122     /**
 123      * Returns true if MVT is enabled.
 124      *
 125      * jdk.incubator.mvt must be resolved in the boot layer.
 126      */
 127     public static boolean isValueTypeEnabled() {
 128         return VALUE_TYPE_ENABLED;
 129     }
 130 
 131     public static boolean isValueType(Class<?> dvt) {
 132         return (dvt.getModifiers() & ACC_VALUE) != 0;
 133     }
 134 
 135     /**
 136      * Returns true if the given class is a value-capable class, i.e.
 137      * annotated with @ValueCapableClass.
 138      */
 139     public static boolean isValueCapable(Class<?> vcc) {
 140         if (!isValueTypeEnabled()) {
 141             return false;
 142         }
 143 
 144         return ValueClassHelper.hasValueCapableAnnotation(vcc);
 145     }
 146 
 147     public static Class<?> getValueCapableClass(Class<?> dvt) {
 148         if (!isValueType(dvt)) {
 149             throw new IllegalArgumentException(dvt + " is not a derived value type");
 150         }
 151 
 152         Class<?> c = Class.forName(dvt.getModule(), getValueCapableClassName(dvt.getName()));
 153         if (c == null || !isValueCapable(c)) {
 154             throw new InternalError(dvt + " not bound to ValueType");
 155         }
 156         return c;
 157     }
 158 
 159     public static Class<?> getValueTypeClass(Class<?> vcc) {
 160         if (!isValueCapable(vcc)) {
 161             throw new IllegalArgumentException(vcc + " is not a value capable class");
 162         }
 163         return loadValueTypeClass(vcc, getValueTypeClassName(vcc.getName()));
 164     }
 165 
 166     public static Class<?> loadValueTypeClass(Class<?> vcc, String className) {
 167         if (!isValueCapable(vcc)) {
 168             throw new IllegalArgumentException(vcc.getName() + " is not a value capable class");
 169         }
 170         return JLA.loadValueTypeClass(vcc.getModule(), vcc.getClassLoader(), className);
 171     }
 172 
 173     /**
 174      * This method is invoked by the VM.
 175      *
 176      * @param fds   : name/sig pairs
 177      * @param fmods : field modifiers
 178      */
 179     public static String createDerivedValueType(String vccInternalClassName,
 180                                                 ClassLoader cl,
 181                                                 ProtectionDomain pd,
 182                                                 String[] fds,
 183                                                 int[] fmods) {
 184         String vtInternalClassName = getValueTypeClassName(vccInternalClassName);
 185         ValueTypeDesc valueTypeDesc = new ValueTypeDesc(vccInternalClassName, fds, fmods);
 186         byte[] valueTypeBytes = createValueType(valueTypeDesc);
 187         Class<?> vtClass = Unsafe.getUnsafe().defineClass(vtInternalClassName, valueTypeBytes, 0, valueTypeBytes.length, cl, pd);
 188         return vtInternalClassName;
 189     }
 190 
 191     public static byte[] createValueType(ValueTypeDesc valueTypeDesc) {
 192 
 193         String valueTypeClassName = getValueTypeClassName(valueTypeDesc);
 194 
 195         BasicClassBuilder builder = new BasicClassBuilder(mangleValueClassName(valueTypeClassName), 53, 1)
 196             .withFlags(DERIVE_VT_CLASS_ACCESS)
 197             .withSuperclass(mangleValueClassName(VALUE_CLASS_DESC));
 198 
 199         ValueTypeDesc.Field[] fields = valueTypeDesc.getFields();
 200         for (ValueTypeDesc.Field field : fields) {
 201             builder.withField(field.name, field.type, F -> F.withFlags(field.modifiers));
 202         }
 203 
 204         byte[] newBytes = builder.build();
 205         maybeDump(valueTypeClassName, newBytes);
 206         return newBytes;
 207     }
 208 
 209     /** debugging flag for saving generated class files */
 210     private static final Path DUMP_CLASS_FILES_DIR;
 211 
 212     static {
 213         if (DUMP_CLASS_FILES) {
 214             try {
 215                 Path dumpDir = Paths.get("DUMP_CLASS_FILES");
 216                 Files.createDirectories(dumpDir);
 217                 DUMP_CLASS_FILES_DIR = dumpDir;
 218             } catch (Exception e) {
 219                 throw new InternalError(e);
 220             }
 221         } else {
 222             DUMP_CLASS_FILES_DIR = null;
 223         }
 224     }
 225 
 226     public static void maybeDump(final String className, final byte[] classFile) {
 227         if (DUMP_CLASS_FILES_DIR != null) {
 228             java.security.AccessController.doPrivileged(
 229                 new java.security.PrivilegedAction<>() {
 230                     public Void run() {
 231                         String dumpName = className;
 232                         //dumpName = dumpName.replace('/', '-');
 233                         Path dumpFile = DUMP_CLASS_FILES_DIR.resolve(dumpName + ".class");
 234                         File root = dumpFile.getParent().toFile();
 235                         if (!root.exists() && !root.mkdirs()) {
 236                             throw new IllegalStateException("Could not create dump file directory: " + root);
 237                         }
 238                         System.out.println("dump: " + dumpFile);
 239                         try (OutputStream os = Files.newOutputStream(dumpFile);
 240                              BufferedOutputStream bos = new BufferedOutputStream(os)) {
 241                             bos.write(classFile);
 242                         } catch (IOException ex) {
 243                             throw new InternalError(ex);
 244                         }
 245                         return null;
 246                     }
 247                 });
 248 
 249         }
 250     }
 251 
 252     private final native Class<?> getDerivedValueType(Class<?> ofClass);
 253 
 254     public static Class<?> getValueClass() {
 255         return isValueTypeEnabled() ? ValueClassHelper.VALUE_CLASS : null;
 256     }
 257 
 258     public static String getValueTypeClassName(ValueTypeDesc valueTypeDesc) {
 259         return getValueTypeClassName(valueTypeDesc.getName());
 260     }
 261 
 262     public static String getValueTypeClassName(String vccName) {
 263         return vccName + DERIVE_VT_CLASSNAME_POSTFIX;
 264     }
 265 
 266     public static String getValueCapableClassName(String valName) {
 267         return valName.substring(0, valName.length() - DERIVE_VT_CLASSNAME_POSTFIX.length());
 268     }
 269 
 270     public static String mangleValueClassName(String name) {
 271         return ";Q" + name + ";";
 272     }
 273 
 274     /*
 275      * This helper class should only be loaded when MVT is enabled.
 276      * Otherwise, it will load __Value but if MVT is not enabled and
 277      * that would fail verification.
 278      */
 279     private static class ValueClassHelper {
 280         static final Class<?> VALUE_CLASS =
 281              (Class<?>)(Object)__Value.class; //hack around static type-system checks
 282 
 283         static volatile Class<? extends Annotation> annotationClass;
 284         static volatile Class<?> valueTypeClass;
 285 
 286         static boolean hasValueCapableAnnotation(Class<?> c) {
 287             if (!VM.isModuleSystemInited()) {
 288                 return false;
 289             }
 290             Class<? extends Annotation> annClass = annotationClass;
 291             if (annClass == null) {
 292                 annotationClass = annClass = loadValueCapableAnnotation();
 293             }
 294             return annClass != null &&
 295                     c.getDeclaredAnnotation(annClass) != null;
 296         }
 297 
 298         static Class<? extends Annotation> loadValueCapableAnnotation() {
 299             try {
 300                 @SuppressWarnings("unchecked")
 301                 Class<? extends Annotation> c = (Class<? extends Annotation>)
 302                     Class.forName("jdk.incubator.mvt.ValueCapableClass", false,
 303                         ClassLoader.getPlatformClassLoader());
 304                 return c;
 305 
 306             } catch (ClassNotFoundException e) {
 307                 return null;
 308             }
 309         }
 310 
 311         static Class<?> incubatorValueTypeClass() {
 312             Class<?> c = valueTypeClass;
 313             if (c == null) {
 314                 try {
 315                     valueTypeClass = c =
 316                         Class.forName("jdk.incubator.mvt.ValueType", false,
 317                                       ClassLoader.getPlatformClassLoader());
 318                 } catch (ClassNotFoundException e) {
 319                 }
 320             }
 321             return valueTypeClass;
 322         }
 323     }
 324 
 325     static Class<?> getIncubatorValueTypeClass() {
 326         if (!isValueTypeEnabled()) {
 327             throw new UnsupportedOperationException("MVT is not enabled");
 328         }
 329         return ValueClassHelper.incubatorValueTypeClass();
 330     }
 331 }