Infer annotations 注解库全解析:面向 Java 静态分析的 34 个注解、空值安全契约与 Maven 构建指南
2026/9/23 16:23:53 网站建设 项目流程
  • 静态分析
  • 代码质量
  • 开发工具

【免费下载链接】infer

A static analyzer for Java, C, C++, and Objective-C

项目地址:https://gitcode.com/gh_mirrors/infer/infer
点击查看免费下载

本指南以infer/annotations/README.md为骨架,完整覆盖注解 JAR 的构建流程,并结合infer/annotations/src/main/java/com/facebook/infer/annotation/下的全部注解源码逐一剖析其语义与适用场景。读完本文,你将掌握 Infer 为 Java 检查器(如 Starvation、RacerD、Nullsafe、Lineage)提供的注解体系的分类、每个注解的精确契约、在项目中的标注方法,以及通过 Maven 或仓库内置 Makefile 两种方式产出可发布 JAR 的完整流程。

一、注解库在 Infer 生态中的定位

Infer 是一个针对 Java、C、C++ 与 Objective-C 的静态分析器,其infer/annotations子模块承担着一个独特角色:它不包含任何分析逻辑,而是提供一个纯 Java 的注解集合(连同断言工具类),让开发者能在被分析的源码中标注意图与契约,从而让各检查器更精确地工作、减少误报。

正如 infer/annotations/README.md 所述:"This project provides extra annotations for Java checkers, such as starvation."——即该项目为 Java 检查器(如 starvation 检查器)提供额外的注解。仓库源码中该模块共包含35 个 Java 源文件(34 个注解 + 1 个Assertions工具类),统一位于com.facebook.infer.annotation包下,见 infer/annotations/src/main/java/com/facebook/infer/annotation。

从源码结构看,该模块与多个检查器存在明确的对接关系:@ThreadSafe@Functional@ReturnsOwnership@SynchronizedCollection@ThreadConfined服务于线程安全分析(RacerD);@NonBlocking@Lockless服务于 Starvation 检查器;@Nullsafe家族服务于空值安全类型检查器(Nullsafe);@PrivacySource/@PrivacySink@IntegritySource/@IntegritySink服务于数据流(Lineage)分析。相关检查器说明见 infer/documentation/checkers/Starvation.md、infer/documentation/checkers/RacerD.md 与 infer/documentation/checkers/Pulse.md。

二、注解全景地图

为便于检索,先将模块内全部注解按功能域归类如下(依据各源文件的 javadoc 与@Target/@Retention元注解):

功能域注解/类目标元素(@Target)保留策略
空值安全@Nullsafe@NullsafeStrictTYPECLASS
空值安全(方法契约)@PropagatesNullable@TrueOnNull@FalseOnNull@Present前者 PARAMETER,中两者 METHOD,后者多元素CLASS
空值安全(初始化)@Initializer@Cleanup@SuppressViewNullability多元素(后者 FIELD)CLASS
并发与线程安全@ThreadSafe@ThreadConfined@Functional@ReturnsOwnership@SynchronizedCollection@Lockless@NonBlocking多元素CLASS
性能与分配@Expensive@PerformanceCritical@NoAllocation@IgnoreAllocations多元素(后两者 METHOD)CLASS
隐私与完整性@PrivacySource@PrivacySink@IntegritySource@IntegritySinkMETHOD/PARAMETER/FIELD(sink 仅 PARAMETER)CLASS
压制类@SuppressLint@SuppressNullFieldAccess@SuppressNullMethodCall@SuppressFieldNotInitialized@SuppressFieldNotNullable@SuppressParameterNotNullable@SuppressReturnOverAnnotated多元素(SuppressLint 仅构造器/方法)CLASS
可变性@MutableTYPE/FIELD/CONSTRUCTOR/METHODCLASS
扩展性@OkToExtend未显式声明 Target(默认全元素)SOURCE
验证@VerifyCONSTRUCTOR/METHOD/PACKAGE/TYPECLASS
断言工具Assertions(类)

从上表可以提炼出一个实现事实:绝大多数注解的保留策略为RetentionPolicy.CLASS,这意味着它们会被写入 class 文件、对反射不可见,但足以被编译期与字节码分析期的静态检查器读取;唯一例外是@OkToExtend,其保留策略为RetentionPolicy.SOURCE,仅作为编译期语义标记、不进入字节码。

三、按功能域的源码级深入解析

3.1 空值安全注解:Nullsafe 生态

