
编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载在 Dart AOT预编译模式下嵌入宿主embedder或 Native 代码需要通过 Native Dart API 反向调用 Dart 中的类、成员或闭包但预编译器在执行 tree shaking 与 TFAType Flow Analysis时会把“编译器看不见的调用”全部裁掉。pragma(vm:entry-point, ...)就是为解决这一矛盾而设计的强制标注它显式声明哪些 Dart 实体可作为入口点root被 Native/VM 代码解析、分配或调用并在启用混淆obfuscation时保证对应符号名得以保留。读完本文你将掌握该 pragma 在类、getter、setter、构造器、普通过程与字段上的完整语法与语义并能从 precompiler.cc 等源码层面理解标注是如何被解析、校验并转化为“保留原因”retain reason的。一、为什么 AOT 模式必须声明入口点Dart VM 的预编译器AOT compiler会对整个程序做全程序优化核心手段包括Tree shaking死代码消除凡是从可达性分析角度看不到调用者的函数与类都不会进入最终镜像TFAType Flow Analysis基于类型流分析推断各调用点/分配点的精确类型以生成更紧凑的代码。这类优化隐含一个前提编译器能看到完整的 Dart 程序能发现并分析所有运行时“可能执行”的函数与成员。Dart 侧代码确实全部可见但嵌入宿主的 C 代码与 native 方法对编译器而言是不可达的黑盒——它们通过 Native Dart API 回调 Dart。于是预编译器无法知道哪些 Dart 实体会被这些外部代码触达。这带来两条硬性规则来自 官方文档标注是强制的must只要程序中定义了会回调 Dart 的 native 方法就必须显式列出被 Native 代码访问的类与成员否则编译结果在运行时可能出错例如解析不到类名、调用到已被裁剪的函数。它不是可选的“优化提示”而是正确性前提。混淆场景的符号保留启用 obfuscation 时预编译器还需要知道哪些符号不能被改名否则 Native 代码按名字解析会失败。另外为了缩小 JIT 与 AOT 的行为差异JIT 模式同样会检查入口点标注唯一的例外是dart:mirrors的用法以及通过 VM service 进行的调试访问——这两种通道在 JIT 下天然拥有“全程序可见”的能力不需要 pragma 背书。二、标注语法与语义pragma(vm:entry-point, ...)必须放在目标类或成员上。所有位置都支持同一组“第二参数”形式表示入口点的有效性条件pragma(vm:entry-point) // 无条件生效 pragma(vm:entry-point, true/false) // 常量布尔 pragma(vm:entry-point, !const bool.fromEnvironment(dart.vm.product)) // 仅非 product调试模式由于第二参数是常量布尔表达式!const bool.fromEnvironment(dart.vm.product)这种写法可以在product发布构建中整体剔除该入口点从而在发布镜像中不保留仅用于开发期调试的回调函数。以下按文档的六类承载对象逐一说明以下代码块均继承自 entry_point_pragma.md可逐字复用。2.1 类允许从 Native 代码分配pragma(vm:entry-point) pragma(vm:entry-point, true/false) pragma(vm:entry-point, !const bool.fromEnvironment(dart.vm.product)) class C { ... }第二参数缺失、为null或true时该类可以被 Native 或 VM 代码直接分配例如通过Dart_New系列 API 按名解析类并创建实例。该标注可以加在抽象类上此时类名会在混淆后幸存符号保留但不会生成任何分配桩allocation stubs即只保留“名字”不可实例化。2.2 Getter允许通过 Dart_GetField 取值pragma(vm:entry-point) pragma(vm:entry-point, true/false) pragma(vm:entry-point, !const bool.fromEnvironment(dart.vm.product)) pragma(vm:entry-point, get) void get foo { ... }get表示允许通过Dart_GetField获取 getter 的值。注意一个易错点Dart_Invoke只能用于返回闭包值的 getter。此时实际流程等价于先用Dart_GetField取出闭包、再用Dart_InvokeClosure调用它——因此这类用法同样需要get标注单独用call是不正确的。第二参数缺失/null/true时语义与get相同。getter不能被 closurize取不出它的闭包对象。2.3 Setter允许通过 Dart_SetField 赋值pragma(vm:entry-point) pragma(vm:entry-point, true/false) pragma(vm:entry-point, !const bool.fromEnvironment(dart.vm.product)) pragma(vm:entry-point, set) void set foo(int value) { ... }set表示允许通过Dart_SetField写入。第二参数缺失/null/true时语义与set相同。setter 同样不能被 closurize。2.4 构造器允许被调用生成式构造器需连带类标注pragma(vm:entry-point) pragma(vm:entry-point, true/false) pragma(vm:entry-point, !const bool.fromEnvironment(dart.vm.product)) pragma(vm:entry-point, call) C(this.foo) { ... }call表示构造器可被直接调用经Dart_Invoke按名解析并调用。第二参数缺失/null/true时语义与call相同。关键约束如果构造器是生成式generative的其所在类必须同时带有入口点标注使类可从 Native 或 VM 代码分配。构造器不能被 closurize。2.5 其他过程区分“取闭包”与“直接调用”pragma(vm:entry-point) pragma(vm:entry-point, true/false) pragma(vm:entry-point, !const bool.fromEnvironment(dart.vm.product)) pragma(vm:entry-point, get) pragma(vm:entry-point, call) void foo(int value) { ... }对普通函数/方法语义与 getter/setter 不同需要区分两种访问方式get仅允许 closurization即通过Dart_GetField取出该函数的闭包对象后续可用Dart_InvokeClosure调用call仅允许通过Dart_Invoke直接调用第二参数缺失/null/true两种访问方式都允许。2.6 字段get / set / init 三种粒度对非静态字段以下形式都允许其中前三种形式无条件、null、布尔同样可用于静态字段pragma(vm:entry-point) pragma(vm:entry-point, null) pragma(vm:entry-point, true/false) pragma(vm:entry-point, !const bool.fromEnvironment(dart.vm.product)) pragma(vm:entry-point, get/set/init) int foo;语义分三种情况缺省 /null/true字段整体标记为可供 Native 访问对非静态字段其所在类接口上对应的 getter 和 setter同时被标记为可供 Native 调用。get或set只标记对应的 getter 或 setter 之一。init将该字段可以是final标记为“由 Native 代码初始化”例如在external构造器中完成赋值不标记其 getter/setter。对静态字段只要字段被标记为可访问其隐式 getter 就总是被一并标记。还有一个与闭包交互的细节一个持有闭包的字段只有在其 getter 被标记后才能用Dart_Invoke调用其中的闭包——这等价于先用Dart_GetField从字段取出闭包再用Dart_InvokeClosure调用。三、源码实现标注如何被解析为“入口点”3.1 符号与枚举定义pragma 名字vm:entry-point本身注册在 VM 的符号表中见 symbol_list.hV(vm_entry_point, vm:entry-point)解析结果被归约为一个枚举并配有静态判断工具类定义在 object.henum class EntryPointPragma { kAlways, // 无条件生效 kNever, // 不生效含显式 false kGetterOnly, // get kSetterOnly, // set kCallOnly, // call kTypeOnly // 仅类型可访问不产生访问桩 }; struct EntryPointPragmaUtils : public AllStatic { static constexpr bool AllowsCall(EntryPointPragma pragma) { ... } static constexpr bool AllowsGet(EntryPointPragma pragma) { ... } static constexpr bool AllowsSet(EntryPointPragma pragma) { ... } static constexpr bool AllowsTypeAccess(EntryPointPragma pragma) { ... } static constexpr bool AllowsAccess(EntryPointPragma pragma) { // kNever 与 kTypeOnly 不允许实例级访问 return pragma ! EntryPointPragma::kNever pragma ! EntryPointPragma::kTypeOnly; } };这里的语义矩阵与文档完全对应kAlways允许 call/get/set 三种访问kGetterOnly/kSetterOnly/kCallOnly各自放行一种kTypeOnly只放行“类型访问”对应文档中抽象类“名字幸存但无分配桩”的行为kNever全部拒绝。3.2 解析函数 FindEntryPointPragma把pragma(vm:entry-point, X)的第二参数映射到上述枚举的核心函数是 object.cc 中的 FindEntryPointPragma。从源码结构看它遍历目标实体 metadata 上的所有pragma识别vm:entry-point以及与动态模块相关的同类标注然后按第二参数取值返回第二参数取值返回枚举对应文档语义缺失 /null/truekAlways完全可用getkGetterOnly仅允许取值/取闭包setkSetterOnly仅允许赋值callkCallOnly仅允许调用其余如falsekNever入口点被禁用也就是说文档中pragma(vm:entry-point, false)这种写法在实现层的含义是该成员显式声明“不是入口点”解析为kNever可用于表达“此标注在某个变体下失效”的条件组合。3.3 预编译器的入口点收集流程真正的“根”收集发生在 AOT 预编译器里。precompiler.cc 的 MarkEntryPointsFromNativePragmas 相关逻辑 遍历每个库中的每个类按顺序处理三类带 pragma 的成员类级标注若AllowsAccess成立则AddInstantiatedClassAddApiUse——把类加入实例化清单并保留其 API 可见性混淆时保名若只是AllowsTypeAccess即kTypeOnly则只保留 API 与类型不产生分配桩。字段级标注被标记的字段本身通过AddField/AddApiUse保留对非静态字段按AllowsGet/AllowsSet把该字段记入隐式 getter / 隐式 setter 清单静态字段记入隐式静态 getter 清单与文档“静态字段被标记时隐式 getter 总是被标记”一一对应。函数级标注隐式 getter/setter只有在其对应字段被标记时才补标mark_implicit_method_if_field_saved——这解释了为什么字段标注会“连带”接口的访问器显式 getter 需AllowsGet、显式 setter 需AllowsSet、构造器需AllowsCall且生成式构造器会触发AddInstantiatedClass强制把所在类加入可分配清单——这正是文档中“生成式构造器要求类也带标注”的实现保证普通过程AllowsCall时保留函数本体AllowsGet时则对其ImplicitClosureFunction()隐式闭包函数调用mark_entry_point_and_api_methods。代码注释明确写道Dart_GetField是通过原函数取得隐式闭包函数的因此 API 保留记在原函数上而实际被保留的代码实体是闭包函数。被标记的函数最终写入functions_with_entry_point_pragmas_集合并以RetainReasons::kEntryPointPragma作为保留原因即不会被 tree shaking 裁掉。3.4 混淆下的符号保留启用 obfuscation 后光保留代码还不够符号名也必须幸存。precompiler.cc 中凡是命中functions_with_entry_point_pragmas_的函数会额外追加RetainReasons::kEntryPointPragmaSignature保留原因确保其签名可被 Native 侧按名解析的符号在混淆后不变。这就是文档 Background 一节所说“precompiler 需要知道哪些符号需要被保留”的落点。3.5 JIT 模式下的运行时校验文档提到 JIT 模式也会检查入口点标注mirrors 与 VM service 调试除外。从源码结构看VM 运行时在解析/调用路径上同样调用EntryPointPragmaUtils::AllowsGet/AllowsSet/AllowsCall来校验函数是否被允许以某种方式访问参见 object.cc 中对 expected 访问方式的判定。这意味着即使你在 JIT 下“侥幸能跑”一旦切到 AOT缺少正确标注的入口点会直接失效——以 AOT 能正确解析和调用为准是标注完整性的验收标准。四、真实用例从 samples 到测试代码仓库内的 embedder 示例提供了可直接对照的最小实践。samples/embedder/timer.dart 展示了一个典型的“Native 驱动 Dart 定时器”入口面pragma(vm:entry-point, call) void startTimer(int millis) { ... } pragma(vm:entry-point, call) void stopTimer() { ... } pragma(vm:entry-point, call) void resetTimer() { ... } pragma(vm:entry-point, get) int get ticks _tickCount;四个函数全部使用精确的单一权限标注需要被Dart_Invoke调用的用call需要被Dart_GetField读值的 getter 用get——没有一处滥用缺省的全能标注。samples/embedder/hello.dart 与 program1.dart、program2.dart 则是最简形式对main级别的入口函数统一标注call。dart_api_impl_test.cc 还演示了类级标注pragma(vm:entry-point)配合嵌套类内call/get函数标注的组合用法可作为编写自己的 Native API 面时参考的“标注模板”。五、实践建议与常见陷阱综合文档与源码归纳出以下可直接执行的规则权限最小化优先使用call/get/set精确标注而不是裸pragma(vm:entry-point)后者等价kAlways三种访问全放行。精确标注在 JIT 下能更早暴露标注错误。生成式构造器连带类标注只标注构造器而不标注类是无效的——预编译器要求类可实例化类名与分配路径必须同时保留。“调用闭包”不等于call对返回闭包的 getter、或持有闭包的字段其“调用”路径本质是Dart_GetFieldDart_InvokeClosure必须标get。静态字段与 getter 的连带关系静态字段被标记后其隐式 getter 自动保留无需也不应再单独标注。init专用于 final 字段当final字段在external构造器或 Native 初始化路径中赋值时用init标记“该字段由 Native 初始化”避免误伤 getter/setter 的保留。调试专用入口用条件标注!const bool.fromEnvironment(dart.vm.product)可让仅开发期使用的回调函数在 product 构建中整体消失减小镜像体积。验收标准用 AOT 预编译产物验证——Native 侧按名解析、分配、调用均成功且若启用 obfuscation符号名未被改写。六、延伸阅读pragmas_recognized_by_compiler.md编译器含 AOT 预编译器识别的全部 pragma 一览vm:entry-point是其中与 Native 互操作最相关的一个pragmas.mdVM 层面 pragma 机制的总览runtime/vm/compiler/aot/precompiler.cc入口点收集、保留原因与签名保留的完整实现samples/embedder 目录一组可编译运行的 embedder 示例展示了 pragma 在实际 C/Dart 混合工程中的用法。赞分享编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载相关推荐Dart VM 方法内联 FAQ 深度解读pragma(vm:prefer-inline) 的条件、实战与 JIT/AOT 差异Dart VM 方法内联 FAQ 深度解读 pragma vm:prefer inline 的条件、实战与 JIT/AOT 差异 本指南以 docs/F编程语言编译器语言运行时标准库开发工具Dart SDK 中 dart2js Pragma 注解完全指南内联、运行时检查与代码优化控制Dart SDK 中 dart2js Pragma 注解完全指南内联、运行时检查与代码优化控制 导读 本文基于 Dart SDK 中 pkg/compiler编程语言编译器语言运行时标准库开发工具终极指南Ivy编译缓存安全机制如何防止恶意代码注入终极指南Ivy编译缓存安全机制如何防止恶意代码注入 在人工智能开发中编译缓存机制是提升效率的关键技术但同时也可能成为恶意代码注入的温床。Ivy作为一款强大人工智能机器学习开发工具上一篇5个强力理由选择MahApps.Metro构建现代Windows应用下一篇GalaxyBook Mask终极指南3分钟让普通电脑运行三星笔记的完整解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考