1/*
2 * Copyright (c) 2003, 2017, 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
26package sun.instrument;
27
28import java.lang.instrument.UnmodifiableModuleException;
29import java.lang.reflect.Method;
30import java.lang.reflect.AccessibleObject;
31import java.lang.instrument.ClassFileTransformer;
32import java.lang.instrument.ClassDefinition;
33import java.lang.instrument.Instrumentation;
34import java.security.AccessController;
35import java.security.PrivilegedAction;
36import java.security.ProtectionDomain;
37import java.util.Collections;
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44import java.util.jar.JarFile;
45
46import jdk.internal.module.Modules;
47
48/*
49 * Copyright 2003 Wily Technology, Inc.
50 */
51
52/**
53 * The Java side of the JPLIS implementation. Works in concert with a native JVMTI agent
54 * to implement the JPLIS API set. Provides both the Java API implementation of
55 * the Instrumentation interface and utility Java routines to support the native code.
56 * Keeps a pointer to the native data structure in a scalar field to allow native
57 * processing behind native methods.
58 */
59public class InstrumentationImpl implements Instrumentation {
60    private final     TransformerManager      mTransformerManager;
61    private           TransformerManager      mRetransfomableTransformerManager;
62    // needs to store a native pointer, so use 64 bits
63    private final     long                    mNativeAgent;
64    private final     boolean                 mEnvironmentSupportsRedefineClasses;
65    private volatile  boolean                 mEnvironmentSupportsRetransformClassesKnown;
66    private volatile  boolean                 mEnvironmentSupportsRetransformClasses;
67    private final     boolean                 mEnvironmentSupportsNativeMethodPrefix;
68
69    private
70    InstrumentationImpl(long    nativeAgent,
71                        boolean environmentSupportsRedefineClasses,
72                        boolean environmentSupportsNativeMethodPrefix) {
73        mTransformerManager                    = new TransformerManager(false);
74        mRetransfomableTransformerManager      = null;
75        mNativeAgent                           = nativeAgent;
76        mEnvironmentSupportsRedefineClasses    = environmentSupportsRedefineClasses;
77        mEnvironmentSupportsRetransformClassesKnown = false; // false = need to ask
78        mEnvironmentSupportsRetransformClasses = false;      // don't know yet
79        mEnvironmentSupportsNativeMethodPrefix = environmentSupportsNativeMethodPrefix;
80    }
81
82    public void
83    addTransformer(ClassFileTransformer transformer) {
84        addTransformer(transformer, false);
85    }
86
87    public synchronized void
88    addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
89        if (transformer == null) {
90            throw new NullPointerException("null passed as 'transformer' in addTransformer");
91        }
92        if (canRetransform) {
93            if (!isRetransformClassesSupported()) {
94                throw new UnsupportedOperationException(
95                  "adding retransformable transformers is not supported in this environment");
96            }
97            if (mRetransfomableTransformerManager == null) {
98                mRetransfomableTransformerManager = new TransformerManager(true);
99            }
100            mRetransfomableTransformerManager.addTransformer(transformer);
101            if (mRetransfomableTransformerManager.getTransformerCount() == 1) {
102                setHasRetransformableTransformers(mNativeAgent, true);
103            }
104        } else {
105            mTransformerManager.addTransformer(transformer);
106        }
107    }
108
109    public synchronized boolean
110    removeTransformer(ClassFileTransformer transformer) {
111        if (transformer == null) {
112            throw new NullPointerException("null passed as 'transformer' in removeTransformer");
113        }
114        TransformerManager mgr = findTransformerManager(transformer);
115        if (mgr != null) {
116            mgr.removeTransformer(transformer);
117            if (mgr.isRetransformable() && mgr.getTransformerCount() == 0) {
118                setHasRetransformableTransformers(mNativeAgent, false);
119            }
120            return true;
121        }
122        return false;
123    }
124
125    public boolean
126    isModifiableClass(Class<?> theClass) {
127        if (theClass == null) {
128            throw new NullPointerException(
129                         "null passed as 'theClass' in isModifiableClass");
130        }
131        return isModifiableClass0(mNativeAgent, theClass);
132    }
133
134    public boolean isModifiableModule(Module module) {
135        if (module == null) {
136            throw new NullPointerException("'module' is null");
137        }
138        return true;
139    }
140
141    public boolean
142    isRetransformClassesSupported() {
143        // ask lazily since there is some overhead
144        if (!mEnvironmentSupportsRetransformClassesKnown) {
145            mEnvironmentSupportsRetransformClasses = isRetransformClassesSupported0(mNativeAgent);
146            mEnvironmentSupportsRetransformClassesKnown = true;
147        }
148        return mEnvironmentSupportsRetransformClasses;
149    }
150
151    public void
152    retransformClasses(Class<?>... classes) {
153        if (!isRetransformClassesSupported()) {
154            throw new UnsupportedOperationException(
155              "retransformClasses is not supported in this environment");
156        }
157        retransformClasses0(mNativeAgent, classes);
158    }
159
160    public boolean
161    isRedefineClassesSupported() {
162        return mEnvironmentSupportsRedefineClasses;
163    }
164
165    public void
166    redefineClasses(ClassDefinition...  definitions)
167            throws  ClassNotFoundException {
168        if (!isRedefineClassesSupported()) {
169            throw new UnsupportedOperationException("redefineClasses is not supported in this environment");
170        }
171        if (definitions == null) {
172            throw new NullPointerException("null passed as 'definitions' in redefineClasses");
173        }
174        for (int i = 0; i < definitions.length; ++i) {
175            if (definitions[i] == null) {
176                throw new NullPointerException("element of 'definitions' is null in redefineClasses");
177            }
178        }
179        if (definitions.length == 0) {
180            return; // short-circuit if there are no changes requested
181        }
182
183        redefineClasses0(mNativeAgent, definitions);
184    }
185
186    @SuppressWarnings("rawtypes")
187    public Class[]
188    getAllLoadedClasses() {
189        return getAllLoadedClasses0(mNativeAgent);
190    }
191
192    @SuppressWarnings("rawtypes")
193    public Class[]
194    getInitiatedClasses(ClassLoader loader) {
195        return getInitiatedClasses0(mNativeAgent, loader);
196    }
197
198    public long
199    getObjectSize(Object objectToSize) {
200        if (objectToSize == null) {
201            throw new NullPointerException("null passed as 'objectToSize' in getObjectSize");
202        }
203        return getObjectSize0(mNativeAgent, objectToSize);
204    }
205
206    public void
207    appendToBootstrapClassLoaderSearch(JarFile jarfile) {
208        appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), true);
209    }
210
211    public void
212    appendToSystemClassLoaderSearch(JarFile jarfile) {
213        appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), false);
214    }
215
216    public boolean
217    isNativeMethodPrefixSupported() {
218        return mEnvironmentSupportsNativeMethodPrefix;
219    }
220
221    public synchronized void
222    setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) {
223        if (!isNativeMethodPrefixSupported()) {
224            throw new UnsupportedOperationException(
225                   "setNativeMethodPrefix is not supported in this environment");
226        }
227        if (transformer == null) {
228            throw new NullPointerException(
229                       "null passed as 'transformer' in setNativeMethodPrefix");
230        }
231        TransformerManager mgr = findTransformerManager(transformer);
232        if (mgr == null) {
233            throw new IllegalArgumentException(
234                       "transformer not registered in setNativeMethodPrefix");
235        }
236        mgr.setNativeMethodPrefix(transformer, prefix);
237        String[] prefixes = mgr.getNativeMethodPrefixes();
238        setNativeMethodPrefixes(mNativeAgent, prefixes, mgr.isRetransformable());
239    }
240
241    @Override
242    public void redefineModule(Module module,
243                               Set<Module> extraReads,
244                               Map<String, Set<Module>> extraExports,
245                               Map<String, Set<Module>> extraOpens,
246                               Set<Class<?>> extraUses,
247                               Map<Class<?>, List<Class<?>>> extraProvides)
248    {
249        if (!module.isNamed())
250            return;
251
252        if (!isModifiableModule(module))
253            throw new UnmodifiableModuleException(module.getName());
254
255        // copy and check reads
256        extraReads = new HashSet<>(extraReads);
257        if (extraReads.contains(null))
258            throw new NullPointerException("'extraReads' contains null");
259
260        // copy and check exports and opens
261        extraExports = cloneAndCheckMap(module, extraExports);
262        extraOpens = cloneAndCheckMap(module, extraOpens);
263
264        // copy and check uses
265        extraUses = new HashSet<>(extraUses);
266        if (extraUses.contains(null))
267            throw new NullPointerException("'extraUses' contains null");
268
269        // copy and check provides
270        Map<Class<?>, List<Class<?>>> tmpProvides = new HashMap<>();
271        for (Map.Entry<Class<?>, List<Class<?>>> e : extraProvides.entrySet()) {
272            Class<?> service = e.getKey();
273            if (service == null)
274                throw new NullPointerException("'extraProvides' contains null");
275            List<Class<?>> providers = new ArrayList<>(e.getValue());
276            if (providers.isEmpty())
277                throw new IllegalArgumentException("list of providers is empty");
278            providers.forEach(p -> {
279                if (p.getModule() != module)
280                    throw new IllegalArgumentException(p + " not in " + module);
281                if (!service.isAssignableFrom(p))
282                    throw new IllegalArgumentException(p + " is not a " + service);
283            });
284            tmpProvides.put(service, providers);
285        }
286        extraProvides = tmpProvides;
287
288
289        // update reads
290        extraReads.forEach(m -> Modules.addReads(module, m));
291
292        // update exports
293        for (Map.Entry<String, Set<Module>> e : extraExports.entrySet()) {
294            String pkg = e.getKey();
295            Set<Module> targets = e.getValue();
296            targets.forEach(m -> Modules.addExports(module, pkg, m));
297        }
298
299        // update opens
300        for (Map.Entry<String, Set<Module>> e : extraOpens.entrySet()) {
301            String pkg = e.getKey();
302            Set<Module> targets = e.getValue();
303            targets.forEach(m -> Modules.addOpens(module, pkg, m));
304        }
305
306        // update uses
307        extraUses.forEach(service -> Modules.addUses(module, service));
308
309        // update provides
310        for (Map.Entry<Class<?>, List<Class<?>>> e : extraProvides.entrySet()) {
311            Class<?> service = e.getKey();
312            List<Class<?>> providers = e.getValue();
313            providers.forEach(p -> Modules.addProvides(module, service, p));
314        }
315    }
316
317    private Map<String, Set<Module>>
318        cloneAndCheckMap(Module module, Map<String, Set<Module>> map)
319    {
320        if (map.isEmpty())
321            return Collections.emptyMap();
322
323        Map<String, Set<Module>> result = new HashMap<>();
324        Set<String> packages = module.getPackages();
325        for (Map.Entry<String, Set<Module>> e : map.entrySet()) {
326            String pkg = e.getKey();
327            if (pkg == null)
328                throw new NullPointerException("package cannot be null");
329            if (!packages.contains(pkg))
330                throw new IllegalArgumentException(pkg + " not in module");
331            Set<Module> targets = new HashSet<>(e.getValue());
332            if (targets.isEmpty())
333                throw new IllegalArgumentException("set of targets is empty");
334            if (targets.contains(null))
335                throw new NullPointerException("set of targets cannot include null");
336            result.put(pkg, targets);
337        }
338        return result;
339    }
340
341
342    private TransformerManager
343    findTransformerManager(ClassFileTransformer transformer) {
344        if (mTransformerManager.includesTransformer(transformer)) {
345            return mTransformerManager;
346        }
347        if (mRetransfomableTransformerManager != null &&
348                mRetransfomableTransformerManager.includesTransformer(transformer)) {
349            return mRetransfomableTransformerManager;
350        }
351        return null;
352    }
353
354
355    /*
356     *  Natives
357     */
358    private native boolean
359    isModifiableClass0(long nativeAgent, Class<?> theClass);
360
361    private native boolean
362    isRetransformClassesSupported0(long nativeAgent);
363
364    private native void
365    setHasRetransformableTransformers(long nativeAgent, boolean has);
366
367    private native void
368    retransformClasses0(long nativeAgent, Class<?>[] classes);
369
370    private native void
371    redefineClasses0(long nativeAgent, ClassDefinition[]  definitions)
372        throws  ClassNotFoundException;
373
374    @SuppressWarnings("rawtypes")
375    private native Class[]
376    getAllLoadedClasses0(long nativeAgent);
377
378    @SuppressWarnings("rawtypes")
379    private native Class[]
380    getInitiatedClasses0(long nativeAgent, ClassLoader loader);
381
382    private native long
383    getObjectSize0(long nativeAgent, Object objectToSize);
384
385    private native void
386    appendToClassLoaderSearch0(long nativeAgent, String jarfile, boolean bootLoader);
387
388    private native void
389    setNativeMethodPrefixes(long nativeAgent, String[] prefixes, boolean isRetransformable);
390
391    static {
392        System.loadLibrary("instrument");
393    }
394
395    /*
396     *  Internals
397     */
398
399
400    // Enable or disable Java programming language access checks on a
401    // reflected object (for example, a method)
402    private static void setAccessible(final AccessibleObject ao, final boolean accessible) {
403        AccessController.doPrivileged(new PrivilegedAction<Object>() {
404                public Object run() {
405                    ao.setAccessible(accessible);
406                    return null;
407                }});
408    }
409
410    // Attempt to load and start an agent
411    private void
412    loadClassAndStartAgent( String  classname,
413                            String  methodname,
414                            String  optionsString)
415            throws Throwable {
416
417        ClassLoader mainAppLoader   = ClassLoader.getSystemClassLoader();
418        Class<?>    javaAgentClass  = mainAppLoader.loadClass(classname);
419
420        Method m = null;
421        NoSuchMethodException firstExc = null;
422        boolean twoArgAgent = false;
423
424        // The agent class must have a premain or agentmain method that
425        // has 1 or 2 arguments. We check in the following order:
426        //
427        // 1) declared with a signature of (String, Instrumentation)
428        // 2) declared with a signature of (String)
429        // 3) inherited with a signature of (String, Instrumentation)
430        // 4) inherited with a signature of (String)
431        //
432        // So the declared version of either 1-arg or 2-arg always takes
433        // primary precedence over an inherited version. After that, the
434        // 2-arg version takes precedence over the 1-arg version.
435        //
436        // If no method is found then we throw the NoSuchMethodException
437        // from the first attempt so that the exception text indicates
438        // the lookup failed for the 2-arg method (same as JDK5.0).
439
440        try {
441            m = javaAgentClass.getDeclaredMethod( methodname,
442                                 new Class<?>[] {
443                                     String.class,
444                                     java.lang.instrument.Instrumentation.class
445                                 }
446                               );
447            twoArgAgent = true;
448        } catch (NoSuchMethodException x) {
449            // remember the NoSuchMethodException
450            firstExc = x;
451        }
452
453        if (m == null) {
454            // now try the declared 1-arg method
455            try {
456                m = javaAgentClass.getDeclaredMethod(methodname,
457                                                 new Class<?>[] { String.class });
458            } catch (NoSuchMethodException x) {
459                // ignore this exception because we'll try
460                // two arg inheritance next
461            }
462        }
463
464        if (m == null) {
465            // now try the inherited 2-arg method
466            try {
467                m = javaAgentClass.getMethod( methodname,
468                                 new Class<?>[] {
469                                     String.class,
470                                     java.lang.instrument.Instrumentation.class
471                                 }
472                               );
473                twoArgAgent = true;
474            } catch (NoSuchMethodException x) {
475                // ignore this exception because we'll try
476                // one arg inheritance next
477            }
478        }
479
480        if (m == null) {
481            // finally try the inherited 1-arg method
482            try {
483                m = javaAgentClass.getMethod(methodname,
484                                             new Class<?>[] { String.class });
485            } catch (NoSuchMethodException x) {
486                // none of the methods exists so we throw the
487                // first NoSuchMethodException as per 5.0
488                throw firstExc;
489            }
490        }
491
492        // the premain method should not be required to be public,
493        // make it accessible so we can call it
494        // Note: The spec says the following:
495        //     The agent class must implement a public static premain method...
496        setAccessible(m, true);
497
498        // invoke the 1 or 2-arg method
499        if (twoArgAgent) {
500            m.invoke(null, new Object[] { optionsString, this });
501        } else {
502            m.invoke(null, new Object[] { optionsString });
503        }
504    }
505
506    // WARNING: the native code knows the name & signature of this method
507    private void
508    loadClassAndCallPremain(    String  classname,
509                                String  optionsString)
510            throws Throwable {
511
512        loadClassAndStartAgent( classname, "premain", optionsString );
513    }
514
515
516    // WARNING: the native code knows the name & signature of this method
517    private void
518    loadClassAndCallAgentmain(  String  classname,
519                                String  optionsString)
520            throws Throwable {
521
522        loadClassAndStartAgent( classname, "agentmain", optionsString );
523    }
524
525    // WARNING: the native code knows the name & signature of this method
526    private byte[]
527    transform(  Module              module,
528                ClassLoader         loader,
529                String              classname,
530                Class<?>            classBeingRedefined,
531                ProtectionDomain    protectionDomain,
532                byte[]              classfileBuffer,
533                boolean             isRetransformer) {
534        TransformerManager mgr = isRetransformer?
535                                        mRetransfomableTransformerManager :
536                                        mTransformerManager;
537        // module is null when not a class load or when loading a class in an
538        // unnamed module and this is the first type to be loaded in the package.
539        if (module == null) {
540            if (classBeingRedefined != null) {
541                module = classBeingRedefined.getModule();
542            } else {
543                module = (loader == null) ? jdk.internal.loader.BootLoader.getUnnamedModule()
544                                          : loader.getUnnamedModule();
545            }
546        }
547        if (mgr == null) {
548            return null; // no manager, no transform
549        } else {
550            return mgr.transform(   module,
551                                    loader,
552                                    classname,
553                                    classBeingRedefined,
554                                    protectionDomain,
555                                    classfileBuffer);
556        }
557    }
558
559
560    /**
561     * Invoked by the java launcher to load a java agent that is packaged with
562     * the main application in an executable JAR file.
563     */
564    public static void loadAgent(String path) {
565        loadAgent0(path);
566    }
567
568    private static native void loadAgent0(String path);
569}
570