@Nullsafe(Nullsafe.java)是该模块中最复杂的注解,它以类为单位配置空值检查模式,可视为通用版本的@NullsafeStrict。其核心是Mode枚举:

  • Mode.LOCAL:要求所有第三方调用都指向"已检查"代码,对未检查的第三方调用按悲观方式处理(返回值视为可空、参数视为非空除非显式标注@Nullable);对内部已检查代码按既有空值注解正常处理;对内部未检查代码允许调用并采用默认假设。
  • Mode.STRICT:已标记为@Deprecated,语义与@NullsafeStrict类似——只信任严格模式下检查过的内部类,其余调用一律悲观处理;javadoc 明确指出该模式"soon will have no effect on the behaviour of Nullsafe"。

@Nullsafe还支持trustOnly = @Nullsafe.TrustList({...})用于细粒度信任控制,以及TrustList.trustAll布尔开关(类比通配符"*");源码注释同样将TrustList标记为@Deprecated。参数命名为value而非mode,是为了支持单元素注解简写,即写@Nullsafe(Nullsafe.Mode.LOCAL)而非@Nullsafe(mode = ...)

为增强与 Kotlin 的互操作,@Nullsafe还组合使用了javax.annotation.Nonnull@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER}),并叠加 Kotlin 的@UnderMigration(status = MigrationStatus.STRICT),使 kotlinc 无需显式传入-Xjsr305=strict即可识别默认非空语义。

@NullsafeStrict(NullsafeStrict.java)约定严格模式的核心不变量:"若函数通过@NullsafeStrict检查且其返回值未被标注为@Nullable,则该函数确实不会返回 null"(受限于个别待修复的不健全场景)。该注解同样已标记@Deprecated,官方指引为改用@Nullsafe

方法契约三件套用于让调用方代码更优雅、减少冗余断言:

  • @PropagatesNullable(PropagatesNullable.java):声明方法"当且仅当该参数为 null 时返回 null"。调用点若传参非空,Nullsafe 便认定返回值非空,无需断言即可安全解引用;若多个参数同时标注,则方法"当且仅当其中任意参数为 null 时返回 null"。
  • @TrueOnNull(TrueOnNull.java):声明布尔方法在任一参数为 null 时恒返回true。例如@TrueOnNull static boolean isStringEmpty(@Nullable String str),调用方在if (!isStringEmpty(myString))分支内即可安全解引用myString
  • @FalseOnNull(FalseOnNull.java):对称地声明布尔方法在任一参数为 null 时恒返回false,同样让调用方在真分支内免去assertNotNull

@Present(Present.java)面向 Optional 类型:标注类字段、方法返回或参数类型为 Optional 的值"不可缺失(absent)",使用方与静态检查器都必须维护并依赖这一不变量。

初始化相关@Initializer(Initializer.java)声明方法必须在对象被使用前被调用。Nullsafe 检查字段初始化时,若某字段在@Initializer方法中被赋值,即视为已初始化、无需@Nullable标注;其 javadoc 还给出约束:@Initializer方法不应是 private,且建议向客户端文档化该约定(源码附有field1/field2/field3的完整示例,见 Initializer.java)。@Cleanup(Cleanup.java)与@Initializer配对使用:被@Cleanup标注的方法始终允许将字段置空(即使字段不可空),从而显式表达 acquire/release 生命周期。

3.2 并发与线程安全注解

  • @ThreadSafe(ThreadSafe.java):类似javax.concurrent.annotation.ThreadSafe,但可作用于方法@Target含 CONSTRUCTOR、METHOD、TYPE)。关键特性是属性boolean enableChecks() default true@ThreadSafe(enableChecks = false)可让 Infer 直接假设线程安全而不再检查。
  • @ThreadConfined(ThreadConfined.java):告知线程安全分析,被标注类/字段/方法的变更被限定在指定线程内。内置两个常量:UI(UI 线程)与ANY(任意线程,但必须是同一线程),也可传入自定义线程名。
  • @Functional(Functional.java):声明方法总是返回同一值(可被子类覆写继承)。用于压制线程安全分析中对"赋值自@Functional方法字段"的良性竞态告警;javadoc 特别指出:若返回类型为doublelong,该注解会被忽略——因为对这两类基本类型的写入不保证原子性,写写竞争不再良性(源码示例见 Functional.java)。
  • @ReturnsOwnership(ReturnsOwnership.java):声明方法将其返回值的所有权转移给调用方——调用方可在同步之外自由读写该值,而被标注方法自身不得再持有该值的引用。源码注明"该注解目前被信任、未来可能被检查"。
  • @SynchronizedCollection(SynchronizedCollection.java):当集合的线程安全无法从类型体现时(如private @SynchronizedCollection Map mMap = Collections.synchronizedMap(...)),用它告知分析器该集合本身是线程安全的。仅可标注字段。
  • @Lockless(Lockless.java):任何被标注的方法、其覆写、或所属类(及超类)被标注的方法,都不得获取锁。
  • @NonBlocking(NonBlocking.java):向 Starvation 检查器声明方法(或类级别声明下类中所有方法)不执行任何可能阻塞的操作。源码注释强调其过滤效果:不仅该方法本身不会产生 starvation 告警,其所有调用方也不会因此产生告警。

