Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different call graphs for APK on subsequent runs #88

Closed
mherzberg opened this issue Aug 15, 2015 · 7 comments
Closed

Different call graphs for APK on subsequent runs #88

mherzberg opened this issue Aug 15, 2015 · 7 comments

Comments

@mherzberg
Copy link

I am struggling to build Java call graphs from APK files in a reproducable manner. I expect the call graph to be the same every time I build it, but the number of nodes differ up to a couple hundred between subsequent runs, although the code and circumstances do not seem to differ. Does anyone have an idea of what I am doing wrong?

I added some sample code below. It runs the ZeroCFABuilder 100 times on the "de.guh.finanzchecker" Android App I downloaded from the Google Play Store, although I encountered this problem with many other apps, too. Additionally I took the android.jar from API level 19. I then print the number of nodes and write nodes from the call graph into a file for debugging purposes. When I run this on my Arch Linux machine using "openjdk version 1.8.0_51" and the latest master branch of WALA, I got at least 10 different call graphs, ranging from 6984 to 7027 nodes. I appended the delta nodes from those two examples below the code.

public static void main(String[] args)
        throws IOException, ClassHierarchyException, IllegalArgumentException, CallGraphBuilderCancelException {
    for (int i = 0; i < 100; i++) {
        AnalysisScope scope = DalvikTestBase.makeDalvikScope(null, new File("android19.jar"),
                new File("de.guh.finanzchecker.apk").getCanonicalPath());
        ClassHierarchy cha = ClassHierarchy.make(scope);
        AnalysisCache cache = new AnalysisCache(new DexIRFactory());
        Set set = new HashSet();
        set.add(LocatorFlags.INCLUDE_CALLBACKS);
        set.add(LocatorFlags.EP_HEURISTIC);
        set.add(LocatorFlags.CB_HEURISTIC);
        AndroidEntryPointLocator eps = new AndroidEntryPointLocator(set);
        AnalysisOptions options = new AnalysisOptions(scope, eps.getEntryPoints(cha));
        options.setReflectionOptions(ReflectionOptions.FULL);
        CallGraphBuilder cgb = Util.makeZeroCFABuilder(options, cache, cha, scope);
        CallGraph cg = cgb.makeCallGraph(options, null);


        File out = new File(cg.getNumberOfNodes() + "nodes.txt");
        if (out.exists())
            out.delete();
        System.out.println(cg.getNumberOfNodes());
        FileWriter fw = new FileWriter(out);
        List<String> nodes = new ArrayList<String>();
        for (Iterator<CGNode> it = cg.iterator(); it.hasNext();) {
            nodes.add(it.next().toString());
        }
        nodes.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
        for (String str : nodes) {
            fw.write(str + "\n");
        }
        fw.close();
    }
}
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry, <init>(Ljava/lang/String;Ljava/lang/String;)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry, access$0(Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry;)Ljava/lang/String; > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry, access$1(Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry;)Ljava/lang/String; > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry, length()I > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, <init>(Ljava/io/DataInputStream;Z)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, emitReferenceSet()V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, getAndReset()Ljava/util/List; > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, getName(I)Ljava/lang/String; > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, getValue(I)Ljava/lang/String; > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, insertIntoHeaderTable(ILcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry;)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readByte()I > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readHeaders(I)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readIndexedHeader(I)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readInt(II)I > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readLiteralHeaderWithIncrementalIndexingIndexedName(I)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readLiteralHeaderWithIncrementalIndexingNewName()V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readLiteralHeaderWithSubstitutionIndexingIndexedName(I)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readLiteralHeaderWithSubstitutionIndexingNewName()V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readLiteralHeaderWithoutIndexingIndexedName(I)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readLiteralHeaderWithoutIndexingNewName()V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, readString()Ljava/lang/String; > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Reader, remove(I)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Writer, <init>(Ljava/io/OutputStream;)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Writer, writeHeaders(Ljava/util/List;)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Writer, writeInt(III)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack$Writer, writeString(Ljava/lang/String;)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Hpack, <clinit>()V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Http20Draft06$Reader, <init>(Ljava/io/InputStream;Z)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Http20Draft06$Writer, <init>(Ljava/io/OutputStream;Z)V > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Http20Draft06, newReader(Ljava/io/InputStream;Z)Lcom/squareup/okhttp/internal/spdy/FrameReader; > Context: Everywhere
> Node: < Application, Lcom/squareup/okhttp/internal/spdy/Http20Draft06, newWriter(Ljava/io/OutputStream;Z)Lcom/squareup/okhttp/internal/spdy/FrameWriter; > Context: Everywhere
> Node: < Primordial, Ljava/io/DataInputStream, readByte()B > Context: Everywhere
> Node: < Primordial, Ljava/io/DataInputStream, readFully([B)V > Context: Everywhere
> Node: < Primordial, Ljava/io/DataInputStream, readFully([BII)V > Context: Everywhere
> Node: < Primordial, Ljava/lang/Long, numberOfTrailingZeros(J)I > Context: Everywhere
> Node: < Primordial, Ljava/util/BitSet, <init>()V > Context: Everywhere
> Node: < Primordial, Ljava/util/BitSet, clear(I)V > Context: Everywhere
> Node: < Primordial, Ljava/util/BitSet, get(I)Z > Context: Everywhere
> Node: < Primordial, Ljava/util/BitSet, nextSetBit(I)I > Context: Everywhere
> Node: < Primordial, Ljava/util/BitSet, recalculateWordsInUse()V > Context: Everywhere
> Node: synthetic < Primordial, Ljava/lang/Object, clone()Ljava/lang/Object; > Context: JavaTypeContext<point: <Application,[Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry>>
> Node: synthetic < Primordial, Ljava/lang/Object, getClass()Ljava/lang/Class; > Context: JavaTypeContext<point: <Application,Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry>>
> Node: synthetic < Primordial, Ljava/lang/Object, getClass()Ljava/lang/Class; > Context: JavaTypeContext<point: <Application,[Lcom/squareup/okhttp/internal/spdy/Hpack$HeaderEntry>>
@msridhar
Copy link
Member

Sorry for the slow response. This is bad. Can you give a URL of an APK file on which you're observing this behavior?

@kripton
Copy link

kripton commented Oct 3, 2015

Here's one version of the app, however I don't know if that's the one the OP has problems with: http://www.appjenny.com/Android/App/88082/de.guh.finanzchecker/Download

@mherzberg
Copy link
Author

Sorry for the late reply. I tried narrowing down the issue to just a few classes, but was unsuccessful. The issue even occurs when analysing a freshly created Apache Cordova application. I assume it has to do with some part of the Apache Cordova framework. I compiled one for you and uploaded it here.

In order to make it work with my code snippet, you have to exchange the correct path to this APK as well as drop the ${ANDROID_HOME}/platforms/android-19/android.jar into the folder as android19.jar (or whatever you specify in the snippet).

If you have any trouble reproducing the issue, please let me know.

@msridhar
Copy link
Member

Ok, so I spent a bit of time on this, and one issue is there seems to be considerable non-determinism in the AndroidEntryPointLocator. If you change the driver code to the following, you'll see the non-determinism:

  public static void main(String[] args)
      throws IOException, ClassHierarchyException, IllegalArgumentException, CallGraphBuilderCancelException {
  for (int i = 0; i < 100; i++) {
      AnalysisScope scope = com.ibm.wala.dalvik.test.util.Util.makeDalvikScope(null, new File("/Users/m.sridharan/Library/Android/sdk/platforms/android-19/android.jar"),
              new File("/Users/m.sridharan/Downloads/HelloCordova-debug.apk").getCanonicalPath());
      ClassHierarchy cha = ClassHierarchy.make(scope);
      System.out.println(cha.getNumberOfClasses() + " classes");
      AnalysisCache cache = new AnalysisCache(new DexIRFactory());
      Set<LocatorFlags> set = new HashSet<LocatorFlags>();
      set.add(LocatorFlags.INCLUDE_CALLBACKS);
      set.add(LocatorFlags.EP_HEURISTIC);
      set.add(LocatorFlags.CB_HEURISTIC);
      AndroidEntryPointLocator eps = new AndroidEntryPointLocator(set);
      List<AndroidEntryPoint> entryPoints = eps.getEntryPoints(cha);
      System.out.println(entryPoints.size() + " entrypoints");
      File out = new File(entryPoints.size() + "entries.txt");
      if (out.exists())
          out.delete();
      FileWriter fw = new FileWriter(out);
      List<String> nodes = new ArrayList<String>();
      for (Iterator<AndroidEntryPoint> it = entryPoints.iterator(); it.hasNext();) {
          nodes.add(it.next().toString());
      }
      Collections.sort(nodes, new Comparator<String>() {
          @Override
          public int compare(String o1, String o2) {
              return o1.compareTo(o2);
          }
      });
      for (String str : nodes) {
          fw.write(str + "\n");
      }
      fw.close();
  }

The entrypoint counts I see vary between 638 and 675.

I'm not too familiar with this Android entrypoint code. @mohrm do you know this code better? Could you take a look?

msridhar added a commit that referenced this issue Oct 11, 2015
Added while investigating #88, but this does not fix the problem
mohrm added a commit to joana-team/WALA that referenced this issue Jan 26, 2017
@mohrm
Copy link
Contributor

mohrm commented Jan 26, 2017

I also executed the example code above and still get variations, when just using the proposed fixes. However, on the current https://github.com/joana-team/WALA master branch, there are no variations anymore. So, pull request #134 only solves part of #88.

@mohrm
Copy link
Contributor

mohrm commented Jan 26, 2017

Ah, forgot to add hashCode() and equals() for DexModuleEntry. Now it works, always outputs 675.

@msridhar
Copy link
Member

Fixed by #134

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants