/*
* Copyright (C) 2012 Sony Mobile Communications AB
*
* This file is part of ApkAnalyser.
*
* Licensed 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 analyser.logic;
import gui.Canceable;
import java.util.ArrayList;
import java.util.List;
import analyser.gui.ProgressReporter;
import mereflect.MEClass;
import mereflect.MEClassContext;
import mereflect.MEMethod;
public class InvSnooper {
public static final int ABSTRACT = 1 << 0;
public static final int OVERRIDDEN = 1 << 1;
private static void registerInvokationResult(List<Invokation> invs,
MEClass fromClass, MEMethod fromMethod,
MEClass toClass, MEMethod toMethod,
int flags,
boolean allowDuplicates) {
Invokation inv = new Invokation(fromClass, fromMethod, toClass, toMethod, flags);
if (allowDuplicates || !invs.contains(inv)) {
invs.add(inv);
}
}
public static List<Invokation> findCalls(RefMethod ref, boolean virtualCall, boolean allowDuplicates, Canceable canceable,
ProgressReporter reporter) throws Throwable {
return findCalls(ref.getMethod(), virtualCall, allowDuplicates, canceable, reporter);
}
public static List<Invokation> findCalls(MEMethod fromMethod, boolean virtualCall, boolean allowDuplicates, Canceable canceable,
ProgressReporter reporter) throws Throwable {
MEClass fromClass = fromMethod.getMEClass();
MEClassContext fromCtx = fromClass.getResource().getContext();
List<Invokation> invRes = new ArrayList<Invokation>();
// If virtual call, we need to check actual calls in method in addition to possible overrides
if (fromMethod.isAbstract() || virtualCall) {
// Coming from a pure interface/abstract method (1)
List<MEClass> implementors = findClassChildren(fromClass);
if (reporter != null) {
reporter.reportStart(implementors.size());
}
implementors.remove(fromClass);
for (int i = 0; i < implementors.size() &&
(canceable != null && canceable.isRunning() || canceable == null); i++) {
MEClass candidateClass = implementors.get(i);
MEMethod rLocalMethod = candidateClass.getMethodIsolated(fromMethod.getName(), fromMethod.getDescriptor());
if (rLocalMethod != null) {
registerInvokationResult(invRes, fromClass, fromMethod, candidateClass,
rLocalMethod, ABSTRACT, allowDuplicates);
if (reporter != null) {
reporter.reportWork(i);
}
}
}
}
if (!fromMethod.isAbstract() || virtualCall) {
// Coming from a normal method (2)
List<MEMethod.Invokation> invokations = fromMethod.getInvokations();
if (reporter != null) {
reporter.reportStart(invokations.size());
}
for (int i = 0; i < invokations.size() &&
(canceable != null && canceable.isRunning() || canceable == null); i++) {
MEMethod.Invokation inv = invokations.get(i);
try {
MEClass invClass = fromCtx.getMEClass(inv.invClassname);
if (invClass.isAbstract() || invClass.isInterface() || inv.isInterface || inv.isVirtual) {
// Calling an interface or abstract method, just add this since it will be resolved in (1)
MEMethod invMethod = invClass.getMethodIsolated(inv.invMethodname, inv.invDescriptor);
if (invMethod != null) {
registerInvokationResult(invRes, fromClass, fromMethod, invClass, invMethod, ABSTRACT, allowDuplicates);
}
} else {
// Calling a normal method, check if there are overrides of this method
List<MEClass> implementors = findClassChildren(invClass);
for (int j = 0; j < implementors.size(); j++) {
invClass = implementors.get(j);
MEMethod invMethod = invClass.getMethodIsolated(inv.invMethodname, inv.invDescriptor);
if (invMethod != null) {
registerInvokationResult(invRes, fromClass, fromMethod, invClass, invMethod, implementors.size() > 1 ? OVERRIDDEN : 0, allowDuplicates);
}
}
}
} catch (ClassNotFoundException cnfe) {
// not a class in midlet
}
if (reporter != null) {
reporter.reportWork(i);
}
}
}
if (reporter != null) {
reporter.reportEnd();
}
return invRes;
}
public static List<Invokation> findCallers(RefMethod ref, boolean allowDuplicates, Canceable canceable,
ProgressReporter reporter) throws Throwable {
return findCallers(ref.getMethod(), allowDuplicates, canceable, reporter);
}
public static List<Invokation> findCallers(MEMethod toMethod, boolean allowDuplicates, Canceable canceable,
ProgressReporter reporter) throws Throwable {
MEClass toClass = toMethod.getMEClass();
MEClassContext ctx = toClass.getResource().getContext();
String[] classNames = ctx.getClassnames();
List<Invokation> invRes = new ArrayList<Invokation>();
List<MEClass> definitors = findClassParents(toClass);
if (reporter != null) {
reporter.reportStart(classNames.length);
}
// All classes in toClass context
for (int i = 0; i < classNames.length && (canceable != null && canceable.isRunning() || canceable == null); i++) {
MEClass fromCandidateClass = ctx.getMEClass(classNames[i]);
MEMethod[] candidateMethods = fromCandidateClass.getMethods();
// All methods
if (candidateMethods != null) {
for (int j = 0; j < candidateMethods.length; j++) {
MEMethod fromCandidateMethod = candidateMethods[j];
List<MEMethod.Invokation> invokations = fromCandidateMethod.getInvokations();
// From all classes, all methods, to all parents of toClass incl toClass
for (int k = 0; k < definitors.size() && (canceable != null && canceable.isRunning() || canceable == null); k++) {
MEClass toDefiningClass = definitors.get(k);
// All classes, all methods, all invokations
for (int l = 0; l < invokations.size() && (canceable != null && canceable.isRunning() || canceable == null); l++) {
MEMethod.Invokation inv = invokations.get(l);
if (inv.invClassname.equals(toDefiningClass.getName()) &&
inv.invMethodname.equals(toMethod.getName()) &&
inv.invDescriptor.equals(toMethod.getDescriptor())) {
MEMethod toDefiningMethod = toDefiningClass.getMethodIsolated(inv.invMethodname, inv.invDescriptor);
if (toDefiningMethod != null) {
if ((inv.isInterface || inv.isVirtual) && !toDefiningClass.equals(toClass) && !toDefiningMethod.equals(toMethod)) {
// put in interface or overridden class in between actual call
registerInvokationResult(invRes, toClass, toMethod, toDefiningClass, toDefiningMethod,
toDefiningClass.equals(toClass) ? InvSnooper.ABSTRACT : InvSnooper.OVERRIDDEN, allowDuplicates);
} else {
registerInvokationResult(invRes, toDefiningClass, toDefiningMethod, fromCandidateClass, fromCandidateMethod,
(inv.isInterface || inv.isVirtual) ? InvSnooper.ABSTRACT : 0, allowDuplicates);
}
}
}
}
}
}
}
if (reporter != null) {
reporter.reportWork(i);
}
}
if (reporter != null && canceable != null && canceable.isRunning()) {
reporter.reportEnd();
}
return invRes;
}
public static List<MEClass> findClassChildren(MEClass clazz) throws Throwable {
List<MEClass> res = new ArrayList<MEClass>();
MEClassContext ctx = clazz.getResource().getContext();
String[] classNames = ctx.getClassnames();
for (int i = 0; i < classNames.length; i++) {
MEClass candidate = ctx.getMEClass(classNames[i]);
if (candidate.isInstanceOf(clazz)) {
res.add(candidate);
}
}
return res;
}
public static List<MEClass> findClassParents(MEClass clazz) throws Throwable {
List<MEClass> res = new ArrayList<MEClass>();
res.add(clazz);
recurseParents(clazz, res);
return res;
}
private static void recurseParents(MEClass clazz, List<MEClass> res) throws Throwable {
MEClass superClass = clazz.getSuperClass();
if (superClass != null) {
res.add(superClass);
recurseParents(superClass, res);
}
MEClass[] ifcs = clazz.getInterfaces();
if (ifcs != null) {
for (int i = 0; i < ifcs.length; i++) {
res.add(ifcs[i]);
}
for (int i = 0; i < ifcs.length; i++) {
recurseParents(ifcs[i], res);
}
}
}
public static List<RefInvokation> toRefInvokations(List<Invokation> invokations) {
List<RefInvokation> res = new ArrayList<RefInvokation>();
for (int i = 0; i < invokations.size(); i++) {
Invokation inv = invokations.get(i);
String invStr = inv.fromClass.getName() + "." +
inv.fromMethod.getFormattedName() +
"(" + inv.fromMethod.getArgumentsString() + ")";
RefContext refContext = new RefContext(inv.fromClass.getResource().getContext());
String refPackName = inv.toClass.getResource().getPackage();
RefPackage refPack = refContext.registerPackage(refPackName);
RefClass refClass = refPack.registerClass(inv.toClass);
RefMethod refMethod = refClass.registerMethod(inv.toMethod);
RefInvokation refInv = new RefInvokation(invStr, refContext,
refPack, refClass, refMethod, true, null);
refMethod.registerInvokation(refInv);
res.add(refInv);
}
return res;
}
public static class Invokation {
public MEClass fromClass;
public MEMethod fromMethod;
public MEClass toClass;
public MEMethod toMethod;
public int flags;
public Invokation(MEClass fromClass, MEMethod fromMethod, MEClass toClass, MEMethod toMethod, int flags) {
this.fromClass = fromClass;
this.fromMethod = fromMethod;
this.toClass = toClass;
this.toMethod = toMethod;
this.flags = flags;
}
@Override
public boolean equals(Object o) {
if (o instanceof Invokation) {
Invokation i = (Invokation) o;
return (i.fromClass.equals(fromClass) &&
i.fromMethod.equals(fromMethod) &&
i.toClass.equals(toClass) && i.toMethod.equals(toMethod));
} else {
return false;
}
}
}
}