• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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