/*
* 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.gui.actions.lookup;
import gui.actions.AbstractCanceableAction;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import analyser.gui.ClassTreeRenderer;
import analyser.gui.MainFrame;
import analyser.gui.Selection;
import analyser.logic.RefMethod;
import mereflect.CollaborateClassContext;
import mereflect.CorruptBytecodeException;
import mereflect.MEClass;
import mereflect.MEClassContext;
import mereflect.MEMethod;
public class FindNativeCallsAction extends AbstractCanceableAction {
private static final long serialVersionUID = -858370226885686630L;
protected static FindNativeCallsAction m_inst = null;
protected List<ArrayList<MEMethod>> m_result;
protected TraceDialog m_dialog = new TraceDialog();
public static FindNativeCallsAction getInstance(MainFrame mainFrame) {
if (m_inst == null) {
m_inst = new FindNativeCallsAction("Find native calls", null);
m_inst.setMainFrame(mainFrame);
}
return m_inst;
}
protected FindNativeCallsAction(String arg0, Icon arg1) {
super(arg0, arg1);
}
@Override
public void run(ActionEvent e) throws Throwable {
m_result = new ArrayList<ArrayList<MEMethod>>();
Object mRef = Selection.getSelectedObject();
if (mRef == null || !(mRef instanceof RefMethod)) {
return;
}
MEMethod mMethod = ((RefMethod) mRef).getMethod();
CollaborateClassContext refCtx = MainFrame.getInstance().getResolver().getReferenceContext();
CollaborateClassContext ctx = new CollaborateClassContext();
ctx.addContext(refCtx);
ctx.addContext(mMethod.getMEClass().getResource().getContext());
recurseInvokations(ctx, new HashSet<MEMethod>(), new ArrayList<MEMethod>(), mMethod);
if (isRunning()) {
getMainFrame().actionFinished(this);
showResult();
}
}
protected void recurseInvokations(MEClassContext ctx, Set<MEMethod> resolved,
List<MEMethod> callStack, MEMethod mMethod) throws IOException {
if (resolved.contains(mMethod)) {
return;
}
resolved.add(mMethod);
callStack.add(mMethod);
List<MEMethod.Invokation> invokations = null;
Iterator<MEMethod.Invokation> iI = null;
try {
invokations = mMethod.getInvokations();
iI = invokations.iterator();
} catch (CorruptBytecodeException cbe) {
}
while (isRunning() && iI != null && iI.hasNext()) {
MEMethod.Invokation invok = iI.next();
// int count = ((Integer) invokations.get(invok)).intValue();
try {
//MEClass rClass = ctx.getMEClass(invok.invClassname);
MEClass rClass = mMethod.getMEClass().getResource().getContext().getMEClass(invok.invClassname);
MEMethod rMethod = rClass.getMethod(invok.invMethodname, invok.invDescriptor);
if (rMethod == null) {
throw new ClassNotFoundException("Method " + invok.invMethodname + ":"
+ invok.invDescriptor + " not found in class " + rClass.getName());
}
if (rMethod.isNative()) {
callStack.add(rMethod);
reportNative(rMethod, callStack);
callStack.remove(rMethod);
} else {
recurseInvokations(ctx, resolved, callStack, rMethod);
}
} catch (ClassNotFoundException e2) {
}
}
callStack.remove(mMethod);
}
protected void reportNative(MEMethod nativeMethod, List<MEMethod> callStack) {
m_result.add(new ArrayList<MEMethod>(callStack));
}
protected void showResult() {
MainFrame mainFrame = (MainFrame) getMainFrame();
if (m_result.size() > 0) {
m_dialog.setSize(700, 500);
int x = mainFrame.getLocationOnScreen().x
+ (mainFrame.getWidth() - m_dialog.getWidth()) / 2;
int y = mainFrame.getLocationOnScreen().y
+ (mainFrame.getHeight() - m_dialog.getHeight()) / 2;
m_dialog.setLocation(x, y);
m_dialog.init();
m_dialog.setVisible(true);
mainFrame.initBottomInfo();
} else {
mainFrame.setBottomInfo("No native invokations found");
}
}
@Override
public void handleThrowable(Throwable t) {
t.printStackTrace();
getMainFrame().showError("Error resolving native invokations", t);
}
@Override
public String getWorkDescription() {
return "Resolving native invokations";
}
class TraceDialog extends JDialog {
private static final long serialVersionUID = -4423231097233811785L;
protected JTree m_tree = new JTree();
public TraceDialog() {
super(MainFrame.getInstance(), "Native invokations trace", false);
initGui();
}
protected void initGui() {
m_tree.setRootVisible(false);
m_tree.setShowsRootHandles(true);
DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer() {
private static final long serialVersionUID = -514794659033069799L;
protected boolean m_top = false;
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean exp, boolean leaf, int row, boolean focused) {
m_top = !leaf;
return super.getTreeCellRendererComponent(tree, value, sel, exp,
leaf, row, focused);
}
@Override
public void paint(Graphics g) {
if (!m_top) {
setForeground(Color.GRAY);
}
super.paint(g);
}
};
renderer.setOpenIcon(ClassTreeRenderer.ICON_PUBLICMETHOD);
renderer.setClosedIcon(ClassTreeRenderer.ICON_PUBLICMETHOD);
renderer.setLeafIcon(null);
m_tree.setCellRenderer(renderer);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(new JScrollPane(m_tree), BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
JButton closeButton = new JButton("Close");
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TraceDialog.this.setVisible(false);
}
});
buttonPanel.add(closeButton);
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
}
public void init() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode(
"Native invokations");
for (int i = 0; i < m_result.size(); i++) {
List<MEMethod> callStack = m_result.get(i);
int callStackSize = callStack.size();
if (callStackSize > 1) {
MEMethod nativeCall = callStack.get(callStackSize - 1);
DefaultMutableTreeNode nativeNode =
new DefaultMutableTreeNode(
nativeCall.getMEClass().getName() + "." +
nativeCall.getName() + "(" +
nativeCall.getArgumentsString() + ")");
for (int j = callStackSize - 2; j >= 0; j--) {
MEMethod subCall = callStack.get(j);
DefaultMutableTreeNode subCallNode = new DefaultMutableTreeNode(
subCall.getMEClass().getName() + "." +
subCall.getName() + "(" +
subCall.getArgumentsString() + ")");
nativeNode.add(subCallNode);
}
root.add(nativeNode);
}
}
m_tree.setModel(new DefaultTreeModel(root));
}
}
}