- 静态分析
- 代码质量
- 开发工具
【免费下载链接】infer
A static analyzer for Java, C, C++, and Objective-C
本指南以
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、@NullsafeStrict | TYPE | CLASS |
| 空值安全(方法契约) | @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、@IntegritySink | METHOD/PARAMETER/FIELD(sink 仅 PARAMETER) | CLASS |
| 压制类 | @SuppressLint、@SuppressNullFieldAccess、@SuppressNullMethodCall、@SuppressFieldNotInitialized、@SuppressFieldNotNullable、@SuppressParameterNotNullable、@SuppressReturnOverAnnotated | 多元素(SuppressLint 仅构造器/方法) | CLASS |
| 可变性 | @Mutable | TYPE/FIELD/CONSTRUCTOR/METHOD | CLASS |
| 扩展性 | @OkToExtend | 未显式声明 Target(默认全元素) | SOURCE |
| 验证 | @Verify | CONSTRUCTOR/METHOD/PACKAGE/TYPE | CLASS |
| 断言工具 | 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 特别指出:若返回类型为double或long,该注解会被忽略——因为对这两类基本类型的写入不保证原子性,写写竞争不再良性(源码示例见 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目录下执行:
- 构建 artifact jar(含 class 文件):
mvn package - 构建 sources jar(源码包):
mvn source:jar - 清理:
mvn clean
所有产物均生成于target/目录下。
4.1 pom.xml 关键配置解读
pom.xml 中的关键坐标与配置(以当前仓库为准):
- Maven 坐标:
groupId为com.facebook.infer.annotation,artifactId为infer-annotation,版本为0.18.1-SNAPSHOT,打包方式为jar,描述为 "Annotations for the Infer static analyzer"。 - 编译级别:
maven.compiler.source与maven.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(提供@UnderMigration与MigrationStatus,对应仓库中 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.1与maven-release-plugin:2.5.3(后者将 pom 定位到infer/annotations/pom.xml执行发布)。
4.2 仓库内置的 Makefile 备选路径
除 Maven 外,仓库还为该模块提供了免 Maven 的构建方式:infer/annotations/Makefile。它通过make直接调用javac与jar:
- 源码收集:
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 等包装脚本)即可让对应检查器读取标注。使用方式分三步:
- 获取 JAR:在
infer/annotations下执行mvn package(产物位于target/),或执行make(产物为infer/annotations/annotations.jar与annotations-src.jar)。 - 加入 classpath:编译被分析源码时将 JAR 置于编译 classpath(静态分析器需要从字节码中读取
RetentionPolicy.CLASS的注解);同时确保两个传递依赖可用,即jsr305:3.0.1与kotlin-annotations-jvm:1.3.72(若通过 Maven 引入com.facebook.infer.annotation:infer-annotation坐标,二者会自动传递)。 - 按契约标注:根据上文各注解的语义选择合适的元素与位置。例如并发场景用
@ThreadSafe/@ThreadConfined,空值场景用@Nullsafe(Nullsafe.Mode.LOCAL)加方法契约注解,数据流场景用@PrivacySource/@PrivacySink。
六、小结
infer/annotations虽只由 35 个源码文件构成,却是 Infer Java 检查器生态中"契约注入"的关键一环:它把分析器无法从语言本身获得的信息——线程归属、所有权转移、空值传播契约、性能语义、隐私/完整性来源——以标准 Java 注解的形式固化下来。从源码可以确认,绝大多数注解采用RetentionPolicy.CLASS保留策略并组合灵活的@Target,兼顾了字节码可读性与标注自由度;而 README.md 提供的 Maven 构建命令(mvn package、mvn source:jar、mvn clean)配合 pom.xml 与 Makefile 的双路径,使该模块既能走标准发布流程、也能在仓库内快速产出 JAR。理解这套注解契约,是让 Infer 的 Java 检查器真正"读懂"业务代码意图的前提。
- 静态分析
- 代码质量
- 开发工具
【免费下载链接】infer
A static analyzer for Java, C, C++, and Objective-C
相关推荐
Infer RacerD 线程安全分析器完全指南:从数据竞争检测到注解驱动的静态分析
Infer RacerD 线程安全分析器完全指南:从数据竞争检测到注解驱动的静态分析 本篇技术指南深入解析 Facebook Infer 静态分析器中专门用于并
静态分析代码质量开发工具NemoClaw Runtime Provider Bundle API 契约:13 个 Surface 的注册、身份约束与安全边界全解析
NemoClaw Runtime Provider Bundle API 契约:13 个 Surface 的注册、身份约束与安全边界全解析 本文以 Bundle
dotnet/runtime 可空性(Nullability)注解规范:API 契约、注解决策与可空属性完全指南
dotnet/runtime 可空性(Nullability)注解规范:API 契约、注解决策与可空属性完全指南 本文基于 dotnet/runtime 官方编
语言运行时标准库JIT编译编译器
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考