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
;
13 import java
.io
.FileOutputStream
;
14 import java
.io
.IOException
;
15 import java
.io
.InputStream
;
16 import java
.lang
.instrument
.ClassFileTransformer
;
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
;
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
;
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";
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
;
71 dumpDir
= File
.createTempFile("transformed-classes", "");
73 logger
.log(Level
.INFO
, "Dumping transformed classes to, " + dumpDir
);
74 } catch (IOException e
) {
80 private static String
getClassPackage(String className
) {
81 int lastPackageIndex
= className
.lastIndexOf('.');
82 if (lastPackageIndex
== -1) {
85 return className
.substring(0, lastPackageIndex
).replace('.', '/');
89 private static String
getSimpleName(String className
) {
90 return className
.substring(className
.lastIndexOf('.') + 1);
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
)) {
102 boolean isUserCode
= !(isAppEngineCode(domain
) ||
103 className
.startsWith("java.") || className
.startsWith("javax."));
105 return rewrite(className
, classBuffer
, false, isUserCode
, domain
);
106 } catch (Throwable t
) {
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
);
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();
136 dumpClass(className
, bytes
);
142 private void dumpClass(final String className
, final byte[] bytes
) {
143 AccessController
.doPrivileged(new PrivilegedAction
<Object
>() {
145 public Object
run() {
146 String dir
= dumpDir
+ File
.separator
+ getClassPackage(className
);
147 new File(dir
).mkdirs();
149 FileOutputStream fileOutput
= new FileOutputStream(
150 dir
+ File
.separator
+ getSimpleName(className
) + ".class");
151 fileOutput
.write(bytes
);
153 } catch (IOException e
) {
154 logger
.log(Level
.WARNING
, "Unable to dump class bytes for " + className
, e
);
162 * Returns true for runtime code (and its dependencies) which should not
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) {
185 if (loader
.getClass().getName().equals(APP_CLASS_LOADER
)) {
189 if (agent
.isAppConstructedURLClassLoader(loader
)) {
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();
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;
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
);
220 if (API_JAR_IMPLEMENTATION_VENDOR_ID
.equals(
221 attrs
.getValue(IMPLEMENTATION_VENDOR_ID_ATTR
))) {
222 isAppEngineLib
= true;
229 } catch (IOException e
) {
230 logger
.log(Level
.SEVERE
, "Unable to process " + url
, e
);
236 } catch (IOException e
) {
237 logger
.log(Level
.SEVERE
, "Unable to close JarInputStream", e
);
241 userCodeSources
.put(url
, isAppEngineLib
);
244 return userCodeSources
.get(url
);