Version 1.7.4
[gae.git] / java / src / main / com / google / appengine / tools / development / agent / impl / Transformer.java
blobedba4f87766c93b246d44c2f2820718d6e9bde8b
1 // Copyright 2009 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.development.agent.impl;
5 import com.google.appengine.tools.info.SdkImplInfo;
7 import org.objectweb.asm.ClassReader;
8 import org.objectweb.asm.ClassVisitor;
9 import org.objectweb.asm.ClassWriter;
11 import java.io.ByteArrayInputStream;
12 import java.io.File;
13 import java.io.FileOutputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.lang.instrument.ClassFileTransformer;
17 import java.net.URL;
18 import java.security.AccessController;
19 import java.security.PrivilegedAction;
20 import java.security.ProtectionDomain;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.jar.Attributes;
26 import java.util.jar.JarInputStream;
27 import java.util.jar.Manifest;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
31 /**
32 * Transforms user bytecode to implement various security restrictions.
35 public class Transformer implements ClassFileTransformer {
37 private static final Logger logger = Logger.getLogger(Transformer.class.getName());
39 private static final String APP_CLASS_LOADER
40 = "com.google.appengine.tools.development.IsolatedAppClassLoader";
42 private static final String APP_ENGINE_PACKAGE_PREFIX = "com/google/appengine/";
44 private static final String IMPLEMENTATION_VENDOR_ID_ATTR = "Implementation-Vendor-Id";
45 private static final String API_JAR_IMPLEMENTATION_VENDOR_ID = "com.google";
47 private final boolean treatRestrictedClassListViolationsAsErrors;
49 private AgentImpl agent = AgentImpl.getInstance();
51 private Set<URL> agentRuntimeLibs;
53 private final Map<URL, Boolean> userCodeSources = new HashMap<URL, Boolean>();
55 private static final String DUMP_CLASSES_PROPERTY = "com.google.appengine.dumpclasses";
57 /**
58 * Set to true to dump transformed classes. Useful for debugging.
60 private static final boolean dumpClasses = Boolean.getBoolean(DUMP_CLASSES_PROPERTY);
62 private static File dumpDir;
64 public Transformer(boolean treatRestrictedClassListViolationsAsErrors) {
65 this.treatRestrictedClassListViolationsAsErrors = treatRestrictedClassListViolationsAsErrors;
68 static {
69 if (dumpClasses) {
70 try {
71 dumpDir = File.createTempFile("transformed-classes", "");
72 dumpDir.delete();
73 logger.log(Level.INFO, "Dumping transformed classes to, " + dumpDir);
74 } catch (IOException e) {
75 e.printStackTrace();
80 private static String getClassPackage(String className) {
81 int lastPackageIndex = className.lastIndexOf('.');
82 if (lastPackageIndex == -1) {
83 return "";
84 } else {
85 return className.substring(0, lastPackageIndex).replace('.', '/');
89 private static String getSimpleName(String className) {
90 return className.substring(className.lastIndexOf('.') + 1);
93 @Override
94 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
95 ProtectionDomain domain, byte[] classBuffer) {
96 className = className.replace('/', '.');
98 if (!isAppLoader(loader) || isRuntimeCode(domain)) {
99 return null;
102 boolean isUserCode = !(isAppEngineCode(domain) ||
103 className.startsWith("java.") || className.startsWith("javax."));
104 try {
105 return rewrite(className, classBuffer, false, isUserCode, domain);
106 } catch (Throwable t) {
107 try {
108 return rewrite(className, classBuffer, true, isUserCode, domain);
109 } catch (Throwable t2) {
110 logger.log(Level.SEVERE, "Unable to instrument " + className + ". Security restrictions " +
111 "may not be entirely emulated.", t);
112 return null;
117 private byte[] rewrite(String className, byte[] classBuffer,
118 boolean stripLocalVars, boolean isUserCode, ProtectionDomain domain) throws IOException {
119 ClassReader cr = new ClassReader(new ByteArrayInputStream(classBuffer));
120 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
121 ClassVisitor visitor = cw;
122 visitor = new ObjectAccessVisitor(visitor, isUserCode,
123 treatRestrictedClassListViolationsAsErrors,
124 className, domain.getCodeSource().getLocation());
125 visitor = new ReflectionVisitor(visitor);
126 visitor = new ClassLoaderVisitor(visitor);
128 if (stripLocalVars) {
129 visitor = new StripLocalVariablesVisitor(visitor);
132 cr.accept(visitor, ClassReader.EXPAND_FRAMES);
133 byte[] bytes = cw.toByteArray();
135 if (dumpClasses) {
136 dumpClass(className, bytes);
139 return bytes;
142 private void dumpClass(final String className, final byte[] bytes) {
143 AccessController.doPrivileged(new PrivilegedAction<Object>() {
144 @Override
145 public Object run() {
146 String dir = dumpDir + File.separator + getClassPackage(className);
147 new File(dir).mkdirs();
148 try {
149 FileOutputStream fileOutput = new FileOutputStream(
150 dir + File.separator + getSimpleName(className) + ".class");
151 fileOutput.write(bytes);
152 fileOutput.close();
153 } catch (IOException e) {
154 logger.log(Level.WARNING, "Unable to dump class bytes for " + className, e);
156 return null;
162 * Returns true for runtime code (and its dependencies) which should not
163 * be instrumented.
165 private boolean isRuntimeCode(ProtectionDomain domain) {
166 return getAgentRuntimeLibs().contains(domain.getCodeSource().getLocation());
169 private Set<URL> getAgentRuntimeLibs() {
170 synchronized (this) {
171 if (agentRuntimeLibs == null) {
172 agentRuntimeLibs = new HashSet<URL>(SdkImplInfo.getAgentRuntimeLibs());
176 return agentRuntimeLibs;
179 private boolean isAppLoader(ClassLoader loader) {
181 if (loader == null) {
182 return false;
185 if (loader.getClass().getName().equals(APP_CLASS_LOADER)) {
186 return true;
189 if (agent.isAppConstructedURLClassLoader(loader)) {
190 return true;
193 return isAppLoader(loader.getClass().getClassLoader());
197 * Returns {@code true} if the CodeSource of the given domain is a jar
198 * provided by app engine. Such jars can be identified by the implementation
199 * vendor id attribute in the jar manifest.
201 private boolean isAppEngineCode(ProtectionDomain domain) {
202 URL url = domain.getCodeSource().getLocation();
203 if (url == null) {
204 return false;
206 synchronized (this) {
207 if (!userCodeSources.containsKey(url)) {
208 boolean isAppEngineLib = false;
209 if (url.getProtocol().equals("file") && url.getPath().endsWith(".jar")) {
210 JarInputStream jis = null;
211 try {
212 InputStream is = url.openStream();
213 jis = new JarInputStream(is);
214 Manifest manifest = jis.getManifest();
215 if (manifest != null) {
216 for (String attrName : manifest.getEntries().keySet()) {
217 if (attrName.startsWith(APP_ENGINE_PACKAGE_PREFIX)) {
218 Attributes attrs = manifest.getAttributes(attrName);
219 if (attrs != null) {
220 if (API_JAR_IMPLEMENTATION_VENDOR_ID.equals(
221 attrs.getValue(IMPLEMENTATION_VENDOR_ID_ATTR))) {
222 isAppEngineLib = true;
223 break;
229 } catch (IOException e) {
230 logger.log(Level.SEVERE, "Unable to process " + url, e);
231 } finally {
232 try {
233 if (jis != null) {
234 jis.close();
236 } catch (IOException e) {
237 logger.log(Level.SEVERE, "Unable to close JarInputStream", e);
241 userCodeSources.put(url, isAppEngineLib);
244 return userCodeSources.get(url);