1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * 
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  * 
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  * 
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  * 
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  * 
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.agent.util;
  34 
  35 import java.lang.reflect.Array;
  36 import java.lang.reflect.Field;
  37 import java.lang.reflect.InvocationTargetException;
  38 import java.lang.reflect.Method;
  39 import java.security.ProtectionDomain;
  40 import java.util.List;
  41 import java.util.logging.Level;
  42 import java.util.logging.Logger;
  43 
  44 import org.objectweb.asm.MethodVisitor;
  45 import org.objectweb.asm.Opcodes;
  46 import org.objectweb.asm.Type;
  47 import org.openjdk.jmc.agent.Agent;
  48 import org.openjdk.jmc.agent.Parameter;
  49 import org.openjdk.jmc.agent.jfr.impl.JFRUtils;
  50 
  51 /**
  52  * Helper methods for doing transforms.
  53  */
  54 public final class TypeUtils {
  55         private static final String NULL_REFERENCE_STRING = "null"; //$NON-NLS-1$
  56         /**
  57          * The internal name of this class.
  58          */
  59         public static final String INAME = Type.getInternalName(TypeUtils.class);
  60         public static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); //$NON-NLS-1$
  61         public static final Type OBJECT_ARRAY_TYPE = Type.getObjectType("[Ljava/lang/Object;"); //$NON-NLS-1$
  62         public static final Type STRING_TYPE = Type.getType("Ljava/lang/String;"); //$NON-NLS-1$
  63 
  64         public static final Object STRING_INTERNAL_NAME = "java/lang/String"; //$NON-NLS-1$
  65 
  66         private final static String UNSAFE_JDK_7_CLASS = "sun.misc.Unsafe"; //$NON-NLS-1$
  67         private final static String UNSAFE_JDK_11_CLASS = "jdk.internal.misc.Unsafe"; //$NON-NLS-1$
  68 
  69         private static final Object UNSAFE;
  70         private static final Method UNSAFE_DEFINE_CLASS_METHOD;
  71 
  72         static {
  73                 UNSAFE = getUnsafe();
  74                 UNSAFE_DEFINE_CLASS_METHOD = getUnsafeDefineClassMethod(UNSAFE);
  75         }
  76 
  77         /**
  78          * The file extension for java source files (.java).
  79          */
  80         public static final String JAVA_FILE_EXTENSION = ".java"; //$NON-NLS-1$
  81 
  82         private TypeUtils() {
  83                 throw new UnsupportedOperationException("Toolkit!"); //$NON-NLS-1$
  84         }
  85 
  86         public static Object box(byte val) {
  87                 return val;
  88         }
  89 
  90         public static Object box(short val) {
  91                 return val;
  92         }
  93 
  94         public static Object box(char val) {
  95                 return val;
  96         }
  97 
  98         public static Object box(int val) {
  99                 return val;
 100         }
 101 
 102         public static Object box(long val) {
 103                 return val;
 104         }
 105 
 106         public static Object box(float val) {
 107                 return val;
 108         }
 109 
 110         public static Object box(double val) {
 111                 return val;
 112         }
 113 
 114         public static String toString(Object o) {
 115                 if (o == null) {
 116                         return NULL_REFERENCE_STRING;
 117                 }
 118                 if (o.getClass().isArray()) {
 119                         return toString(o, Array.getLength(o));
 120                 }
 121                 return String.valueOf(o);
 122         }
 123 
 124         public static Class<?> defineClass(String eventClassName, byte[] eventClass, int i, int length,
 125                         ClassLoader definingClassLoader, ProtectionDomain protectionDomain) {
 126                 try {
 127                         return (Class<?>) UNSAFE_DEFINE_CLASS_METHOD.invoke(UNSAFE, eventClassName, eventClass, i, length,
 128                                         definingClassLoader, protectionDomain);
 129                 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
 130                         Agent.getLogger().log(Level.SEVERE, "Failed to dynamically define the class " + eventClassName, e); //$NON-NLS-1$
 131                 }
 132                 return null;
 133         }
 134 
 135         /**
 136          * Ensure that the operand is on the stack before calling. If type is void, this
 137          * is a noop, and depending on your use case you may instead want to push
 138          * Opcodes.ACONST_NULL.
 139          */
 140         public static void visitBox(MethodVisitor mv, Type type) {
 141                 switch (type.getSort()) {
 142                 case Type.VOID:
 143                         break;
 144                 case Type.BOOLEAN:
 145                         emitBox(mv, "(Z)Ljava/lang/Object;"); //$NON-NLS-1$
 146                         break;
 147                 case Type.BYTE:
 148                         emitBox(mv, "(B)Ljava/lang/Object;"); //$NON-NLS-1$
 149                         break;
 150                 case Type.CHAR:
 151                         emitBox(mv, "(C)Ljava/lang/Object;"); //$NON-NLS-1$
 152                         break;
 153                 case Type.SHORT:
 154                         emitBox(mv, "(S)Ljava/lang/Object;"); //$NON-NLS-1$
 155                         break;
 156                 case Type.INT:
 157                         emitBox(mv, "(I)Ljava/lang/Object;"); //$NON-NLS-1$
 158                         break;
 159                 case Type.LONG:
 160                         emitBox(mv, "(J)Ljava/lang/Object;"); //$NON-NLS-1$
 161                         break;
 162                 case Type.FLOAT:
 163                         emitBox(mv, "(F)Ljava/lang/Object;"); //$NON-NLS-1$
 164                         break;
 165                 case Type.DOUBLE:
 166                         emitBox(mv, "(D)Ljava/lang/Object;"); //$NON-NLS-1$
 167                         break;
 168                 }
 169         }
 170 
 171         public static boolean isValidJavaIdentifier(String identifier) {
 172                 if (identifier == null || identifier.length() == 0) {
 173                         return false;
 174                 }
 175 
 176                 if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
 177                         return false;
 178                 }
 179 
 180                 for (int i = 1; i < identifier.length(); i++) {
 181                         if (!Character.isJavaIdentifierPart(identifier.charAt(i))) {
 182                                 return false;
 183                         }
 184                 }
 185                 return true;
 186         }
 187 
 188         public static String deriveIdentifierPart(String str) {
 189                 StringBuilder builder = new StringBuilder();
 190 
 191                 for (int i = 0; i < str.length(); i++) {
 192                         char c = str.charAt(i);
 193                         if (Character.isJavaIdentifierPart(c)) {
 194                                 builder.append(c);
 195                         }
 196                 }
 197                 builder.setCharAt(0, Character.toUpperCase(builder.toString().charAt(0)));
 198                 return builder.toString();
 199         }
 200 
 201         public static String getPathPart(String fqcn) {
 202                 int lastSlashIndex = fqcn.lastIndexOf('/');
 203                 if (lastSlashIndex >= 0) {
 204                         return fqcn.substring(0, lastSlashIndex + 1);
 205                 }
 206                 return fqcn;
 207         }
 208 
 209         public static String getNamePart(String fqcn) {
 210                 int lastSlashIndex = fqcn.lastIndexOf('/');
 211                 if (lastSlashIndex >= 0) {
 212                         return fqcn.substring(lastSlashIndex + 1);
 213                 }
 214                 return fqcn;
 215         }
 216 
 217         public static void stringify(MethodVisitor mv, Parameter param, Type argumentType) {
 218                 mv.visitMethodInsn(Opcodes.INVOKESTATIC, INAME, "toString", //$NON-NLS-1$
 219                                 "(Ljava/lang/Object;)Ljava/lang/String;", false); //$NON-NLS-1$
 220         }
 221 
 222         public static boolean shouldStringify(Parameter param, Type argumentType) {
 223                 if (argumentType.getSort() == Type.ARRAY || argumentType.getSort() == Type.OBJECT) {
 224                         return !argumentType.getInternalName().equals(STRING_INTERNAL_NAME);
 225                 }
 226                 return false;
 227         }
 228 
 229         public static Parameter findReturnParam(List<Parameter> parameters) {
 230                 for (Parameter p : parameters) {
 231                         if (p.isReturn()) {
 232                                 return p;
 233                         }
 234                 }
 235                 return null;
 236         }
 237 
 238         /**
 239          * Transforms a FQN in internal form, so that it can be used in e.g. formal
 240          * descriptors.
 241          *
 242          * @param className the fully qualified class name in internal form.
 243          * @return the transformed class name.
 244          */
 245         public static String parameterize(String className) {
 246                 return "L" + className + ";"; //$NON-NLS-1$ //$NON-NLS-2$
 247         }
 248 
 249         /**
 250          * Type agnostic array toString() which also handles primitive arrays.
 251          */
 252         private static String toString(Object o, int length) {
 253                 int iMax = length - 1;
 254                 if (iMax == -1) {
 255                         return "[]"; //$NON-NLS-1$
 256                 }
 257 
 258                 StringBuilder b = new StringBuilder();
 259                 b.append('[');
 260                 for (int i = 0;; i++) {
 261                         b.append(Array.get(o, i));
 262                         if (i == iMax) {
 263                                 return b.append(']').toString();
 264                         }
 265                         b.append(", "); //$NON-NLS-1$
 266                 }
 267         }
 268 
 269         private static void emitBox(MethodVisitor mv, String desc) {
 270                 mv.visitMethodInsn(Opcodes.INVOKESTATIC, INAME, "box", desc, false); //$NON-NLS-1$
 271         }
 272 
 273         private static Object getUnsafe() {
 274                 // Lovely, but this seems to be the only way
 275                 Class<?> unsafeClass = getUnsafeClass();
 276                 try {
 277                         Field f = unsafeClass.getDeclaredField("theUnsafe"); //$NON-NLS-1$
 278                         f.setAccessible(true);
 279                         return f.get(null);
 280                 } catch (Exception e) {
 281                         Logger.getLogger(JFRUtils.class.getName()).log(Level.SEVERE, "Could not access Unsafe!", e); //$NON-NLS-1$
 282                 }
 283                 return null;
 284         }
 285 
 286         private static Method getUnsafeDefineClassMethod(Object unsafe) {
 287                 try {
 288                         return unsafe.getClass().getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class,
 289                                         ClassLoader.class, ProtectionDomain.class);
 290                 } catch (NoSuchMethodException | SecurityException e) {
 291                         System.out.println(
 292                                         "Could not find, or access, any defineClass method. The agent will not work. If on JDK 11, try adding  --add-exports java.base/jdk.internal.misc=ALL-UNNAMED"); //$NON-NLS-1$
 293                         e.printStackTrace();
 294                         System.out.flush();
 295                         System.exit(3);
 296                 }
 297                 return null;
 298         }
 299 
 300         private static Class<?> getUnsafeClass() {
 301                 Class<?> clazz = null;
 302                 try {
 303                         clazz = Class.forName(UNSAFE_JDK_11_CLASS);
 304                 } catch (ClassNotFoundException e) {
 305                         try {
 306                                 clazz = Class.forName(UNSAFE_JDK_7_CLASS);
 307                         } catch (ClassNotFoundException e1) {
 308                                 System.out.println(
 309                                                 "Could not find, or access, any Unsafe class. The agent will not work. If on JDK 11, try adding  --add-exports java.base/jdk.internal.misc=ALL-UNNAMED"); //$NON-NLS-1$
 310                                 e1.printStackTrace();
 311                                 System.out.flush();
 312                                 System.exit(2);
 313                         }
 314                 }
 315                 return clazz;
 316         }
 317 }