Skip to content

Instantly share code, notes, and snippets.

@kbsriram
Created May 27, 2014 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kbsriram/a55a360a3457f4d11d8d to your computer and use it in GitHub Desktop.
Save kbsriram/a55a360a3457f4d11d8d to your computer and use it in GitHub Desktop.
Generating android dependencies
package com.kbsriram.depviz.jvm;
import java.io.DataInput;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
public class CExtractUses
{
public final static class Info
{
private Info()
{
m_classes = new ArrayList<String>();
m_codesize = 0l;
}
public List<String> getClasses()
{ return m_classes; }
public long getCodeSize()
{ return m_codesize; }
private void addClass(String cl)
{ m_classes.add(cl); }
private void addCodeSize(long l)
{ m_codesize += l; }
private final List<String> m_classes;
private long m_codesize;
}
// Given an inputstrem (from a .class file) grab all the
// classnames that it uses.
public final static Info ofBytecode(DataInput inp)
throws IOException
{
Info ret = new Info();
// u4 magic
if (inp.readInt() != 0xcafebabe) {
throw new IOException("bad class file");
}
// u2 minor
inp.readUnsignedShort();
// u2 major
inp.readUnsignedShort();
int cpcnt = inp.readUnsignedShort();
String[] symbols = new String[cpcnt+1];
List<Integer> cnames = new ArrayList<Integer>();
int cpidx = 1;
while (cpidx <= (cpcnt-1)) {
cpidx += readPoolInfo(cpidx, inp, symbols, cnames);
}
for (int nameidx: cnames) {
ret.addClass(symbols[nameidx]);
}
// u2 access
inp.readUnsignedShort();
// u2 this
inp.readUnsignedShort();
// u2 super
inp.readUnsignedShort();
// u2 ninterfaces
int niface = inp.readUnsignedShort();
// u2 interfaces[ninterfaces]
for (int i=0; i<niface; i++) {
inp.readUnsignedShort();
}
// u2 nfields;
int nfields = inp.readUnsignedShort();
// System.out.println("nfields="+nfields);
for (int i=0; i<nfields; i++) {
readFieldInfo(inp, symbols);
}
// u2 nmethods;
int nmethods = inp.readUnsignedShort();
// System.out.println("nmethods="+nmethods);
for (int i=0; i<nmethods; i++) {
readMethodInfo(inp, symbols, ret);
}
// System.out.println("nmethods="+nmethods);
return ret;
}
private final static void readFieldInfo(DataInput inp, String[] symbols)
throws IOException
{
// u2 access
inp.readUnsignedShort();
// u2 name
int name = inp.readUnsignedShort();
// System.out.println(symbols[name]);
// u2 desc_idx
inp.readUnsignedShort();
// u2 nattrs
int nattrs = inp.readUnsignedShort();
for (int i=0; i<nattrs; i++) {
readAttributeInfo(inp, symbols, null);
}
}
private final static void readMethodInfo
(DataInput inp, String[] symbols, Info info)
throws IOException
{
// u2 access
inp.readUnsignedShort();
// u2 name
int name = inp.readUnsignedShort();
// System.out.println("\t"+symbols[name]);
// u2 desc_idx
inp.readUnsignedShort();
// u2 nattrs
int nattrs = inp.readUnsignedShort();
for (int i=0; i<nattrs; i++) {
readAttributeInfo(inp, symbols, info);
}
}
private final static void readAttributeInfo
(DataInput inp, String[] symbols, Info info)
throws IOException
{
// u2 name
int name = inp.readUnsignedShort();
// u4 alen
long l = ((long)inp.readInt()) & (0xffffffffl);
if ((info != null) && ("Code".equals(symbols[name]))) {
readCodeAttribute(inp, symbols, info);
}
else {
// skip u1 info[alen]
for (long i=0; i<l; i++) {
inp.readByte();
}
}
}
private final static void readCodeAttribute
(DataInput inp, String[] symbols, Info info)
throws IOException
{
// u2 max-stack
inp.readUnsignedShort();
// u2 max-locals
inp.readUnsignedShort();
// u4 code-len
long l = ((long)inp.readInt()) & (0xffffffffl);
info.addCodeSize(l);
for (long i=0; i<l; i++) {
inp.readByte();
}
// u2 nexceptions
int nex = inp.readUnsignedShort();
for (int i=0; i<nex; i++) {
// u2 start
inp.readUnsignedShort();
// u2 handler
inp.readUnsignedShort();
// u2 catch-type
inp.readUnsignedShort();
}
// u2 attrcnt
int nattr = inp.readUnsignedShort();
for (int i=0; i<nattr; i++) {
readAttributeInfo(inp, symbols, info);
}
}
private final static int readPoolInfo
(int idx, DataInput inp, String[] symbols, List<Integer> cnames)
throws IOException
{
int tag = inp.readUnsignedByte();
// System.out.println("tag= "+CONSTANT_NAMES[tag]);
int ret = 1;
switch (tag) {
case CONSTANT_Class:
// u2 name-index
cnames.add(inp.readUnsignedShort());
break;
case CONSTANT_Utf8:
symbols[idx] = inp.readUTF();
break;
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref:
case CONSTANT_NameAndType:
case CONSTANT_Fieldref:
// u2 classindex
inp.readUnsignedShort();
// name_type_index
inp.readUnsignedShort();
break;
case CONSTANT_Integer:
case CONSTANT_Float:
// u4 bytes
inp.readInt();
break;
case CONSTANT_String:
// u2 sidx
inp.readUnsignedShort();
break;
case CONSTANT_Long:
case CONSTANT_Double:
// u8 bytes
inp.readInt();
inp.readInt();
// also, occupies 2 slots in pool.
ret = 2;
break;
default:
throw new IOException("missing tag: "+tag);
}
return ret;
}
public final static byte CONSTANT_Utf8 = 1;
public final static byte CONSTANT_Integer = 3;
public final static byte CONSTANT_Float = 4;
public final static byte CONSTANT_Long = 5;
public final static byte CONSTANT_Double = 6;
public final static byte CONSTANT_Class = 7;
public final static byte CONSTANT_Fieldref = 9;
public final static byte CONSTANT_String = 8;
public final static byte CONSTANT_Methodref = 10;
public final static byte CONSTANT_InterfaceMethodref = 11;
public final static byte CONSTANT_NameAndType = 12;
}
package com.kbsriram.depviz;
import com.kbsriram.depviz.jvm.CExtractUses;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataInput;
import java.util.Enumeration;
import java.util.List;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
public class CMain
{
public final static void main(String args[])
throws IOException
{
for (int i=0; i<args.length; i++) {
dumpJar(args[i]);
}
}
private final static void dumpJar(String path)
throws IOException
{
ZipFile zf = new ZipFile(path);
Enumeration<? extends ZipEntry> zentries = zf.entries();
while (zentries.hasMoreElements()) {
ZipEntry ze = zentries.nextElement();
String zname = ze.getName();
if (!zname.endsWith(".class")) { continue; }
String cname = zname.substring(0, zname.length()-6);
DataInputStream din =
new DataInputStream
(new BufferedInputStream
(zf.getInputStream(ze)));
dumpClass(cname, din);
din.close();
}
}
private final static void dumpClass
(String cname, DataInput inp)
throws IOException
{
System.out.print(cname);
System.out.print(":");
CExtractUses.Info info = CExtractUses.ofBytecode(inp);
System.out.print(info.getCodeSize());
System.out.print(":");
boolean first = true;
for (String use: info.getClasses()) {
if (first) { first = false; }
else { System.out.print(","); }
System.out.print(cleanup(use));
}
System.out.println();
}
private final static String cleanup(String cname)
{
int idx = cname.lastIndexOf('[');
if (idx >= 0) { return cname.substring(idx+1); }
else { return cname; }
}
}
package com.kbsriram.depviz;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CSvg
{
public final static void main(String args[])
throws IOException
{
BufferedReader br = new BufferedReader
(new FileReader(args[0]));
String line;
Map<String,Integer> cls2pos = new HashMap<String,Integer>();
Map<String,Long> cls2len = new HashMap<String,Long>();
List<String> cls = new ArrayList<String>();
Map<String,List<String>> in_arcs =
new HashMap<String,List<String>>();
int pos = 0;
long maxlen = 0l;
double avlen = 0d;
while ((line = br.readLine()) != null) {
String kv[] = line.split(":");
String cname = kv[0];
if (!cname.startsWith("android/")) { continue; }
long clen = Long.parseLong(kv[1]);
String outs[] = kv[2].split(",");
cls.add(cname);
cls2pos.put(cname, pos);
cls2len.put(cname, clen);
if (maxlen < clen) { maxlen = clen; }
avlen += clen;
for (int i=0; i<outs.length; i++) {
String out = outs[i];
if (out.equals(cname)) { continue; }
List<String> ins = in_arcs.get(out);
if (ins == null) {
ins = new ArrayList<String>();
in_arcs.put(out, ins);
}
ins.add(cname);
}
pos++;
}
br.close();
dumpHeader();
int max = cls2pos.size();
avlen /= max;
double logmaxlen = Math.log(1+maxlen);
for (String cname: cls) {
List<String> ins = in_arcs.get(cname);
int srcpos = cls2pos.get(cname);
System.out.println("<g>");
drawClass(cname, cls2len.get(cname), avlen, srcpos, max);
if (ins != null) {
for (String target: ins) {
drawPath(cname, srcpos, target, cls2pos.get(target), max);
}
}
System.out.println("</g>");
}
}
private final static void dumpHeader()
{
System.out.println("<body>");
System.out.println("<style>");
System.out.println
("path { stroke:rgba(204,0,0,0.1); stroke-width:1; fill:none; }");
System.out.println
("g:hover>path { stroke:rgba(0,150,204,1); }");
System.out.println
("text { font-size: 3px; fill: none; stroke: none}");
System.out.println
("g:hover>text { font-size: 18px; fill: rgba(0, 150, 204, 1);}");
System.out.println
("line { stroke:rgba(204, 204, 204, 1); stroke-width: 1;}");
System.out.println
("g:hover>line { stroke:rgba(102, 153, 0, 0.5); stroke-width: 4;}");
System.out.println("</style>");
System.out.println("<svg height='1000' width='1000'>");
}
private final static void dumpFooter()
{
System.out.println("</svg>");
System.out.println("</body>");
}
private final static String getPackage(String cn)
{
String[] fields = cn.split("/");
switch (fields.length) {
case 0: return cn;
case 1: return cn;
case 2: return "android";
default:
return fields[1];
}
}
private final static void drawPath
(String srcname, int src, String targname, Integer targI, int max)
{
if (targI == null) { return; }
int[] srcpos = asPosition(src, max);
int targ = targI.intValue();
int[] targpos = asPosition(targ, max);
int dx = srcpos[0]-targpos[0];
int dy = srcpos[1]-targpos[1];
double dist = Math.sqrt(dx*dx+dy*dy);
// dist is atmost the diameter of the circle.
int[] ctrlpos = asScaledPosition((src+targ)/2d, max, 1-dist/500f);
//drawClassName(srcname, 0l, src, max);
System.out.println
("<path d='M "+srcpos[0]+" "+srcpos[1]+
" Q "+ctrlpos[0]+" "+ctrlpos[1]+
" "+targpos[0]+" "+targpos[1]+"'/>");
//drawClassName(targname, 0l, targ, max, "quiet");
}
private final static void drawClass
(String cname, double clen, double avlen, int src, int max)
{
int[] srcpos = asPosition(src, max);
int angle = (int) ((double)src/(double)max*360d + 0.5d);
//double normalized_len = Math.log(1+clen)/logmaxlen;
double normalized_len = clen/avlen;
int line_len = (int) (0.5d + normalized_len*20d);
System.out.print
("<text x='"+srcpos[0]+"' y='"+srcpos[1]+
"' transform='rotate("+angle+" "+srcpos[0]+","+srcpos[1]+")'>");
System.out.print("&nbsp;"+cname);
System.out.println("</text>");
System.out.println
("<line x1='"+srcpos[0]+"' y1='"+srcpos[1]+
"' x2='"+(srcpos[0]+line_len)+"' y2='"+srcpos[1]+
"' transform='rotate("+angle+" "+srcpos[0]+","+srcpos[1]+")'/>");
}
private final static int[] asPosition(double pos, double max)
{
double v = (pos/max)*Math.PI*2d; // radians, angle.
int[] ret = new int[2];
ret[0] = (int) (500d+250d*Math.cos(v)+0.5d);
ret[1] = (int) (500d+250d*Math.sin(v)+0.5d);
return ret;
}
private final static int[] asScaledPosition
(double pos, double max, double frac)
{
double v = (pos/max)*Math.PI*2d; // radians, angle.
int[] ret = new int[2];
ret[0] = (int) (500d+250d*Math.cos(v)*frac+0.5d);
ret[1] = (int) (500d+250d*Math.sin(v)*frac+0.5d);
return ret;
}
}
java -cp depviz.jar com.kbsriram.depviz.CMain ~/android-sdk-macosx/platforms/android-19/android.jar | grep '^android' > /tmp/deps.txt
sort /tmp/deps.txt > /tmp/sorted_deps.txt
java -cp depviz.jar com.kbsriram.depviz.CSvg /tmp/sorted_deps.txt > /tmp/android.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment