blob: d1ae673240f5166d083e0f106cee8726a7886079 [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.SdkConstants.CLASS_VIEW
import com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX
import com.android.support.AndroidxName
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.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.intellij.psi.PsiMethod
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UReferenceExpression
import org.jetbrains.uast.USuperExpression
import org.jetbrains.uast.visitor.AbstractUastVisitor
/**
* Makes sure that methods call super when overriding methods.
*/
class CallSuperDetector : Detector(), SourceCodeScanner {
companion object Issues {
private val IMPLEMENTATION = Implementation(
CallSuperDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
/** Missing call to super */
@JvmField
val ISSUE = Issue.create(
id = "MissingSuperCall",
briefDescription = "Missing Super Call",
explanation = """
Some methods, such as `View#onDetachedFromWindow`, require that you also call the \
super implementation as part of your method.
""",
category = Category.CORRECTNESS,
priority = 9,
severity = Severity.ERROR,
implementation = IMPLEMENTATION
)
private val CALL_SUPER_ANNOTATION = AndroidxName.of(SUPPORT_ANNOTATIONS_PREFIX, "CallSuper")
private const val ON_DETACHED_FROM_WINDOW = "onDetachedFromWindow"
private const val ON_VISIBILITY_CHANGED = "onVisibilityChanged"
}
override fun getApplicableUastTypes(): List<Class<out UElement>>? =
listOf(UMethod::class.java)
override fun createUastHandler(context: JavaContext): UElementHandler? =
object : UElementHandler() {
override fun visitMethod(node: UMethod) {
val superMethod = getRequiredSuperMethod(context, node) ?: return
val visitor = SuperCallVisitor(superMethod)
node.accept(visitor)
val count = visitor.callsSuperCount
if (count == 0) {
val message = "Overriding method should call `super.${node.name}`"
val location = context.getNameLocation(node)
val fix = fix().map().put(PsiMethod::class.java, superMethod).build()
context.report(ISSUE, node, location, message, fix)
} else if (count > 1 && node.name == "onCreate") {
val overlap = visitor.findFirstOverlap(node) ?: return
val message = "Calling `super.${node.name}` more than once can lead to crashes"
val location = context.getNameLocation(overlap)
context.report(ISSUE, node, location, message)
}
}
}
/**
* Checks whether the given method overrides a method which requires the super method
* to be invoked, and if so, returns it (otherwise returns null)
*/
private fun getRequiredSuperMethod(
context: JavaContext,
method: UMethod
): PsiMethod? {
val evaluator = context.evaluator
val directSuper = evaluator.getSuperMethod(method) ?: return null
val name = method.name
if (ON_DETACHED_FROM_WINDOW == name) {
// No longer annotated on the framework method since it's
// now handled via onDetachedFromWindowInternal, but overriding
// is still dangerous if supporting older versions so flag
// this for now (should make annotation carry metadata like
// compileSdkVersion >= N).
if (!evaluator.isMemberInSubClassOf(method, CLASS_VIEW, false)) {
return null
}
return directSuper
} else if (ON_VISIBILITY_CHANGED == name) {
// From Android Wear API; doesn't yet have an annotation
// but we want to enforce this right away until the AAR
// is updated to supply it once @CallSuper is available in
// the support library
if (!evaluator.isMemberInSubClassOf(
method,
"android.support.wearable.watchface.WatchFaceService.Engine", false
)
) {
return null
}
return directSuper
}
val annotations = evaluator.getAllAnnotations(directSuper, true)
for (annotation in annotations) {
val signature = annotation.qualifiedName
if (CALL_SUPER_ANNOTATION.isEquals(signature) || signature != null &&
(signature.endsWith(".OverrideMustInvoke") ||
signature.endsWith(".OverridingMethodsMustInvokeSuper"))
) {
return directSuper
}
}
return null
}
/** Visits a method and determines whether the method calls its super method */
private class SuperCallVisitor constructor(private val targetMethod: PsiMethod) :
AbstractUastVisitor() {
val superCalls = mutableListOf<USuperExpression>()
val callsSuperCount: Int get() = superCalls.size
override fun visitSuperExpression(node: USuperExpression): Boolean {
val parent = com.android.tools.lint.detector.api.skipParentheses(node.uastParent)
if (parent is UReferenceExpression) {
val resolved = parent.resolve()
if (resolved == null || // Avoid false positives for type resolution problems
targetMethod.isEquivalentTo(resolved)
) {
superCalls.add(node)
}
}
return super.visitSuperExpression(node)
}
fun findFirstOverlap(method: UMethod): USuperExpression? {
for (i in 0 until superCalls.size) {
for (j in i + 1 until superCalls.size) {
if (CutPasteDetector.isReachableFrom(method, superCalls[i], superCalls[j])) {
return superCalls[j]
}
}
}
return null
}
}
}