1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx.mockito.inline; 18 19 import android.os.Build; 20 import android.os.Debug; 21 22 import java.io.File; 23 import java.io.FileOutputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.OutputStream; 27 import java.security.ProtectionDomain; 28 import java.util.ArrayList; 29 30 import dalvik.system.BaseDexClassLoader; 31 32 /** 33 * Interface to the native jvmti agent in agent.cc 34 */ 35 class JvmtiAgent { 36 private static final String AGENT_LIB_NAME = "libdexmakerjvmtiagent.so"; 37 38 private static final Object lock = new Object(); 39 40 /** Registered byte code transformers */ 41 private final ArrayList<ClassTransformer> transformers = new ArrayList<>(); 42 nativeRegisterTransformerHook()43 private native void nativeRegisterTransformerHook(); 44 45 /** 46 * Enable jvmti and load agent. 47 * 48 * <p><b>If there are more than agent transforming classes the other agent might remove 49 * transformations added by this agent.</b> 50 * 51 * @throws IOException If jvmti could not be enabled or agent could not be loaded 52 */ JvmtiAgent()53 JvmtiAgent() throws IOException { 54 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { 55 throw new IOException("Requires API level " + Build.VERSION_CODES.P + ". API level is " 56 + Build.VERSION.SDK_INT); 57 } 58 59 ClassLoader cl = JvmtiAgent.class.getClassLoader(); 60 if (!(cl instanceof BaseDexClassLoader)) { 61 throw new IOException("Could not load jvmti plugin as JvmtiAgent class was not loaded " 62 + "by a BaseDexClassLoader"); 63 } 64 65 Debug.attachJvmtiAgent(AGENT_LIB_NAME, null, cl); 66 nativeRegisterTransformerHook(); 67 } 68 nativeUnregisterTransformerHook()69 private native void nativeUnregisterTransformerHook(); 70 71 @Override finalize()72 protected void finalize() throws Throwable { 73 nativeUnregisterTransformerHook(); 74 } 75 nativeAppendToBootstrapClassLoaderSearch(String absolutePath)76 private native static void nativeAppendToBootstrapClassLoaderSearch(String absolutePath); 77 78 /** 79 * Append the jar to be bootstrap class load. This makes the classes in the jar behave as if 80 * they are loaded from the BCL. E.g. classes from java.lang can now call the classes in the 81 * jar. 82 * 83 * @param jarStream stream of jar to be added 84 */ appendToBootstrapClassLoaderSearch(InputStream jarStream)85 void appendToBootstrapClassLoaderSearch(InputStream jarStream) throws IOException { 86 File jarFile = File.createTempFile("mockito-boot", ".jar"); 87 jarFile.deleteOnExit(); 88 89 byte[] buffer = new byte[64 * 1024]; 90 try (OutputStream os = new FileOutputStream(jarFile)) { 91 while (true) { 92 int numRead = jarStream.read(buffer); 93 if (numRead == -1) { 94 break; 95 } 96 97 os.write(buffer, 0, numRead); 98 } 99 } 100 101 nativeAppendToBootstrapClassLoaderSearch(jarFile.getAbsolutePath()); 102 } 103 104 /** 105 * Ask the agent to trigger transformation of some classes. This will extract the byte code of 106 * the classes and the call back the {@link #addTransformer(ClassTransformer) transformers} for 107 * each individual class. 108 * 109 * @param classes The classes to transform 110 * 111 * @throws UnmodifiableClassException If one of the classes can not be transformed 112 */ requestTransformClasses(Class<?>[] classes)113 void requestTransformClasses(Class<?>[] classes) throws UnmodifiableClassException { 114 synchronized (lock) { 115 try { 116 nativeRetransformClasses(classes); 117 } catch (RuntimeException e) { 118 throw new UnmodifiableClassException(e); 119 } 120 } 121 } 122 123 // called by JNI 124 @SuppressWarnings("unused") shouldTransform(Class<?> classBeingRedefined)125 public boolean shouldTransform(Class<?> classBeingRedefined) { 126 for (ClassTransformer transformer : transformers) { 127 if (transformer.shouldTransform(classBeingRedefined)) { 128 return true; 129 } 130 } 131 132 return false; 133 } 134 135 /** 136 * Register a transformer. These are called for each class when a transformation was triggered 137 * via {@link #requestTransformClasses(Class[])}. 138 * 139 * @param transformer the transformer to add. 140 */ addTransformer(ClassTransformer transformer)141 void addTransformer(ClassTransformer transformer) { 142 transformers.add(transformer); 143 } 144 145 // called by JNI 146 @SuppressWarnings("unused") runTransformers(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)147 public byte[] runTransformers(ClassLoader loader, String className, 148 Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 149 byte[] classfileBuffer) throws IllegalClassFormatException { 150 byte[] transformedByteCode = classfileBuffer; 151 for (ClassTransformer transformer : transformers) { 152 transformedByteCode = transformer.transform(classBeingRedefined, transformedByteCode); 153 } 154 155 return transformedByteCode; 156 } 157 nativeRetransformClasses(Class<?>[] classes)158 private native void nativeRetransformClasses(Class<?>[] classes); 159 } 160