CodeQL 文档

Java 和 Kotlin 中的注解

Java/Kotlin 项目的 CodeQL 数据库包含有关附加到程序元素的所有注解的信息。

注意

CodeQL 对 Kotlin 的分析目前处于测试阶段。在测试期间,对 Kotlin 代码的分析及其相关文档的完整性将不如其他语言。

关于使用注解

注解由以下 CodeQL 类表示

  • Annotatable 表示所有可能附加有注解的实体(即,包、引用类型、字段、方法和局部变量)。
  • AnnotationType 表示 Java 注解类型,例如 java.lang.Override;注解类型是接口。
  • AnnotationElement 表示注解元素,即注解类型的成员。
  • Annotation 表示注解,例如 @Override;注解值可以通过成员谓词 getValue 访问。

例如,Java/Kotlin 标准库定义了一个注解 SuppressWarnings,它指示编译器不要发出某些类型的警告

package java.lang;

public @interface SuppressWarnings {
    String[] value;
}

SuppressWarnings 被表示为一个 AnnotationType,其唯一的 AnnotationElementvalue

SuppressWarnings 的典型用法是用于防止使用原始类型发出警告的此注解

class A {
    @SuppressWarnings("rawtypes")
    public A(java.util.List rawlist) {
    }
}

表达式 @SuppressWarnings("rawtypes") 被表示为一个 Annotation。字符串字面量 "rawtypes" 用于初始化注解元素 value,其值可以通过 getValue 谓词从注解中提取。

然后我们可以编写此查询来查找附加到构造函数的所有 @SuppressWarnings 注解,并返回注解本身及其 value 元素的值

import java

from Constructor c, Annotation ann, AnnotationType anntp
where ann = c.getAnAnnotation() and
    anntp = ann.getType() and
    anntp.hasQualifiedName("java.lang", "SuppressWarnings")
select ann, ann.getValue("value")

如果您正在分析的代码库使用 @SuppressWarnings 注解,您可以检查查询返回的注解元素的 value。它们应该使用上面描述的 "rawtypes" 值。

作为另一个例子,此查询查找所有只有一个注解元素的注解类型,该元素的名称为 value

import java

from AnnotationType anntp
where forex(AnnotationElement elt |
    elt = anntp.getAnAnnotationElement() |
    elt.getName() = "value"
)
select anntp

示例:查找缺少的 @Override 注解

在较新的 Java 版本中,建议(虽然不是必需)您将覆盖另一个方法的方法用 @Override 注解进行注释。这些注解由编译器检查,用作文档,并帮助您避免意外的重载,而本应是覆盖。

例如,考虑此示例程序

class Super {
    public void m() {}
}

class Sub1 extends Super {
    @Override public void m() {}
}

class Sub2 extends Super {
    public void m() {}
}

这里,Sub1.mSub2.m 都覆盖了 Super.m,但只有 Sub1.m@Override 进行注释。

现在我们将开发一个查询,用于查找像 Sub2.m 这样的方法,这些方法应该用 @Override 注解,但没有注释。

作为第一步,让我们编写一个查询,查找所有 @Override 注解。注解是表达式,因此可以使用 getType 访问其类型。另一方面,注解类型是接口,因此可以使用 hasQualifiedName 查询其限定名称。因此,我们可以像这样实现查询

import java

from Annotation ann
where ann.getType().hasQualifiedName("java.lang", "Override")
select ann

和往常一样,最好在 Java/Kotlin 项目的 CodeQL 数据库上尝试此查询,以确保它确实产生了一些结果。在前面的示例中,它应该找到 Sub1.m 上的注解。接下来,我们将 @Override 注解的概念封装为 CodeQL 类

class OverrideAnnotation extends Annotation {
    OverrideAnnotation() {
        this.getType().hasQualifiedName("java.lang", "Override")
    }
}

这使得编写查找覆盖另一个方法但没有 @Override 注解的方法的查询变得非常容易:我们使用谓词 overrides 来确定一个方法是否覆盖另一个方法,并使用谓词 getAnAnnotation(对任何 Annotatable 都可用)来检索某个注解。

import java

