Package com.google.appengine.tools.development.agent.impl

Source Code of com.google.appengine.tools.development.agent.impl.Transformer

// Copyright 2009 Google Inc. All Rights Reserved.

package com.google.appengine.tools.development.agent.impl;

import com.google.appengine.tools.info.SdkImplInfo;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Transforms user bytecode to implement various security restrictions.
*
*/
public class Transformer implements ClassFileTransformer {

  private static final Logger logger = Logger.getLogger(Transformer.class.getName());

  private static final String APP_CLASS_LOADER
      = "com.google.appengine.tools.development.IsolatedAppClassLoader";

  private static final String APP_ENGINE_PACKAGE_PREFIX = "com/google/appengine/";

  private static final String IMPLEMENTATION_VENDOR_ID_ATTR = "Implementation-Vendor-Id";
  private static final String API_JAR_IMPLEMENTATION_VENDOR_ID = "com.google";

  private final boolean treatRestrictedClassListViolationsAsErrors;

  private AgentImpl agent = AgentImpl.getInstance();

  private Set<URL> agentRuntimeLibs;

  private final Map<URL, Boolean> userCodeSources = new HashMap<URL, Boolean>();

  private static final String DUMP_CLASSES_PROPERTY = "com.google.appengine.dumpclasses";

  /**
   * Set to true to dump transformed classes. Useful for debugging.
   */
  private static final boolean dumpClasses = Boolean.getBoolean(DUMP_CLASSES_PROPERTY);

  private static File dumpDir;

  public Transformer(boolean treatRestrictedClassListViolationsAsErrors) {
    this.treatRestrictedClassListViolationsAsErrors = treatRestrictedClassListViolationsAsErrors;
  }

  static {
    if (dumpClasses) {
      try {
        dumpDir = File.createTempFile("transformed-classes", "");
        dumpDir.delete();
        logger.log(Level.INFO, "Dumping transformed classes to, " + dumpDir);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  private static String getClassPackage(String className) {
    int lastPackageIndex = className.lastIndexOf('.');
    if (lastPackageIndex == -1) {
      return "";
    } else {
      return className.substring(0, lastPackageIndex).replace('.', '/');
    }
  }

  private static String getSimpleName(String className) {
    return className.substring(className.lastIndexOf('.') + 1);
  }

  @Override
  public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
      ProtectionDomain domain, byte[] classBuffer) {
    className = className.replace('/', '.');

    if (!isAppLoader(loader) || isRuntimeCode(domain)) {
      return null;
    }

    boolean isUserCode = !(isAppEngineCode(domain) ||
        className.startsWith("java.") || className.startsWith("javax."));
    try {
      return rewrite(className, classBuffer, false, isUserCode, domain);
    } catch (Throwable t) {
      try {
        return rewrite(className, classBuffer, true, isUserCode, domain);
      } catch (Throwable t2) {
        logger.log(Level.SEVERE, "Unable to instrument " + className + ". Security restrictions " +
            "may not be entirely emulated.", t);
        return null;
      }
    }
  }

  private byte[] rewrite(String className, byte[] classBuffer,
      boolean stripLocalVars, boolean isUserCode, ProtectionDomain domain) throws IOException {
    ClassReader cr = new ClassReader(new ByteArrayInputStream(classBuffer));
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    ClassVisitor visitor = cw;
    visitor = new ObjectAccessVisitor(visitor, isUserCode,
        treatRestrictedClassListViolationsAsErrors,
        className, domain.getCodeSource().getLocation());
    visitor = new ReflectionVisitor(visitor);
    visitor = new ClassLoaderVisitor(visitor);

    if (stripLocalVars) {
      visitor = new StripLocalVariablesVisitor(visitor);
    }

    cr.accept(visitor, ClassReader.EXPAND_FRAMES);
    byte[] bytes = cw.toByteArray();

    if (dumpClasses) {
      dumpClass(className, bytes);
    }

    return bytes;
  }

  private void dumpClass(final String className, final byte[] bytes) {
    AccessController.doPrivileged(new PrivilegedAction<Object>() {
      @Override
      public Object run() {
        String dir = dumpDir + File.separator + getClassPackage(className);
        new File(dir).mkdirs();
        try {
          FileOutputStream fileOutput = new FileOutputStream(
              dir + File.separator + getSimpleName(className) + ".class");
          fileOutput.write(bytes);
          fileOutput.close();
        } catch (IOException e) {
          logger.log(Level.WARNING, "Unable to dump class bytes for " + className, e);
        }
        return null;
      }
    });
  }

  /**
   * Returns true for runtime code (and its dependencies) which should not
   * be instrumented.
   */
  private boolean isRuntimeCode(ProtectionDomain domain) {
    return getAgentRuntimeLibs().contains(domain.getCodeSource().getLocation());
  }

  private Set<URL> getAgentRuntimeLibs() {
    synchronized (this) {
      if (agentRuntimeLibs == null) {
        agentRuntimeLibs = new HashSet<URL>(SdkImplInfo.getAgentRuntimeLibs());
      }
    }

    return agentRuntimeLibs;
  }

  private boolean isAppLoader(ClassLoader loader) {

    if (loader == null) {
      return false;
    }

    if (loader.getClass().getName().equals(APP_CLASS_LOADER)) {
      return true;
    }

    if (agent.isAppConstructedURLClassLoader(loader)) {
      return true;
    }

    return isAppLoader(loader.getClass().getClassLoader());
  }

  /**
   * Returns {@code true} if the CodeSource of the given domain is a jar
   * provided by app engine. Such jars can be identified by the implementation
   * vendor id attribute in the jar manifest.
   */
  private boolean isAppEngineCode(ProtectionDomain domain) {
    URL url = domain.getCodeSource().getLocation();
    if (url == null) {
      return false;
    }
    synchronized (this) {
      if (!userCodeSources.containsKey(url)) {
        boolean isAppEngineLib = false;
        if (url.getProtocol().equals("file") && url.getPath().endsWith(".jar")) {
          JarInputStream jis = null;
          try {
            InputStream is = url.openStream();
            jis = new JarInputStream(is);
            Manifest manifest = jis.getManifest();
            if (manifest != null) {
              for (String attrName : manifest.getEntries().keySet()) {
                if (attrName.startsWith(APP_ENGINE_PACKAGE_PREFIX)) {
                  Attributes attrs = manifest.getAttributes(attrName);
                  if (attrs != null) {
                    if (API_JAR_IMPLEMENTATION_VENDOR_ID.equals(
                        attrs.getValue(IMPLEMENTATION_VENDOR_ID_ATTR))) {
                      isAppEngineLib = true;
                      break;
                    }
                  }
                }
              }
            }
          } catch (IOException e) {
            logger.log(Level.SEVERE, "Unable to process " + url, e);
          } finally {
            try {
              if (jis != null) {
                jis.close();
              }
            } catch (IOException e) {
              logger.log(Level.SEVERE, "Unable to close JarInputStream", e);
            }
          }
        }
        userCodeSources.put(url, isAppEngineLib);
      }
    }
    return userCodeSources.get(url);
  }
}
TOP

Related Classes of com.google.appengine.tools.development.agent.impl.Transformer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.