為さねば成らぬ

retia.verno@gmail.com

Android LintのDetectorを読む Day5: CallSuperDetector

ソースコード

https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.kt

背景

メソッドがoverrideされるときに、親クラスのメソッドを呼ぶように示すアノテーション @CallSuper が存在する。 ただしく実装されているかをチェックするLint。

適用例

以下の条件をすべて満たしているときにエラーが出る。

  • @CallSuperがついているメソッドをoverrideする
  • View.onDetachedFromWindow
    • Android 5からこのメソッドでの実際の処理は onDetachedFromWindowInternal で行われるようになったため、onDetachedFromWindow をsuperを呼ばずにoverrideしても問題なくなった。実際 View.onDetachedFromWindow の実装は空になっている。
    • が、それ以前では View.onDetachedFromWindow に実装が入っておりsuperを呼ぶ必要があり、また @CallSuper は存在していない時代だったため、特殊ケースとして条件が存在する。
  • android.support.wearable.watchface.WatchFaceService.Engine.onVisibilityChanged
    • サポートライブラリ側で @CallSuper 対応するまでは特殊ケースとしてチェックしている。

また、onCreate内でsuperを複数回呼ぶとクラッシュの危険性があるので、その場合もチェックする。 この際、複数回呼ばれない構造であればエラーが検出されない。

例えば以下のようなケースでは検出されない

override fun onCreate(savedInstanceState: Bundle){
  if(hoge){
    super.onCreate(savedInstanceState)
  } else {
    super.onCreate(savedInstanceState)
  }
}

気づき

SuperCallVisitorで superの呼び出しをチェック

特定のメソッドの中で呼び出したsuperのメソッドのついての情報を収集している。 skipParenthesizedExprUp は簡単に言うとsuperのメソッドを取得しており、targetとなるメソッドと名前が一致していれば配列に入れておくようになっている。この配列が0ならsuperが呼ばれていないのでエラーが出る。

        override fun visitSuperExpression(node: USuperExpression): Boolean {
            anySuperCallCount++
            val parent = skipParenthesizedExprUp(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)
        }

その他感想

CallSuperDetector という名前的には合っているが、 CallSuper のメソッドでちゃんとsuperが呼んでいるか、とonCreateが複数回呼んでいないかは別のクラスに責務分けたほうが良くないか?とは思う。 また、onCreate複数回呼んでいないかについて、クラスの判定を行っていない。複数回呼んでクラッシュするというのは Activity での話であり、これを条件に入れたほうが良さそう。