3.3 性能与分配注解

  • @Expensive(Expensive.java):标注代价高昂的方法或类型,供相关检查器识别昂贵调用。
  • @PerformanceCritical(PerformanceCritical.java):标注性能关键的方法或类型。
  • @NoAllocation(NoAllocation.java)与@IgnoreAllocations(IgnoreAllocations.java):均仅可标注方法,分别表达"方法不得分配内存"与"检查器应忽略方法内的分配"两种语义。

3.4 隐私与完整性注解(数据流分析)

这组注解服务于数据流(Lineage)检查器,成对出现:

  • @PrivacySource(PrivacySource.java):标注"隐私数据"的来源——方法返回隐私值、参数为隐私值、字段为隐私值。
  • @PrivacySink(PrivacySink.java):隐私数据不应流入该参数。
  • @IntegritySource(IntegritySource.java):标注"用户可控数据"的来源(方法返回值、参数、字段)。
  • @IntegritySink(IntegritySink.java):用户可控数据不应流入该参数。

3.5 压制类注解与可变性、扩展性、验证注解

Nullsafe 检查器提供了一组精准的"压制"注解,均支持 TYPE/FIELD/CONSTRUCTOR/METHOD 四种目标,用于在局部关闭特定类别的告警而不影响全局:

  • @SuppressNullFieldAccess(SuppressNullFieldAccess.java):压制对可能为 null 字段访问的告警。
  • @SuppressNullMethodCall(SuppressNullMethodCall.java):压制对可能为 null 接收者方法调用的告警。
  • @SuppressFieldNotInitialized(SuppressFieldNotInitialized.java):压制字段未初始化告警。
  • @SuppressFieldNotNullable(SuppressFieldNotNullable.java):压制字段不可空性(field-not-nullable)告警。
  • @SuppressParameterNotNullable(SuppressParameterNotNullable.java):压制参数不可空性告警。
  • @SuppressReturnOverAnnotated(SuppressReturnOverAnnotated.java):压制返回值过度标注(return-over-annotated)告警。
  • @SuppressViewNullability(SuppressViewNullability.java):仅可标注字段,用于 View 在析构中被置空、在初始化器中创建的场景下静默告警。

其余注解:

  • @Mutable(Mutable.java):标注可变的类型、字段、构造器或方法,供需要区分可变/不可变的分析使用。
  • @OkToExtend(OkToExtend.java):声明类"预期被继承",用于抑制对子类化的常见滥用;其 javadoc 建议只为有意被扩展的类添加,避免给长期无需扩展的类打标。
  • @Verify(Verify.java):可标注构造器、方法、包与类型,用于要求/标记待验证的范围。
  • @SuppressLint(SuppressLint.java):与 Android lint 的@SuppressLint同名,支持String[] value()指定要压制的告警名称。

3.6 断言工具类 Assertions

Assertions.java 是唯一非注解的类,为 Nullsafe 类型检查器提供运行时与纯静态两套断言:

  • assertNotNull(T, String)/assertNotNull(T):带运行时检查(为 null 则抛AssertionError)。
  • assumeNotNull(T, String)/assumeNotNull(T):不做运行时检查,仅静态告知检查器"此处不会为 null";javadoc 建议优先使用assertNotNull,因为 JVM 会优化空值检查,而assumeNotNull会让意外 null 向后传播、掩盖真实错误。
  • nullsafeFIXME(T, String):与assumeNotNull类似,但语义上明确"正确修复待落地",用于临时压制。
  • assertGet(int, List)/assertGet(K, Map):带越界/键存在检查的集合取值。
  • assertCondition(boolean[, String])assumeCondition(boolean[, String]):条件断言。
  • assertUnreachable([String|Exception]):不可达代码断言。

四、构建注解 JAR:Maven 与 Makefile 双路径

