blob: a57538ecb99bab6afd8cd7b4886e5668ec3baa11 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.commons.weaver.privilizer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Set;
import javax.activation.DataSource;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.weaver.model.WeaveEnvironment;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;
/**
* Coordinates privilization activities.
*/
public class Privilizer {
/**
* An ASM {@link ClassVisitor} for privilization.
*/
abstract class PrivilizerClassVisitor extends ClassVisitor {
String className;
Type target;
protected PrivilizerClassVisitor() {
this(null);
}
protected PrivilizerClassVisitor(final ClassVisitor cv) { //NOPMD
super(Opcodes.ASM5, cv);
}
protected Privilizer privilizer() {
return Privilizer.this;
}
@Override
@SuppressWarnings("PMD.UseVarargs") //overridden method
public void visit(final int version, final int access, final String name, final String signature,
final String superName, final String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
className = name;
target = Type.getObjectType(name);
}
}
private final class CustomClassWriter extends ClassWriter {
CustomClassWriter(final int flags) {
super(flags);
}
CustomClassWriter(final ClassReader classReader, final int flags) {
super(classReader, flags);
}
@Override
protected String getCommonSuperClass(final String type1, final String type2) {
return "java/lang/Object";
}
}
/**
* Convenient {@link ClassVisitor} layer to write classfiles into the {@link WeaveEnvironment}.
*/
class WriteClass extends PrivilizerClassVisitor {
WriteClass(final ClassReader classReader, final int flags) {
super(new CustomClassWriter(classReader, flags));
}
WriteClass(final int flags) {
super(new CustomClassWriter(flags));
}
@Override
public void visitEnd() {
super.visitEnd();
final byte[] bytecode = ((ClassWriter) cv).toByteArray();
if (verify) {
verify(className, bytecode);
}
final DataSource classfile = env.getClassfile(className);
env.debug("Writing class %s to resource %s", className, classfile.getName());
OutputStream outputStream = null;
try {
outputStream = classfile.getOutputStream();
IOUtils.write(bytecode, outputStream);
} catch (final IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(outputStream);
}
}
}
/**
* Privilizer weaver configuration prefix.
*/
public static final String CONFIG_WEAVER = "privilizer.";
/**
* {@link AccessLevel} configuration key.
* @see AccessLevel#parse(String)
*/
public static final String CONFIG_ACCESS_LEVEL = CONFIG_WEAVER + "accessLevel";
/**
* Weave {@link Policy} configuration key.
* @see Policy#parse(String)
*/
public static final String CONFIG_POLICY = CONFIG_WEAVER + "policy";
/**
* Verification configuration key.
* @see BooleanUtils#toBoolean(String)
*/
public static final String CONFIG_VERIFY = CONFIG_WEAVER + "verify";
private static final String GENERATE_NAME = "__privileged_%s";
static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
final WeaveEnvironment env;
final AccessLevel accessLevel;
final Policy policy;
final boolean verify;
/**
* Create a new {@link Privilizer}.
* @param env to use
*/
public Privilizer(final WeaveEnvironment env) {
super();
this.env = env;
this.policy = Policy.parse(env.config.getProperty(CONFIG_POLICY));
this.accessLevel = AccessLevel.parse(env.config.getProperty(CONFIG_ACCESS_LEVEL));
verify = BooleanUtils.toBoolean(env.config.getProperty(CONFIG_VERIFY));
}
String generateName(final String simple) {
return String.format(GENERATE_NAME, simple);
}
Type wrap(final Type type) {
switch (type.getSort()) {
case Type.BOOLEAN:
return Type.getType(Boolean.class);
case Type.BYTE:
return Type.getType(Byte.class);
case Type.SHORT:
return Type.getType(Short.class);
case Type.INT:
return Type.getType(Integer.class);
case Type.CHAR:
return Type.getType(Character.class);
case Type.LONG:
return Type.getType(Long.class);
case Type.FLOAT:
return Type.getType(Float.class);
case Type.DOUBLE:
return Type.getType(Double.class);
case Type.VOID:
return Type.getType(Void.class);
default:
return type;
}
}
void blueprint(final Class<?> type, final Privilizing privilizing) {
final Object[] args = { type.getName(), privilizing };
env.debug("blueprinting class %s %s", args);
InputStream bytecode = null;
try {
bytecode = env.getClassfile(type).getInputStream();
final ClassReader classReader = new ClassReader(bytecode);
ClassVisitor cvr;
cvr = new WriteClass(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cvr = new PrivilizingVisitor(this, cvr);
cvr = new BlueprintingVisitor(this, cvr, privilizing);
classReader.accept(cvr, ClassReader.EXPAND_FRAMES);
} catch (final Exception e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(bytecode);
}
}
void privilize(final Class<?> type) {
final Object[] args = { type.getName() };
env.debug("privilizing class %s", args);
InputStream bytecode = null;
try {
bytecode = env.getClassfile(type).getInputStream();
final ClassReader classReader = new ClassReader(bytecode);
ClassVisitor cv; //NOPMD
cv = new WriteClass(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cv = new PrivilizingVisitor(this, cv);
classReader.accept(cv, ClassReader.EXPAND_FRAMES);
} catch (final Exception e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(bytecode);
}
}
void verify(final String className, final byte[] bytecode) {
final ClassReader reader = new ClassReader(bytecode);
env.debug("Verifying bytecode for class %s", className);
final StringWriter w = new StringWriter(); //NOPMD
CheckClassAdapter.verify(reader, env.classLoader, false, new PrintWriter(w));
final String error = w.toString();
if (!error.isEmpty()) {
env.error(error);
final StringWriter trace = new StringWriter();
reader.accept(new TraceClassVisitor(new PrintWriter(trace)), ClassReader.SKIP_DEBUG);
env.debug(trace.toString());
throw new IllegalStateException();
}
Validate.validState(StringUtils.isBlank(error), error);
final ClassVisitor checkInnerClasses = new ClassVisitor(Opcodes.ASM5, null) {
final Set<String> innerNames = new HashSet<String>();
@Override
public void visitInnerClass(final String name, final String outerName, final String innerName,
final int access) {
super.visitInnerClass(name, outerName, innerName, access);
Validate.validState(innerNames.add(innerName), "%s already defined", innerName);
}
};
reader.accept(checkInnerClasses, ClassReader.SKIP_CODE);
}
}