package bytecode;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class ApplyAT extends BaseStreamingJarProcessor {
public static class Pattern {
String clazz; // internal name
String object; // field name, or method name+descriptor
String action;
boolean matches(String clazz, String object) {
if(!this.clazz.equals(clazz))
return false;
if(this.object == null)
return object == null;
if(object == null)
return false;
if(this.object.equals("*"))
return !object.contains("(");
if(this.object.equals("*()"))
return object.contains("(");
return this.object.equals(object);
}
int changeAccess(int old) {
String action = this.action;
if(action.endsWith("-f")) {
action = action.substring(0, action.length() - 2);
old &= ~Opcodes.ACC_FINAL;
} else if(action.endsWith("+f")) {
action = action.substring(0, action.length() - 2);
old |= Opcodes.ACC_FINAL;
}
int oldAcc = old & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
switch(action) {
case "public":
old &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
old |= Opcodes.ACC_PUBLIC;
break;
case "protected":
if(oldAcc == Opcodes.ACC_PRIVATE || oldAcc == 0) {
old &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PUBLIC);
old |= Opcodes.ACC_PROTECTED;
}
break;
case "default":
if(oldAcc == Opcodes.ACC_PRIVATE)
old &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED);
break;
default: throw new RuntimeException("unknown action for "+clazz+"/"+object+": "+this.action);
}
return old;
}
}
public static List<Pattern> loadActions(Reader file) throws Exception {
List<Pattern> actions = new ArrayList<>();
try (Scanner s = new Scanner(file)) {
while(s.hasNextLine()) {
String line = s.nextLine();
if(line.contains("#"))
line = line.substring(0, line.indexOf('#'));
line = line.trim();
if(line.equals(""))
continue;
String[] parts = line.split(" ");
Pattern p = new Pattern();
p.action = parts[0];
p.clazz = parts[1].replace('.', '/');
if(parts.length < 3)
p.object = null;
else if(parts.length == 3)
p.object = parts[2].replace('.', '/');
else
throw new Exception("unparseable line: "+line);
actions.add(p);
}
}
return actions;
}
public static void main(String[] args) {
new ApplyAT().go(args);
}
private List<Pattern> actions;
@Override
public void loadConfig(Reader file) throws Exception {
actions = loadActions(file);
}
@Override
public ClassVisitor createClassVisitor(ClassVisitor parent) throws Exception {
return new ApplyATClassVisitor(parent, actions);
}
static int changeAccess(List<Pattern> actions, int old, String clazz, String object) {
for(Pattern p : actions)
if(p.matches(clazz, object))
old = p.changeAccess(old);
return old;
}
public static class ApplyATClassVisitor extends ClassVisitor {
private List<Pattern> actions;
public ApplyATClassVisitor(ClassVisitor parent, List<Pattern> actions) {
super(Opcodes.ASM5, parent);
this.actions = actions;
}
String classInternalName;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
access = changeAccess(actions, access, name, null);
classInternalName = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
access = changeAccess(actions, access, classInternalName, name);
return super.visitField(access, name, desc, signature, value);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
int newAccess = changeAccess(actions, access, name, null);
super.visitInnerClass(name, outerName, innerName, newAccess);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
access = changeAccess(actions, access, classInternalName, name+desc);
return new MethodVisitor(Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions)) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
// if calling a method which was private but now isn't, replace INVOKESPECIAL with INVOKEVIRTUAL
// check this by calling changeAccess with ACC_PRIVATE and checking if the result still has ACC_PRIVATE
if(opcode == Opcodes.INVOKESPECIAL && owner.equals(classInternalName) && !name.contains("<")) {
int methodNewAccess = changeAccess(actions, Opcodes.ACC_PRIVATE, classInternalName, name+desc);
if((methodNewAccess & Opcodes.ACC_PRIVATE) == 0)
opcode = Opcodes.INVOKEVIRTUAL;
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
};
}
}
}