Skip to content

AIDL编译器原理

追踪 aidl main、Options、parser、AST、类型解析和 backend 生成的真实编译主线。

基于android-17.0.0_r1
AndroidAIDL编译器源码阅读

AIDL编译器原理 ​

Android 17 的 aidl 可执行程序只做两件事:构造 Options,再调用 aidl_entry()。真正的编译流程位于共享库:读取输入、词法/语法解析为 AST、解析类型和常量引用、执行语义检查,最后按 language/backend 生成输出。理解这条主线,才能解释“语法合法但为什么不能生成”。

本文承接 AIDL语法详解、Stub与Proxy代码分析 和 Java-Parcel序列化。本文聚焦编译器入口与阶段 owner,不重复 grammar 每条产生式,也不展开某个 backend 的代码模板。

1. 命令入口 ​

1.1 main ​

源码文件:system/tools/aidl/main.cpp

cpp
int main(int argc, char* argv[]) {
  Options options(argc, argv, kDefaultLang);
  android::aidl::IoDelegate io_delegate;
  int ret = aidl_entry(options, io_delegate);
  return ret;
}

main 不直接 parse 或 generate。这样设计让 aidl_entry 可以在单元测试中复用,命令行只负责组装依赖和返回退出码。

1.2 Options ​

源码文件:system/tools/aidl/options.cpp

Options 保存 language、输入文件、输出目录、import 路径、预处理状态、稳定 AIDL 和 backend 开关。选项错误在进入 Parser 前失败;不能把命令行参数解析错误归到语法错误。

2. 解析阶段 ​

2.1 输入读取 ​

源码文件:system/tools/aidl/parser.cpp

cpp
const AidlDocument* Parser::Parse(
        const std::string& filename,
        const IoDelegate& io_delegate,
        AidlTypenames& typenames,
        bool is_preprocessed) {
    auto clean_path = IoDelegate::CleanPath(filename);
    unique_ptr<string> raw_buffer =
            io_delegate.GetFileContents(clean_path);
    if (raw_buffer == nullptr) return nullptr;
    raw_buffer->append(2u, '\0');
    Parser parser(clean_path, *raw_buffer, is_preprocessed);
    if (yy::parser(&parser).parse() != 0 ||
            parser.HasError()) return nullptr;
    ...
}

Parser 先读取完整文本并追加两个 null,满足 flex/bison 扫描器的缓冲要求;已解析文档可由 AidlTypenames 复用,避免重复解析同一 import。

2.2 AST建立 ​

cpp
ps->MakeDocument(loc(@1), Comments(),
                 std::move(imports),
                 std::move(*$3));

grammar action 将 package/imports/decls 构造成 AidlDocument 和 AidlDefinedType 节点。此时节点存在不等于类型引用已经解析。

2.3 预处理访问 ​

Parser 在加入 typenames 前运行 UnionTagGenerater,为 union 派生 Tag enum。它是 AST 预处理,不是 backend 生成;因此后续类型解析可看到派生声明。

3. 语义阶段 ​

3.1 类型解析 ​

源码文件:system/tools/aidl/parser.cpp

cpp
bool ResolveReferences(const AidlDocument& document,
                       TypeResolver& type_resolver) {
    if (!TypeReferenceResolver(type_resolver).Resolve(document)) {
        return false;
    }
    if (!ConstantReferenceResolver().Resolve(document)) {
        return false;
    }
    if (!CheckNoRecursiveDefinition(document)) {
        return false;
    }
    return true;
}

顺序是类型引用、常量引用、递归定义检查。类型解析可能加载 import 文档;类型失败会阻止后续常量解析。

3.2 常量解析 ​

ConstantReferenceResolver 维护引用栈,发现循环引用后报告路径。常量 token 能被 parser 接受不表示最终表达式可求值;类型范围和引用对象由 semantic phase 判断。

3.3 类型约束 ​

AIDLTypenames 和各 AidlNode 的 CheckValid 继续检查重复声明、非法类型、参数方向、稳定性/注解和目标语言能力。这里产生的是 semantic/backend error,而不是 parser syntax error。

4. 生成阶段 ​

4.1 Backend选择 ​

Options 的 language/backend 选择决定 Java、CPP、NDK、Rust 等生成器;同一个 AST 可被不同 backend 访问,但每个 backend 对注解、parcelable 和稳定性支持不同。

4.2 CodeWriter ​

源码文件:system/tools/aidl/code_writer.cpp

CodeWriter 负责把生成文本写入目标路径并维护错误状态;它不是 AST parser,也不负责决定类型是否合法。生成失败应检查输出目录、权限和 backend writer 错误。

4.3 输出消费者 ​

生成的 Java Stub/Proxy、C++ Bn/Bp 或 Rust trait 成为后续编译输入;AIDL 编译成功只证明生成阶段完成,不证明服务实现能链接、运行或通过 Binder 权限检查。

5. 错误边界 ​

命令行 Options 错误发生在入口;文件读取错误发生在 IoDelegate;token/grammar 错误发生在 bison parser;类型/常量/递归错误发生在 semantic resolver;writer/目标路径错误发生在 backend 输出。分层定位比只看 “aidl failed” 更有用。

6. 测试边界 ​

源码文件:system/tools/aidl/tests/aidl_parser_fuzzer.cpp

parser fuzzer 将任意 bytes 送入解析入口,目标是崩溃和状态健壮性,不证明语义或 backend 输出有效。

源码文件:system/tools/aidl/options_unittest.cpp

Options 单元测试验证命令行参数组合和默认 language,不能证明 parser/生成器成功。

bash
rg -n "Options options|aidl_entry" system/tools/aidl/main.cpp
rg -n "Parser::Parse|MakeDocument|ResolveReferences|TypeReferenceResolver|ConstantReferenceResolver" system/tools/aidl/parser.cpp
rg -n "class AidlDocument|class AidlInterface|class AidlMethod|AidlTypeSpecifier" system/tools/aidl/aidl_language.h
rg -n "CodeWriter|generate|backend" system/tools/aidl/code_writer.cpp system/tools/aidl

读者可以用一个真实 AIDL 文件逐阶段定位:先验证 Options 输入,再看 Parser 是否产生 Document,随后检查引用解析和递归定义,最后确认目标 backend 是否写出 Stub/Proxy;不要把“生成文件存在”当作运行时 Binder 成功。