from Method overriding, Method overridden
where overriding.overrides(overridden) and
    not overriding.getAnAnnotation() instanceof OverrideAnnotation
select overriding, "Method overrides another method, but does not have an @Override annotation."

在实践中,此查询可能会从编译的库代码中产生许多结果,这些结果并不十分有趣。因此,最好添加另一个合取词 overriding.fromSource() 来限制结果,仅报告有源代码可用的方法。

示例:查找对已弃用方法的调用

作为另一个例子,我们可以编写一个查询,查找对用 @Deprecated 注解标记的方法的调用。

例如,考虑此示例程序

class A {
    @Deprecated void m() {}

    @Deprecated void n() {
        m();
    }

    void r() {
        m();
    }
}

这里,A.mA.n 都被标记为已弃用。方法 nr 都调用了 m,但请注意,n 本身已弃用,因此我们可能不应该对此调用发出警告。

与前面的示例一样,我们将从定义一个用于表示 @Deprecated 注解的类开始

class DeprecatedAnnotation extends Annotation {
    DeprecatedAnnotation() {
        this.getType().hasQualifiedName("java.lang", "Deprecated")
    }
}

现在我们可以定义一个用于表示已弃用方法的类

class DeprecatedMethod extends Method {
    DeprecatedMethod() {
        this.getAnAnnotation() instanceof DeprecatedAnnotation
    }
}

最后,我们使用这些类来查找对已弃用方法的调用,排除自身出现在已弃用方法中的调用

import java

from Call call
where call.getCallee() instanceof DeprecatedMethod
    and not call.getCaller() instanceof DeprecatedMethod
select call, "This call invokes a deprecated method."

在我们的示例中,此查询会标记 A.r 中对 A.m 的调用,但不会标记 A.n 中的调用。

有关类 Call 的更多信息,请参见“在调用图中导航”。

改进

Java/Kotlin 标准库提供了另一个注解类型 java.lang.SupressWarnings,它可用于抑制某些类别的警告。特别是,它可以用于关闭对已弃用方法的调用的警告。因此,改进我们的查询以忽略从标记有 @SuppressWarnings("deprecation") 的方法内部对已弃用方法的调用是很有意义的。

例如,考虑此略微更新的示例

class A {
    @Deprecated void m() {}

    @Deprecated void n() {
        m();
    }

    @SuppressWarnings("deprecation")
    void r() {
        m();
    }
}

这里,程序员已明确抑制了 A.r 中关于已弃用调用的警告,因此我们的查询不应该再标记对 A.m 的调用了。

为此,我们首先引入一个类来表示所有 @SuppressWarnings 注解,其中字符串 deprecation 出现在要抑制的警告列表中

class SuppressDeprecationWarningAnnotation extends Annotation {
    SuppressDeprecationWarningAnnotation() {
        this.getType().hasQualifiedName("java.lang", "SuppressWarnings") and
        this.getAValue().(Literal).getLiteral().regexpMatch(".*deprecation.*")
    }
}

这里,我们使用 getAValue() 来检索任何注解值:实际上,注解类型 SuppressWarnings 只有一个注解元素,因此每个 @SuppressWarnings 注解只有一个注解值。然后,我们确保它是一个字面量,使用 getLiteral 获取其字符串值,并使用正则表达式匹配检查它是否包含字符串 deprecation

对于实际使用,此检查需要稍微概括一下:例如,OpenJDK Java 编译器允许 @SuppressWarnings("all") 注解来抑制所有警告。我们可能还想确保将 deprecation 匹配为一个完整的词,而不是另一个词的一部分,方法是将正则表达式更改为 ".*\\bdeprecation\\b.*"

现在我们可以扩展我们的查询以过滤掉包含 SuppressDeprecationWarningAnnotation 的方法中的调用

import java

// Insert the class definitions from above

from Call call
where call.getCallee() instanceof DeprecatedMethod
    and not call.getCaller() instanceof DeprecatedMethod
    and not call.getCaller().getAnAnnotation() instanceof SuppressDeprecationWarningAnnotation
select call, "This call invokes a deprecated method."

项目中通常包含对似乎已弃用方法的调用。

  • ©GitHub, Inc.
  • 条款
  • 隐私