/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.startup;
import java.io.*;
import java.lang.instrument.*;
import javax.annotation.*;
import mockit.coverage.*;
import mockit.coverage.standalone.*;
import mockit.internal.expectations.transformation.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
/**
* This is the "agent class" that initializes the JMockit "Java agent". It is not intended for use in client code.
* <p/>
* There are several possible initialization scenarios:
* <ol>
* <li>Execution with {@code -javaagent:jmockit.jar} and without reloading in a custom class loader.</li>
* <li>Execution from system CL without {@code -javaagent} and without reloading in custom CL.</li>
* <li>Execution with {@code -javaagent} and with reloading in custom CL.</li>
* <li>Execution from system CL without {@code -javaagent} and with reloading in custom CL.</li>
* <li>Execution from custom CL without {@code -javaagent} and without reloading in another custom CL.</li>
* <li>Execution from custom CL without {@code -javaagent} and with reloading in another custom CL.</li>
* </ol>
*
* @see #premain(String, Instrumentation)
* @see #agentmain(String, Instrumentation)
*/
public final class Startup
{
public static boolean initializing;
private static boolean initializedOnDemand;
private Startup() {}
/**
* This method must only be called by the JVM, to provide the instrumentation object.
* In order for this to occur, the JVM must be started with "-javaagent:jmockit.jar" as a command line parameter
* (assuming the jar file is in the current directory).
* <p/>
* It is also possible to load user-specified mock-ups at this time, by having set the "mockups" system property.
*
* @param agentArgs not used
* @param inst the instrumentation service provided by the JVM
*/
public static void premain(@Nullable String agentArgs, @Nonnull Instrumentation inst)
{
if (!activateCodeCoverageIfRequested(agentArgs, inst)) {
String hostJREClassName = MockingBridgeFields.createSyntheticFieldsInJREClassToHoldMockingBridges(inst);
Instrumentation wrappedInst = InstrumentationHolder.set(inst, hostJREClassName);
initialize(wrappedInst);
}
}
private static void initialize(@Nonnull Instrumentation inst)
{
inst.addTransformer(CachedClassfiles.INSTANCE, true);
applyStartupMocks(inst);
inst.addTransformer(new ExpectationsTransformer(inst));
}
private static void applyStartupMocks(@Nonnull Instrumentation inst)
{
initializing = true;
try {
new JMockitInitialization().initialize(inst);
}
finally {
initializing = false;
}
}
/**
* This method must only be called by the JVM, to provide the instrumentation object.
* This occurs only when the JMockit Java agent gets loaded on demand, through the Attach API.
* <p/>
* For additional details, see the {@link #premain(String, Instrumentation)} method.
*
* @param agentArgs not used
* @param inst the instrumentation service provided by the JVM
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public static void agentmain(@Nullable String agentArgs, @Nonnull Instrumentation inst)
{
if (!inst.isRedefineClassesSupported()) {
throw new UnsupportedOperationException(
"This JRE must be started in debug mode, or with -javaagent:<proper path>/jmockit.jar");
}
String hostJREClassName = InstrumentationHolder.hostJREClassName;
if (hostJREClassName == null) {
hostJREClassName = MockingBridgeFields.createSyntheticFieldsInJREClassToHoldMockingBridges(inst);
}
InstrumentationHolder.set(inst, hostJREClassName);
activateCodeCoverageIfRequested(agentArgs, inst);
}
private static boolean activateCodeCoverageIfRequested(@Nullable String agentArgs, @Nonnull Instrumentation inst)
{
boolean standalone = "standalone".equals(agentArgs);
if (standalone || "coverage".equals(agentArgs)) {
try {
if (standalone) {
CoverageControl.create();
}
CodeCoverage coverage = CodeCoverage.create(standalone, true);
inst.addTransformer(coverage);
return true;
}
catch (Throwable t) {
try {
PrintWriter out = new PrintWriter("coverage-failure.txt");
t.printStackTrace(out);
out.close();
}
catch (FileNotFoundException ignore) {}
}
}
return false;
}
@Nonnull
public static Instrumentation instrumentation()
{
verifyInitialization();
return InstrumentationHolder.get();
}
public static boolean wasInitializedOnDemand() { return initializedOnDemand; }
public static void verifyInitialization()
{
if (InstrumentationHolder.get() == null) {
new AgentLoader().loadAgent(null);
initializedOnDemand = true;
}
}
public static boolean initializeIfPossible()
{
InstrumentationHolder wrappedInst = InstrumentationHolder.get();
if (wrappedInst == null) {
try {
new AgentLoader().loadAgent(null);
Instrumentation inst = InstrumentationHolder.get();
String hostJREClassName = MockingBridgeFields.createSyntheticFieldsInJREClassToHoldMockingBridges(inst);
InstrumentationHolder.setHostJREClassName(hostJREClassName);
initialize(inst);
return true;
}
catch (IllegalStateException e) {
StackTrace.filterStackTrace(e);
e.printStackTrace();
}
catch (RuntimeException e) { e.printStackTrace(); }
return false;
}
if (wrappedInst.wasRecreated()) {
initialize(wrappedInst);
}
return true;
}
public static void retransformClass(@Nonnull Class<?> aClass)
{
try { instrumentation().retransformClasses(aClass); } catch (UnmodifiableClassException ignore) {}
}
public static void redefineMethods(@Nonnull Class<?> classToRedefine, @Nonnull byte[] modifiedClassfile)
{
redefineMethods(new ClassDefinition(classToRedefine, modifiedClassfile));
}
public static void redefineMethods(@Nonnull ClassDefinition... classDefs)
{
try {
instrumentation().redefineClasses(classDefs);
}
catch (ClassNotFoundException e) {
// should never happen
throw new RuntimeException(e);
}
catch (UnmodifiableClassException e) {
throw new RuntimeException(e);
}
catch (InternalError ignore) {
// If a class to be redefined hasn't been loaded yet, the JVM may get a NoClassDefFoundError during
// redefinition. Unfortunately, it then throws a plain InternalError instead.
for (ClassDefinition classDef : classDefs) {
detectMissingDependenciesIfAny(classDef.getDefinitionClass());
}
// If the above didn't throw upon detecting a NoClassDefFoundError, then ignore the original error and
// continue, in order to prevent secondary failures.
}
}
private static void detectMissingDependenciesIfAny(@Nonnull Class<?> mockedClass)
{
try {
Class.forName(mockedClass.getName(), false, mockedClass.getClassLoader());
}
catch (NoClassDefFoundError e) {
throw new RuntimeException("Unable to mock " + mockedClass + " due to a missing dependency", e);
}
catch (ClassNotFoundException ignore) {
// Shouldn't happen since the mocked class would already have been found in the classpath.
}
}
@Nullable
public static Class<?> getClassIfLoaded(@Nonnull String classDescOrName)
{
Instrumentation instrumentation = InstrumentationHolder.get();
if (instrumentation != null) {
String className = classDescOrName.replace('/', '.');
for (Class<?> aClass : instrumentation.getAllLoadedClasses()) {
if (aClass.getName().equals(className)) {
return aClass;
}
}
}
return null;
}
}