Skip to content

Commit b4cc599

Browse files
authored
602:Improve method identification logic (#627)
* Add a unit test. * Temporal commit. * Implemented temporal logic. * Clean up and add unit test. * Tweak a bit. * Remove parameter logic. * Add test case for Java.
1 parent 2509d04 commit b4cc599

File tree

3 files changed

+128
-11
lines changed

3 files changed

+128
-11
lines changed

lint/src/main/java/permissions/dispatcher/CallNeedsPermissionDetector.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.android.tools.lint.detector.api.Severity;
1111

1212
import org.jetbrains.annotations.NotNull;
13+
import org.jetbrains.annotations.Nullable;
1314
import org.jetbrains.uast.UAnnotation;
1415
import org.jetbrains.uast.UCallExpression;
1516
import org.jetbrains.uast.UClass;
@@ -26,6 +27,8 @@
2627

2728
public final class CallNeedsPermissionDetector extends Detector implements Detector.UastScanner {
2829

30+
private static final String COLON = ":";
31+
2932
static final Issue ISSUE = Issue.create("CallNeedsPermission",
3033
"Call the corresponding \"WithPermissionCheck\" method of the generated PermissionsDispatcher class instead",
3134
"Directly invoking a method annotated with @NeedsPermission may lead to misleading behaviour on devices running Marshmallow and up. Therefore, it is advised to use the generated PermissionsDispatcher class instead, which provides a \"WithPermissionCheck\" method that safely handles runtime permissions.",
@@ -75,12 +78,34 @@ public boolean visitCallExpression(@NotNull UCallExpression node) {
7578
if (isGeneratedFiles(context) || !hasRuntimePermissionAnnotation) {
7679
return true;
7780
}
78-
if (node.getReceiver() == null && annotatedMethods.contains(node.getMethodName())) {
81+
if (node.getReceiver() == null && annotatedMethods.contains(methodIdentifier(node))) {
7982
context.report(ISSUE, node, context.getLocation(node), "Trying to access permission-protected method directly");
8083
}
8184
return true;
8285
}
8386

87+
/**
88+
* Generate method identifier from method information.
89+
*
90+
* @param node UCallExpression
91+
* @return className + methodName + parametersType
92+
*/
93+
@Nullable
94+
private static String methodIdentifier(@NotNull UCallExpression node) {
95+
UElement element = node.getUastParent();
96+
while (element != null) {
97+
if (element instanceof UClass) {
98+
break;
99+
}
100+
element = element.getUastParent();
101+
}
102+
UClass uClass = (UClass) element;
103+
if (uClass == null || node.getMethodName() == null) {
104+
return null;
105+
}
106+
return uClass.getName() + COLON + node.getMethodName();
107+
}
108+
84109
@Override
85110
public boolean visitMethod(@NotNull UMethod node) {
86111
if (isGeneratedFiles(context)) {
@@ -90,10 +115,30 @@ public boolean visitMethod(@NotNull UMethod node) {
90115
if (annotation == null) {
91116
return super.visitMethod(node);
92117
}
93-
annotatedMethods.add(node.getName());
118+
String methodIdentifier = methodIdentifier(node);
119+
if (methodIdentifier == null) {
120+
return super.visitMethod(node);
121+
}
122+
annotatedMethods.add(methodIdentifier);
94123
return super.visitMethod(node);
95124
}
96125

126+
/**
127+
* Generate method identifier from method information.
128+
*
129+
* @param node UMethod
130+
* @return className + methodName + parametersType
131+
*/
132+
@Nullable
133+
private static String methodIdentifier(@NotNull UMethod node) {
134+
UElement parent = node.getUastParent();
135+
if (!(parent instanceof UClass)) {
136+
return null;
137+
}
138+
UClass uClass = (UClass) parent;
139+
return uClass.getName() + COLON + node.getName();
140+
}
141+
97142
private static boolean isGeneratedFiles(JavaContext context) {
98143
UFile sourceFile = context.getUastFile();
99144
if (sourceFile == null) {

lint/src/test/java/permissions/dispatcher/CallNeedsPermissionDetectorKtTest.kt

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
package permissions.dispatcher
22

3-
import org.intellij.lang.annotations.Language
4-
import org.junit.Test
5-
63
import com.android.tools.lint.checks.infrastructure.TestFiles.java
74
import com.android.tools.lint.checks.infrastructure.TestFiles.kt
85
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
6+
import org.intellij.lang.annotations.Language
7+
import org.junit.Test
98
import permissions.dispatcher.Utils.onNeedsPermission
109
import permissions.dispatcher.Utils.runtimePermission
1110

1211
class CallNeedsPermissionDetectorKtTest {
1312

1413
@Test
15-
@Throws(Exception::class)
1614
fun callNeedsPermissionMethod() {
1715
@Language("kotlin") val foo = """
1816
package com.example
@@ -52,7 +50,6 @@ class CallNeedsPermissionDetectorKtTest {
5250
}
5351

5452
@Test
55-
@Throws(Exception::class)
5653
fun callNeedsPermissionMethodNoError() {
5754
@Language("kotlin") val foo = """
5855
package com.example
@@ -94,7 +91,6 @@ class CallNeedsPermissionDetectorKtTest {
9491
}
9592

9693
@Test
97-
@Throws(Exception::class)
9894
fun issues502() {
9995
@Language("kotlin") val foo = """
10096
package com.example
@@ -128,4 +124,42 @@ class CallNeedsPermissionDetectorKtTest {
128124
.run()
129125
.expectClean()
130126
}
127+
128+
@Test
129+
fun `same name methods in different class(issue602)`() {
130+
@Language("kotlin") val foo = """
131+
package com.example
132+
133+
import permissions.dispatcher.NeedsPermission
134+
import permissions.dispatcher.RuntimePermissions
135+
136+
@RuntimePermissions
137+
class FirstActivity : AppCompatActivity() {
138+
@NeedsPermission(Manifest.permission.CAMERA)
139+
fun someFun() {
140+
}
141+
}
142+
143+
@RuntimePermissions
144+
class SecondActivity : AppCompatActivity() {
145+
override fun onCreate(savedInstanceState: Bundle?) {
146+
super.onCreate(savedInstanceState)
147+
someFun()
148+
}
149+
150+
fun someFun() {
151+
}
152+
153+
@NeedsPermission(Manifest.permission.CAMERA)
154+
fun otherFun() {
155+
}
156+
}
157+
""".trimMargin()
158+
159+
lint()
160+
.files(java(runtimePermission), java(onNeedsPermission), kt(foo))
161+
.issues(CallNeedsPermissionDetector.ISSUE)
162+
.run()
163+
.expectClean()
164+
}
131165
}

lint/src/test/java/permissions/dispatcher/CallNeedsPermissionDetectorTest.kt

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package permissions.dispatcher
22

3-
import org.intellij.lang.annotations.Language
4-
import org.junit.Test
5-
63
import com.android.tools.lint.checks.infrastructure.TestFiles.java
74
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
5+
import org.intellij.lang.annotations.Language
6+
import org.junit.Test
87
import permissions.dispatcher.Utils.onNeedsPermission
98
import permissions.dispatcher.Utils.runtimePermission
109

@@ -126,4 +125,43 @@ class CallNeedsPermissionDetectorTest {
126125
.run()
127126
.expectClean()
128127
}
128+
129+
@Test
130+
fun `same name methods in different class(issue602)`() {
131+
@Language("java") val foo = """
132+
package com.example;
133+
134+
import permissions.dispatcher.NeedsPermission;
135+
import permissions.dispatcher.RuntimePermissions;
136+
137+
@RuntimePermissions
138+
public class FirstActivity extends AppCompatActivity {
139+
@NeedsPermission({Manifest.permission.READ_SMS})
140+
void someFun() {
141+
}
142+
}
143+
144+
@RuntimePermissions
145+
public class SecondActivity extends AppCompatActivity {
146+
@Override
147+
protected void onCreate(@Nullable Bundle savedInstanceState) {
148+
super.onCreate(savedInstanceState);
149+
someFun();
150+
}
151+
152+
void someFun() {
153+
}
154+
155+
@NeedsPermission({Manifest.permission.READ_SMS})
156+
void otherFun() {
157+
}
158+
}
159+
""".trimMargin()
160+
161+
lint()
162+
.files(java(runtimePermission), java(onNeedsPermission), java(foo))
163+
.issues(CallNeedsPermissionDetector.ISSUE)
164+
.run()
165+
.expectClean()
166+
}
129167
}

0 commit comments

Comments
 (0)