1 /*
2  * Copyright (c) 2002-2011 LWJGL Project
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'LWJGL' nor the names of
17  *   its contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package org.lwjgl.util.mapped;
33 
34 import org.lwjgl.LWJGLUtil;
35 
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.lang.reflect.InvocationTargetException;
39 import java.lang.reflect.Method;
40 import java.net.URLClassLoader;
41 
42 /**
43  * This classloader is responsible for applying the bytecode transformation to mapped objects.
44  * The transformation can either be applied using a Java agent, or with the convenient {@link #fork} method.
45  *
46  * @author Riven
47  */
48 public class MappedObjectClassLoader extends URLClassLoader {
49 
50 	static final String MAPPEDOBJECT_PACKAGE_PREFIX = MappedObjectClassLoader.class.getPackage().getName() + ".";
51 
52 	static boolean FORKED;
53 
54 	/**
55 	 * Forks the specified class containing a main method, passing the specified arguments. See
56 	 * {@link org.lwjgl.test.mapped.TestMappedObject} for example usage.
57 	 *
58 	 * @param mainClass the class containing the main method
59 	 * @param args      the arguments to pass
60 	 *
61 	 * @return true if the fork was successful.
62 	 */
fork(Class<?> mainClass, String[] args)63 	public static boolean fork(Class<?> mainClass, String[] args) {
64 		if ( FORKED ) {
65 			return false;
66 		}
67 
68 		FORKED = true;
69 
70 		try {
71 			MappedObjectClassLoader loader = new MappedObjectClassLoader(mainClass);
72 			loader.loadMappedObject();
73 
74 			Class<?> replacedMainClass = loader.loadClass(mainClass.getName());
75 			Method mainMethod = replacedMainClass.getMethod("main", String[].class);
76 			mainMethod.invoke(null, new Object[] { args });
77 		} catch (InvocationTargetException exc) {
78 			Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), exc.getCause());
79 		} catch (Throwable cause) {
80 			throw new Error("failed to fork", cause);
81 		}
82 
83 		return true;
84 	}
85 
MappedObjectClassLoader(Class<?> mainClass)86 	private MappedObjectClassLoader(Class<?> mainClass) {
87 		super(((URLClassLoader)mainClass.getClassLoader()).getURLs());
88 	}
89 
loadMappedObject()90 	protected synchronized Class<?> loadMappedObject() throws ClassNotFoundException {
91 		final String name = MappedObject.class.getName();
92 		String className = name.replace('.', '/');
93 
94 		byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class")));
95 
96 		long t0 = System.nanoTime();
97 		bytecode = MappedObjectTransformer.transformMappedObject(bytecode);
98 		long t1 = System.nanoTime();
99 		total_time_transforming += (t1 - t0);
100 
101 		if ( MappedObjectTransformer.PRINT_ACTIVITY )
102 			printActivity(className, t0, t1);
103 
104 		Class<?> clazz = super.defineClass(name, bytecode, 0, bytecode.length);
105 		resolveClass(clazz);
106 		return clazz;
107 	}
108 
109 	private static long total_time_transforming;
110 
111 	@Override
loadClass(String name, boolean resolve)112 	protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
113 		if ( name.startsWith("java.")
114 		     || name.startsWith("javax.")
115 		     || name.startsWith("sun.")
116 		     || name.startsWith("sunw.")
117 		     || name.startsWith("org.objectweb.asm.")
118 			)
119 			return super.loadClass(name, resolve);
120 
121 		final String className = name.replace('.', '/');
122 		final boolean inThisPackage = name.startsWith(MAPPEDOBJECT_PACKAGE_PREFIX);
123 
124 		if ( inThisPackage && (
125 			name.equals(MappedObjectClassLoader.class.getName())
126 			|| name.equals((MappedObjectTransformer.class.getName()))
127 			|| name.equals((CacheUtil.class.getName()))
128 		) )
129 			return super.loadClass(name, resolve);
130 
131 		byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class")));
132 
133 		// Classes in this package do not get transformed, but need to go through here because we have transformed MappedObject.
134 		if ( !(inThisPackage && name.substring(MAPPEDOBJECT_PACKAGE_PREFIX.length()).indexOf('.') == -1) ) {
135 			long t0 = System.nanoTime();
136 			final byte[] newBytecode = MappedObjectTransformer.transformMappedAPI(className, bytecode);
137 			long t1 = System.nanoTime();
138 
139 			total_time_transforming += (t1 - t0);
140 
141 			if ( bytecode != newBytecode ) {
142 				bytecode = newBytecode;
143 				if ( MappedObjectTransformer.PRINT_ACTIVITY )
144 					printActivity(className, t0, t1);
145 			}
146 		}
147 
148 		Class<?> clazz = super.defineClass(name, bytecode, 0, bytecode.length);
149 		if ( resolve )
150 			resolveClass(clazz);
151 		return clazz;
152 	}
153 
printActivity(final String className, final long t0, final long t1)154 	private static void printActivity(final String className, final long t0, final long t1) {
155 		final StringBuilder msg = new StringBuilder(MappedObjectClassLoader.class.getSimpleName() + ": " + className);
156 
157 		if ( MappedObjectTransformer.PRINT_TIMING )
158 			msg.append("\n\ttransforming took " + (t1 - t0) / 1000 + " micros (total: " + (total_time_transforming / 1000 / 1000) + "ms)");
159 
160 		LWJGLUtil.log(msg);
161 	}
162 
readStream(InputStream in)163 	private static byte[] readStream(InputStream in) {
164 		byte[] bytecode = new byte[256];
165 		int len = 0;
166 		try {
167 			while ( true ) {
168 				if ( bytecode.length == len )
169 					bytecode = copyOf(bytecode, len * 2);
170 				int got = in.read(bytecode, len, bytecode.length - len);
171 				if ( got == -1 )
172 					break;
173 				len += got;
174 			}
175 		} catch (IOException exc) {
176 			// stop!
177 		} finally {
178 			try {
179 				in.close();
180 			} catch (IOException exc) {
181 				// ignore...
182 			}
183 		}
184 		return copyOf(bytecode, len);
185 	}
186 
copyOf(byte[] original, int newLength)187 	private static byte[] copyOf(byte[] original, int newLength) {
188 		byte[] copy = new byte[newLength];
189 		System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
190 		return copy;
191 	}
192 
193 }