use of org.mapleir.asm.ClassNode in project maple-ir by LLVM-but-worse.
the class FieldRenamerPass method accept.
@Override
public PassResult accept(PassContext pcxt) {
AnalysisContext cxt = pcxt.getAnalysis();
Map<FieldNode, String> remapped = new HashMap<>();
// int totalFields = 0;
// int i = RenamingUtil.computeMinimum(totalFields);
ApplicationClassSource source = cxt.getApplication();
int i = RenamingUtil.numeric("aaaaa");
for (ClassNode cn : source.iterate()) {
// totalFields += cn.fields.size();
for (FieldNode fn : cn.getFields()) {
remapped.put(fn, RenamingUtil.createName(i++));
}
}
InvocationResolver resolver = cxt.getInvocationResolver();
for (ClassNode cn : source.iterate()) {
for (MethodNode m : cn.getMethods()) {
ControlFlowGraph cfg = cxt.getIRCache().getFor(m);
for (BasicBlock b : cfg.vertices()) {
for (Stmt stmt : b) {
if (stmt.getOpcode() == Opcode.FIELD_STORE) {
FieldStoreStmt fs = (FieldStoreStmt) stmt;
FieldNode f = resolver.findField(fs.getOwner(), fs.getName(), fs.getDesc(), fs.getInstanceExpression() == null);
if (f != null) {
if (remapped.containsKey(f)) {
fs.setName(remapped.get(f));
} else if (mustMark(source, f.getOwner())) {
System.err.println(" no remap for " + f + ", owner: " + f.getOwner());
}
} else {
if (mustMark(source, fs.getOwner())) {
System.err.println(" can't resolve field(set): " + fs.getOwner() + "." + fs.getName() + " " + fs.getDesc() + ", " + (fs.getInstanceExpression() == null));
}
}
}
for (Expr e : stmt.enumerateOnlyChildren()) {
if (e.getOpcode() == Opcode.FIELD_LOAD) {
FieldLoadExpr fl = (FieldLoadExpr) e;
FieldNode f = resolver.findField(fl.getOwner(), fl.getName(), fl.getDesc(), fl.getInstanceExpression() == null);
if (f != null) {
if (remapped.containsKey(f)) {
fl.setName(remapped.get(f));
} else if (mustMark(source, f.getOwner())) {
System.err.println(" no remap for " + f + ", owner: " + f.getOwner());
}
} else {
if (mustMark(source, fl.getOwner())) {
System.err.println(" can't resolve field(get): " + fl.getOwner() + "." + fl.getName() + " " + fl.getDesc() + ", " + (fl.getInstanceExpression() == null));
}
}
}
}
}
}
}
}
for (Entry<FieldNode, String> e : remapped.entrySet()) {
e.getKey().node.name = e.getValue();
}
System.out.printf(" Renamed %d fields.%n", remapped.size());
return PassResult.with(pcxt, this).finished().make();
}
use of org.mapleir.asm.ClassNode in project maple-ir by LLVM-but-worse.
the class DefaultInvocationResolver method computeVTable.
private void computeVTable(ClassNode c) {
if (c == null) {
throw new NullPointerException();
}
/* only visit each class once */
if (hasVisited(c)) {
return;
}
/* ensure parents loaded */
ClassNode superKlass = null;
/* if the super class is null it means we're at object and so
* we don't even have to consider interfaces: as we stop
* here */
if (c.node.superName != null) {
computeVTable(superKlass = app.findClassNode(c.node.superName));
for (String i : c.node.interfaces) {
computeVTable(app.findClassNode(i));
}
}
/* the general plan of attack here is:
* 1. add our own methods.
* 2. merge without our super class methods.
* 3. merge and resolve with our interfaces. */
/* we build our local tables for the current class. it's
* important to keep these separate from what will become the
* completely hierarchy sensitive lookup tables later and so
* technically should be immutable after the loop but we can
* ignore this as long as we do not modify them later. */
Map<Selector, MethodNode> thisMethodSet = new HashMap<>();
Map<Selector, MethodNode> thisAbstractSet = new HashMap<>();
for (MethodNode m : c.getMethods()) {
/* we can easily resolve these as they come
* so we don't even need them in the table. */
if (Modifier.isStatic(m.node.access)) {
continue;
}
/* immutable lookup key */
Selector s = new Selector(m.getName(), m.getDesc());
/* store our local declarations. */
if (Modifier.isAbstract(m.node.access)) {
putOrThrow(thisAbstractSet, s, m);
} else {
putOrThrow(thisMethodSet, s, m);
}
}
if (debugLevel >= 2) {
LOGGER.debug("Class: " + c);
LOGGER.debug(" super: " + c.node.superName);
LOGGER.debug(" interfaces: " + c.node.interfaces.toString());
LOGGER.debug(" implSet: ");
print(thisMethodSet);
LOGGER.debug(" absSet: ");
print(thisAbstractSet);
}
/* Store the local results as the global as in the case of
* java/lang/Object we don't have a super class and so the
* ensuing analysis (below) is never executed. It's important
* to note that we shouldn't be looking in the global vtable
* for a given class while processing that class; the local
* maps/vtable values are used but not the field
* references. */
concreteVTables.put(c, thisMethodSet);
abstractVTables.put(c, thisAbstractSet);
if (superKlass != null) {
if (!hasVisited(superKlass)) {
throw new IllegalStateException(String.format("Parent of %s, %s is not initialised", c, superKlass));
}
Map<Selector, MethodNode> globalCVT = new HashMap<>();
Map<Selector, MethodNode> globalAVT = new HashMap<>();
/* inherit all super class methods */
globalCVT.putAll(concreteVTables.get(superKlass));
globalAVT.putAll(abstractVTables.get(superKlass));
assertIntersection(thisAbstractSet.entrySet(), thisMethodSet.entrySet(), Collections.emptySet());
/* (1) and (2)
* NOTE: thisAbstractSet and thisMethodSet should
* intersect to give the null set. */
for (Selector s : thisAbstractSet.keySet()) {
globalCVT.remove(s);
}
for (Selector s : thisMethodSet.keySet()) {
globalAVT.remove(s);
}
/* we shouldn't ever get merge errors from considering the
* current class with it's super. (this can happen with
* interfaces, however) */
assertIntersection(globalAVT.entrySet(), globalCVT.entrySet(), Collections.emptySet());
/* add our own declarations to the tables. this could possibly
* override methods from the super class and we're happy
* about this. */
globalCVT.putAll(thisMethodSet);
globalAVT.putAll(thisAbstractSet);
assertIntersection(globalAVT.entrySet(), globalCVT.entrySet(), Collections.emptySet());
concreteVTables.put(c, globalCVT);
abstractVTables.put(c, globalAVT);
if (debugLevel >= 3) {
LOGGER.debug(" globalCVT: ");
print(globalCVT);
LOGGER.debug(" globalAVT: ");
print(globalAVT);
}
NullPermeableHashMap<Selector, Set<MethodNode>> mergeMap = new NullPermeableHashMap<>(HashSet::new);
if (debugLevel >= 2) {
LOGGER.debug(" process interfaces:");
}
for (String i : c.node.interfaces) {
if (debugLevel >= 2) {
LOGGER.debug(" " + i);
}
ClassNode interfaceKlass = app.findClassNode(i);
/* An abstract interface method cannot kill
* a concrete class implementation of the method
* from the super class. However, if the reaching
* definition is a default implementation from
* another interface which is a superclass of the
* current interface, then the reaching definition
* in the current interface is an abstract method
* and so that is propagated into the implementor.
*
* If for example the super interface defines the
* abstract method and the sub interface has a
* default implementation and we implement the
* default interface, the default method will become
* the reaching definition for the current class,
* not the abstract one.
*
* If a class that implements a default method
* already has a reaching definition in the parent,
* the subinterface default method takes priority.*/
add(concreteVTables.get(interfaceKlass), mergeMap);
add(abstractVTables.get(interfaceKlass), mergeMap);
}
if (debugLevel >= 3) {
LOGGER.debug(" mergeMap:");
printMap(mergeMap);
}
for (Entry<Selector, Set<MethodNode>> e : mergeMap.entrySet()) {
Selector selector = e.getKey();
Set<MethodNode> conflictingMethods = e.getValue();
MethodNode resolve;
if (conflictingMethods.size() > 1) {
if (debugLevel >= 2) {
LOGGER.debug(" conflicts: " + conflictingMethods.size());
LOGGER.debug(" " + selector);
for (MethodNode m : conflictingMethods) {
LOGGER.debug(" ^" + m);
}
}
/* if we have any sort of declaration, we can easily say that
* that is the target without having to check the hierarchy
* and globalCVT rules.
*
* 05/10/17: globalCVT should contain super defs as well or ours
* so surely we use this as it doesn't contain any
* abstracts?*/
if (globalCVT.containsKey(selector)) {
resolve = globalCVT.get(selector);
if (debugLevel >= 2) {
LOGGER.debug(" (1)resolved to " + resolve);
}
} else if (thisAbstractSet.containsKey(selector)) {
resolve = thisAbstractSet.get(selector);
if (debugLevel >= 2) {
LOGGER.debug(" (2)resolved to " + resolve);
}
} else {
/* here is where it gets tricky. */
/* 05/10/17: weird bug? example:
* javax/swing/LayoutComparator implements Comparable
* Comparable extends Object by OUR own definition.
* Comparable reabstracts equals(Object) but LayoutComparator
* extends Object implicitly, so the Object definition is
* reaching unless it is redefined in LayoutComparator.
* Note that you can't have default methods for Object methods.
*
* So do we check for it here or inherit Objects methods before we
* do anything else?
* solution: use global vtable merge interface merges above?
*/
Collection<MethodNode> contenders = getMaximallySpecific(conflictingMethods);
if (contenders.isEmpty()) {
throw new IllegalStateException();
}
if (contenders.size() == 1) {
/* design decision: if there is only 1 contender but multiple
* conflicting methods, all of the conflicting methods are
* defaults, then we have a clear target method, but if any of
* them are abstract, we are actually propagating a
* reabstraction through this class, so we can insert a
* miranda. */
boolean anyAbstract = false;
for (MethodNode m : contenders) {
anyAbstract |= Modifier.isAbstract(m.node.access);
}
if (anyAbstract) {
/* valid: miranda (reabstraction) */
resolve = null;
} else {
resolve = contenders.iterator().next();
}
if (debugLevel >= 2) {
LOGGER.debug(" (3)resolved to " + resolve);
}
} else {
if (Modifier.isAbstract(c.node.access)) {
boolean allAbstract = true;
for (MethodNode m : contenders) {
allAbstract &= Modifier.isAbstract(m.node.access);
}
if (allAbstract) {
/* valid: miranda */
resolve = null;
} else {
/* require a declaration but we don't
* have one -> die. */
throw new IllegalStateException(String.format("Method %s is ambiguous in %s, candidates:%s", selector, c, conflictingMethods));
}
} else {
/* require a declaration but we don't
* have one -> die. */
throw new IllegalStateException(String.format("Method %s is ambiguous in %s, candidates:%s", selector, c, conflictingMethods));
}
}
}
} else {
/* no conflicts, so we can just choose the resolve method as
* the target method from one of the tables. */
if (globalCVT.containsKey(selector)) {
resolve = globalCVT.get(selector);
} else if (thisAbstractSet.containsKey(selector)) {
/* thisAbstractSet instead of the global one because
* these conflicts would make things confusing if
* the global table was constantly changing. */
resolve = thisAbstractSet.get(selector);
} else {
/* no conflict but the current class just does not implement
* the method itself. we could either create a miranda here or
* reference to method. we will do the latter. */
// size == 1
resolve = conflictingMethods.iterator().next();
}
}
/* delete from both so we can do a clean add after. */
globalAVT.remove(selector);
globalCVT.remove(selector);
if (resolve == null && !Modifier.isAbstract(c.node.access)) {
throw new IllegalStateException(String.format("Miranda %s in non abstract class %s", conflictingMethods, c));
}
if (resolve == null) {
// TODO: sigs?
resolve = new MethodNode(new org.objectweb.asm.tree.MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, selector.name, selector.desc, null, getExceptionClasses(conflictingMethods)), c);
c.addMethod(resolve);
if (debugLevel >= 2) {
LOGGER.debug(" generated miranda " + resolve);
}
}
if (Modifier.isAbstract(resolve.node.access)) {
globalAVT.put(selector, resolve);
} else {
globalCVT.put(selector, resolve);
}
}
assertIntersection(concreteVTables.get(c).entrySet(), abstractVTables.get(c).entrySet(), Collections.emptySet());
if (debugLevel >= 2) {
LOGGER.debug(" cvtable: ");
print(concreteVTables.get(c));
LOGGER.debug(" avtable: ");
print(abstractVTables.get(c));
}
}
}
use of org.mapleir.asm.ClassNode in project maple-ir by LLVM-but-worse.
the class DefaultInvocationResolver method resolveVirtualCalls.
@Override
public Set<MethodNode> resolveVirtualCalls(String owner, String name, String desc, boolean strict) {
/* find concrete receivers and resolve */
ClassNode cn = app.findClassNode(owner);
if (!checkNullClass(cn, owner)) {
return Collections.emptySet();
}
Set<MethodNode> result = new HashSet<>();
for (ClassNode receiver : app.getClassTree().getAllChildren(cn)) {
if (!Modifier.isAbstract(receiver.node.access)) {
// use strict mode = false for incomplete analysis
MethodNode target = resolve(receiver, name, desc, true);
if (target == null || Modifier.isAbstract(target.node.access)) {
throw new IllegalStateException(String.format("Could not find vtarget for %s.%s%s", owner, name, desc));
}
result.add(target);
}
}
return result;
}
use of org.mapleir.asm.ClassNode in project maple-ir by LLVM-but-worse.
the class HierarchyMethods method getHierarchyMethodChain.
public Set<MethodNode> getHierarchyMethodChain(ClassNode cn, String name, String desc, boolean exact) {
ClassTree structures = app.getClassTree();
Set<MethodNode> foundMethods = new HashSet<>();
Collection<ClassNode> toSearch = structures.getAllBranches(cn);
for (ClassNode viable : toSearch) {
foundMethods.addAll(findMethods(viable, name, desc, exact ? EXACT_TYPES : CONGRUENT_TYPES, VIRTUAL_METHOD | Modifier.ABSTRACT | Opcodes.ACC_BRIDGE, 0));
}
return foundMethods;
}
use of org.mapleir.asm.ClassNode in project maple-ir by LLVM-but-worse.
the class HierarchyMethods method areTypesCongruent.
/**
* Two types are congruent if they are primitive and the same, or if one is a subclass of another.
* @param a type A
* @param b type B
* @return true if type A and B are congruent
*/
private boolean areTypesCongruent(Type a, Type b) {
if (a.equals(b)) {
return true;
}
boolean eArr = a.getSort() == Type.ARRAY;
boolean aArr = b.getSort() == Type.ARRAY;
if (eArr != aArr) {
return false;
}
if (eArr) {
a = a.getElementType();
b = b.getElementType();
}
if (TypeUtils.isPrimitive(a) || TypeUtils.isPrimitive(b)) {
return false;
}
if (a == Type.VOID_TYPE || b == Type.VOID_TYPE) {
return false;
}
ClassNode cnA = app.findClassNode(a.getInternalName());
ClassNode cnB = app.findClassNode(b.getInternalName());
ClassTree tree = app.getClassTree();
return tree.getAllParents(cnB).contains(cnA) || tree.getAllParents(cnA).contains(cnB);
}
Aggregations