Closed ZephyrusZhang closed 1 year ago
Your approach for obtaining a possible call graph (there might be multiple paths to the same method) is correct. The missing edges are not related to permissions. Instead, these methods are virtual calls on Android system services. You obtain an instance of the TelecomManager
class through a factory method getSystemService
. This methid is part of the Android SDK, which is not analyzed together with your app. Therefore, our callgraph algorithm never sees an allocation site, i.e., a constructor call, for this variable. Consequently, no type information can be propagated (which class exactly is returned - might be a subclass - , and therefore where the call exactly goes) and thus all edges from instance invocations on that variable are missing.
Analyzing the entire Android SDK together with every app would be a solution, but doesn't scale. Since you're not interested in the concrete class (you have the TelecomManager
as public API, you don't care whether it's that class, some internal subclass, etc. that you get back from getSystemService
). For such API methods (everything that's android.*
), I would simply iterate over all units in all methods in all classes of the app, if the current unit is an invocation statement, check the target via InvokeExpr.getMethod()
. You then trace back from that method. Conceptually, we're simply looking at the caller side rather than the callee side.
We could also inject such fake edges into the callgraph at some point. FlowDroid doesn't need them, though, because we have other abstractions for the Android API, e.g., the StubDroid summaries.
Thanks, Steven, but I'm still confused about why Flowdroid doesn't construct call edge to the method I metioned above(isInCall()
). Actually, for all methods mentioned in this file, https://github.com/sqlab-sustech/APER-mapping/blob/master/API31/annotations-Mappings.txt, CallGraph.edgesInto()
doesn't get approprite results. You said This methid is part of the Android SDK, which is not analyzed together with your app, but other android sdk methods, such as ContextCompact.checkSelfPermission()
and Navigation.findNavController()
, their call edges are constructed successfully.
Besides, I alse check jmple code of example above, find out that call site of isInCall()
does exist, and type of variable telecom
is also TelecomManager
.
App code fragment
private AppBarConfiguration appBarConfiguration;
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Clazz.fun();
TelecomManager telecom = (TelecomManager) getBaseContext().getSystemService(Context.TELECOM_SERVICE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
System.out.println();
}
System.out.println(telecom.isInCall());
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
binding.fab.setOnClickListener(view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show());
}
Jimple code of above code
protected void onCreate(android.os.Bundle)
{
android.content.Context $r2;
android.os.Bundle $r1;
androidx.navigation.ui.AppBarConfiguration$Builder $r11;
androidx.navigation.ui.AppBarConfiguration $r13;
androidx.navigation.NavController $r10;
com.google.android.material.floatingactionbutton.FloatingActionButton r14;
int $i0;
androidx.navigation.NavGraph $r12;
boolean $z0;
java.io.PrintStream $r5;
androidx.coordinatorlayout.widget.CoordinatorLayout $r8;
android.view.LayoutInflater $r6;
edu.example.exampleapp.MainActivity r0;
android.telecom.TelecomManager $r4;
edu.example.exampleapp.databinding.ActivityMainBinding $r7;
androidx.appcompat.widget.Toolbar $r9;
java.lang.Object $r3;
edu.example.exampleapp.MainActivity$$ExternalSyntheticLambda0 $r15;
r0 := @this: edu.example.exampleapp.MainActivity;
$r1 := @parameter0: android.os.Bundle;
specialinvoke r0.<androidx.appcompat.app.AppCompatActivity: void onCreate(android.os.Bundle)>($r1);
staticinvoke <edu.example.exampleapp.Clazz: void fun()>();
$r2 = virtualinvoke r0.<edu.example.exampleapp.MainActivity: android.content.Context getBaseContext()>();
$r3 = virtualinvoke $r2.<android.content.Context: java.lang.Object getSystemService(java.lang.String)>("telecom");
$r4 = (android.telecom.TelecomManager) $r3;
$i0 = staticinvoke <androidx.core.app.ActivityCompat: int checkSelfPermission(android.content.Context,java.lang.String)>(r0, "android.permission.READ_PHONE_STATE");
if $i0 == 0 goto label1;
$r5 = <java.lang.System: java.io.PrintStream out>;
virtualinvoke $r5.<java.io.PrintStream: void println()>();
label1:
$r5 = <java.lang.System: java.io.PrintStream out>;
$z0 = virtualinvoke $r4.<android.telecom.TelecomManager: boolean isInCall()>();
virtualinvoke $r5.<java.io.PrintStream: void println(boolean)>($z0);
$r6 = virtualinvoke r0.<edu.example.exampleapp.MainActivity: android.view.LayoutInflater getLayoutInflater()>();
$r7 = staticinvoke <edu.example.exampleapp.databinding.ActivityMainBinding: edu.example.exampleapp.databinding.ActivityMainBinding inflate(android.view.LayoutInflater)>($r6);
r0.<edu.example.exampleapp.MainActivity: edu.example.exampleapp.databinding.ActivityMainBinding binding> = $r7;
$r8 = virtualinvoke $r7.<edu.example.exampleapp.databinding.ActivityMainBinding: androidx.coordinatorlayout.widget.CoordinatorLayout getRoot()>();
virtualinvoke r0.<edu.example.exampleapp.MainActivity: void setContentView(android.view.View)>($r8);
$r7 = r0.<edu.example.exampleapp.MainActivity: edu.example.exampleapp.databinding.ActivityMainBinding binding>;
$r9 = $r7.<edu.example.exampleapp.databinding.ActivityMainBinding: androidx.appcompat.widget.Toolbar toolbar>;
virtualinvoke r0.<edu.example.exampleapp.MainActivity: void setSupportActionBar(androidx.appcompat.widget.Toolbar)>($r9);
$r10 = staticinvoke <androidx.navigation.Navigation: androidx.navigation.NavController findNavController(android.app.Activity,int)>(r0, 2131231035);
$r11 = new androidx.navigation.ui.AppBarConfiguration$Builder;
$r12 = virtualinvoke $r10.<androidx.navigation.NavController: androidx.navigation.NavGraph getGraph()>();
specialinvoke $r11.<androidx.navigation.ui.AppBarConfiguration$Builder: void <init>(androidx.navigation.NavGraph)>($r12);
$r13 = virtualinvoke $r11.<androidx.navigation.ui.AppBarConfiguration$Builder: androidx.navigation.ui.AppBarConfiguration build()>();
r0.<edu.example.exampleapp.MainActivity: androidx.navigation.ui.AppBarConfiguration appBarConfiguration> = $r13;
staticinvoke <androidx.navigation.ui.NavigationUI: void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity,androidx.navigation.NavController,androidx.navigation.ui.AppBarConfiguration)>(r0, $r10, $r13);
$r7 = r0.<edu.example.exampleapp.MainActivity: edu.example.exampleapp.databinding.ActivityMainBinding binding>;
r14 = $r7.<edu.example.exampleapp.databinding.ActivityMainBinding: com.google.android.material.floatingactionbutton.FloatingActionButton fab>;
$r15 = <edu.example.exampleapp.MainActivity$$ExternalSyntheticLambda0: edu.example.exampleapp.MainActivity$$ExternalSyntheticLambda0 INSTANCE>;
virtualinvoke r14.<com.google.android.material.floatingactionbutton.FloatingActionButton: void setOnClickListener(android.view.View$OnClickListener)>($r15);
return;
}
I will be very grateful if you could solve my doubts, and offer me some hlep on how to make FlowDroid construct correct call edge for the methods I want.
The problem is the allocation site. The delcared type is practically irrelevant. Assume the following code:
A a = ?
a.foo();
We don't know the precise type of variable a
. It might be A
, but it might also be som type derived from A
, i.e., a (transitive) subclass. That's why the SPARK callgraph algorithm propagates types from the allocation site. Consider the modified example:
A a = new B();
a.foo();
We now know that the edge is from the call site a.foo()
to method B.foo()
. Note that an edge to A.foo()
would be wrong, because the (overridden) method in B is called.
This leads to a problem when we don't see the allocation site. In the example with the TelecomManager
, we never see the constructor call. The object is "magically" returned from a black-box method for which we don't have the code (because it's in the SDK). Therefore, no type information is propagated. Obviously, we could fall back to the declared type TelecomManager
, but that is not the case at the moment. Therefore, all calls on the TelecomManager
instance are missing from the CG.
I suggested some techniques for resolving the problem above.
Thanks, Steven, I've already understood. After I set call graph construciton algorithm to CHA, it now works well.
Hi, Steven! I'm learning to use Flowdroid recently. And now i'm trying to get the call chain of a specified method. I start from the specified function and analyze it backwards, using
CallGraph.edgeInto
to get the call edges into the function, and get the corresponding caller, and repeat the process untildummyMain
. However, there is some problem when i useedgesInto
. If the specified method requires permission when running,edgesInto
will fail to get any call edges into the method, although it works well when it is applied on methods defined by me or other android api that does not require any permission when running. Since I am still unfamiliar with FlowDroid, I don't if this is a bug or I just set the wrong option or use a inappropriate way to get call chain of a specified method. I would be much grateful if you can offer me some help.Here is the codes
App code fragment