blob: 58c534d2306cab77ef041ddef0ef0b138778a068 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 com.android.tools.lint.checks
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.TypeEvaluator
import com.android.tools.lint.detector.api.isAndroidProject
import com.android.tools.lint.detector.api.targetSdkAtLeast
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UDeclaration
import org.jetbrains.uast.UElement
/**
* Looks for addJavascriptInterface calls on interfaces have been
* properly annotated with `@JavaScriptInterface`
*/
class JavaScriptInterfaceDetector : Detector(), SourceCodeScanner {
// ---- implements SourceCodeScanner ----
override fun getApplicableMethodNames(): List<String> {
return listOf(ADD_JAVASCRIPT_INTERFACE)
}
override fun visitMethodCall(
context: JavaContext,
node: UCallExpression,
method: PsiMethod
) {
val arguments = node.valueArguments
if (arguments.size != 2) {
return
}
val evaluator = context.evaluator
if (!evaluator.isMemberInClass(method, WEB_VIEW_CLS)) {
return
}
val first = arguments[0]
val evaluated = TypeEvaluator.evaluate(first)
if (evaluated is PsiClassType) {
val cls = evaluated.resolve() ?: return
if (isJavaScriptAnnotated(cls)) {
return
}
val location = context.getNameLocation(node)
val message = "None of the methods in the added interface (${cls.name}) have " +
"been annotated with `@android.webkit.JavascriptInterface`; they will not " +
"be visible in API 17"
val incident = Incident(ISSUE, node, location, message)
context.report(incident, targetSdkAtLeast(17))
}
}
override fun getApplicableUastTypes(): List<Class<out UElement>>? =
listOf(UDeclaration::class.java)
override fun createUastHandler(context: JavaContext): UElementHandler? {
return object : UElementHandler() {
override fun visitDeclaration(node: UDeclaration) {
val modifierList = node.modifierList ?: return
if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
return
}
for (annotation in node.uAnnotations) {
if (annotation.qualifiedName == JAVASCRIPT_INTERFACE_CLS) {
val incident = Incident(
ISSUE, node as UElement, context.getNameLocation(node),
"Must be public when using `@JavascriptInterface`"
)
context.report(incident, isAndroidProject())
}
}
}
}
}
companion object {
/** The main issue discovered by this detector. */
@JvmField
val ISSUE = Issue.create(
id = "JavascriptInterface",
briefDescription = "Missing @JavascriptInterface on methods",
explanation = """
As of API 17, you must annotate methods in objects registered with the \
`addJavascriptInterface` method with a `@JavascriptInterface` annotation.
""",
category = Category.SECURITY,
moreInfo = "https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)",
androidSpecific = true,
priority = 8,
severity = Severity.ERROR,
implementation = Implementation(
JavaScriptInterfaceDetector::class.java, Scope.JAVA_FILE_SCOPE
)
)
private const val ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface"
private const val JAVASCRIPT_INTERFACE_CLS = "android.webkit.JavascriptInterface"
private const val WEB_VIEW_CLS = "android.webkit.WebView"
private fun isJavaScriptAnnotated(clz: PsiClass?): Boolean {
var current = clz
while (current != null) {
val modifierList = current.modifierList
if (modifierList?.findAnnotation(JAVASCRIPT_INTERFACE_CLS) != null) {
return true
}
for (method in current.methods) {
if (method.modifierList.findAnnotation(JAVASCRIPT_INTERFACE_CLS) != null) {
return true
}
}
current = current.superClass
}
return false
}
}
}