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;
  34 
  35 import java.lang.instrument.ClassFileTransformer;
  36 import java.lang.instrument.IllegalClassFormatException;
  37 import java.security.ProtectionDomain;
  38 import java.util.List;
  39 import java.util.logging.Level;
  40 import java.util.logging.Logger;
  41 
  42 import org.objectweb.asm.ClassReader;
  43 import org.objectweb.asm.ClassVisitor;
  44 import org.objectweb.asm.ClassWriter;
  45 import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor;
  46 import org.openjdk.jmc.agent.jfr.VersionResolver;
  47 import org.openjdk.jmc.agent.jfr.VersionResolver.JFRVersion;
  48 import org.openjdk.jmc.agent.jfr.impl.JFRClassVisitor;
  49 import org.openjdk.jmc.agent.jfrnext.impl.JFRNextClassVisitor;
  50 import org.openjdk.jmc.agent.text.impl.LoggerClassVisitor;
  51 import org.openjdk.jmc.agent.text.impl.TextTransformDescriptor;
  52 
  53 public class Transformer implements ClassFileTransformer {
  54         private TransformRegistry registry;
  55 
  56         public Transformer(TransformRegistry registry) {
  57                 this.registry = registry;
  58         }
  59 
  60         @Override
  61         public byte[] transform(
  62                 ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
  63                 byte[] classfileBuffer) throws IllegalClassFormatException {
  64                 if (!registry.hasPendingTransforms(className)) {
  65                         return null;
  66                 }
  67                 return doTransforms(registry.getTransformData(className), classfileBuffer, loader, protectionDomain);
  68         }
  69 
  70         private byte[] doTransforms(
  71                 List<TransformDescriptor> transformDataList, byte[] classfileBuffer, ClassLoader definingClassLoader,
  72                 ProtectionDomain protectionDomain) {
  73                 for (TransformDescriptor td : transformDataList) {
  74                         if (td.isPendingTransforms()) {
  75                                 // FIXME: Optimization, should do all transforms to one class in one go, instead of creating one class writer per transform.
  76                                 classfileBuffer = doTransform(td, classfileBuffer, definingClassLoader, protectionDomain);
  77                                 td.setPendingTransforms(false);
  78                         }
  79                 }
  80                 return classfileBuffer;
  81         }
  82 
  83         private byte[] doTransform(
  84                 TransformDescriptor td, byte[] classfileBuffer, ClassLoader definingClassLoader,
  85                 ProtectionDomain protectionDomain) {
  86                 if (td instanceof TextTransformDescriptor) {
  87                         return doTextLogging((TextTransformDescriptor) td, classfileBuffer);
  88                 } else if (td instanceof JFRTransformDescriptor) {
  89                         return doJFRLogging((JFRTransformDescriptor) td, classfileBuffer, definingClassLoader, protectionDomain);
  90                 }
  91                 return classfileBuffer;
  92         }
  93 
  94         private byte[] doJFRLogging(
  95                 JFRTransformDescriptor td, byte[] classfileBuffer, ClassLoader definingClassLoader,
  96                 ProtectionDomain protectionDomain) {
  97                 if (VersionResolver.getAvailableJFRVersion() == JFRVersion.NONE) {
  98                         Logger.getLogger(getClass().getName()).log(Level.SEVERE,
  99                                         "Could not find JFR classes. Failed to instrument " + td.getMethod().toString()); //$NON-NLS-1$
 100                         return classfileBuffer;
 101                 }
 102                 try {
 103                         ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
 104                         ClassVisitor visitor = VersionResolver.getAvailableJFRVersion() == JFRVersion.JFRNEXT
 105                                         ? new JFRNextClassVisitor(classWriter, td, definingClassLoader, protectionDomain)
 106                                         : new JFRClassVisitor(classWriter, td, definingClassLoader, protectionDomain);
 107                         ClassReader reader = new ClassReader(classfileBuffer);
 108                         reader.accept(visitor, 0);
 109                         return classWriter.toByteArray();
 110                 } catch (Throwable t) {
 111                         Logger.getLogger(getClass().getName()).log(Level.SEVERE,
 112                                         "Failed to instrument " + td.getMethod().toString(), t); //$NON-NLS-1$
 113                         return classfileBuffer;
 114                 }
 115         }
 116 
 117         private byte[] doTextLogging(TextTransformDescriptor td, byte[] classfileBuffer) {
 118                 try {
 119                         ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
 120                         LoggerClassVisitor visitor = new LoggerClassVisitor(classWriter, td);
 121                         ClassReader reader = new ClassReader(classfileBuffer);
 122                         reader.accept(visitor, 0);
 123                         return classWriter.toByteArray();
 124                 } catch (Throwable t) {
 125                         Logger.getLogger(getClass().getName()).log(Level.SEVERE,
 126                                         "Failed to instrument " + td.getMethod().toString(), t); //$NON-NLS-1$
 127                         return classfileBuffer;
 128                 }
 129         }
 130 }