原文档明确将JAR 构建定位为"适合发布(suitable for release)"的流程,本模块是一个标准 Maven 工程,常规 maven 命令即可直接使用。以下命令需预先安装 maven,并在infer/annotations目录下执行:

  1. 构建 artifact jar(含 class 文件)mvn package
  2. 构建 sources jar(源码包)mvn source:jar
  3. 清理mvn clean

所有产物均生成于target/目录下。

4.1 pom.xml 关键配置解读

pom.xml 中的关键坐标与配置(以当前仓库为准):

  • Maven 坐标groupIdcom.facebook.infer.annotationartifactIdinfer-annotation,版本为0.18.1-SNAPSHOT,打包方式为jar,描述为 "Annotations for the Infer static analyzer"。
  • 编译级别maven.compiler.sourcemaven.compiler.target均为1.7,即目标为 Java 7 字节码,便于被低版本运行时项目引用。
  • 编译期依赖
    • com.google.code.findbugs:jsr305:3.0.1(提供@Nonnull@Nullable@TypeQualifierDefault等,供@Nullsafe的 Kotlin 互操作使用,对应仓库中 infer/dependencies/java/jsr-305/jsr305.jar);
    • org.jetbrains.kotlin:kotlin-annotations-jvm:1.3.72(提供@UnderMigrationMigrationStatus,对应仓库中 infer/dependencies/java/kotlin-annotations/kotlin-annotations-jvm-1.3.72.jar)。
  • 发布相关:声明了 MIT 许可证、SCM 信息(tag 为infer-annotation-0.18.0),并配置了maven-javadoc-plugin:3.1.1maven-release-plugin:2.5.3(后者将 pom 定位到infer/annotations/pom.xml执行发布)。

4.2 仓库内置的 Makefile 备选路径

除 Maven 外,仓库还为该模块提供了免 Maven 的构建方式:infer/annotations/Makefile。它通过make直接调用javacjar

  • 源码收集:find src/main/java/com/facebook/infer/annotation -name "*.java"
  • 编译:javac -source 8 -target 8 -cp $(JSR_JAR):$(KOTLIN_ANNOT_JAR) ... -d annot_classes(注意此处目标为 Java 8,且 classpath 显式引用上述两个依赖 JAR);
  • 打包:在临时目录中jar cvf annotations.jar com生成 infer/annotations/annotations.jar,并另有annotations-src.jar源码包;
  • 清理:make clean删除临时类目录与两个产物。

两条路径的产物等价,选择依据是构建环境:Maven 适合发布与集成到外部构建链,Makefile 适合仓库内快速产出本地 JAR

五、在自己的项目中使用注解

引用这些注解后运行infer run -- javac ...(或通过 infer/lib/wrappers/javac 等包装脚本)即可让对应检查器读取标注。使用方式分三步:

  1. 获取 JAR:在infer/annotations下执行mvn package(产物位于target/),或执行make(产物为infer/annotations/annotations.jarannotations-src.jar)。
  2. 加入 classpath:编译被分析源码时将 JAR 置于编译 classpath(静态分析器需要从字节码中读取RetentionPolicy.CLASS的注解);同时确保两个传递依赖可用,即jsr305:3.0.1kotlin-annotations-jvm:1.3.72(若通过 Maven 引入com.facebook.infer.annotation:infer-annotation坐标,二者会自动传递)。
  3. 按契约标注:根据上文各注解的语义选择合适的元素与位置。例如并发场景用@ThreadSafe/@ThreadConfined,空值场景用@Nullsafe(Nullsafe.Mode.LOCAL)加方法契约注解,数据流场景用@PrivacySource/@PrivacySink

六、小结

infer/annotations虽只由 35 个源码文件构成,却是 Infer Java 检查器生态中"契约注入"的关键一环:它把分析器无法从语言本身获得的信息——线程归属、所有权转移、空值传播契约、性能语义、隐私/完整性来源——以标准 Java 注解的形式固化下来。从源码可以确认,绝大多数注解采用RetentionPolicy.CLASS保留策略并组合灵活的@Target,兼顾了字节码可读性与标注自由度;而 README.md 提供的 Maven 构建命令(mvn packagemvn source:jarmvn clean)配合 pom.xml 与 Makefile 的双路径,使该模块既能走标准发布流程、也能在仓库内快速产出 JAR。理解这套注解契约,是让 Infer 的 Java 检查器真正"读懂"业务代码意图的前提。

  • 静态分析
  • 代码质量
  • 开发工具

【免费下载链接】infer

A static analyzer for Java, C, C++, and Objective-C

项目地址:https://gitcode.com/gh_mirrors/infer/infer
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询