免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Error Prone 的 UnicodeInCode 检查:禁止代码中的非 ASCII Unicode 字符,杜绝同形异义字(Homoglyph)陷阱

Error Prone 的 UnicodeInCode 检查:禁止代码中的非 ASCII Unicode 字符,杜绝同形异义字(Homoglyph)陷阱 静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载导读本文围绕 Error Prone 内置检查器UnicodeInCode展开说明它为什么把代码中非注释、非字面量区域出现非 ASCII Unicode 字符当作编译错误来报告以及它如何处理同形异义字homoglyph导致的肉眼看似正确、实际调用了另一个方法的安全隐患。读完本文你将掌握该检查的判定规则、豁免区域、SuppressWarnings抑制方式以及它在源码中的实现原理与测试覆盖可以直接在自己的 Error Prone 项目中复现与验证。为什么要在代码中禁用非 ASCII Unicode 字符官方文档开门见山地给出了结论在代码中使用非 ASCII 的 Unicode 字符既容易造成混淆也可能带来安全风险。其典型场景就是同形异义字homoglyph——两个字符字形几乎相同、但码点完全不同被编译器视为不同的标识符。文档中给出的例子非常直观import static com.google.common.base.Objects.equal; public void isAuthenticated(String password) { // The l here is not what it seems. return equaⅼ(password, this.password()); } // ... private boolean equaⅼ(String a, String b) { return true; }注意第 12 行调用的equaⅼ其中的字符并不是普通的拉丁小写字母l而是形似字母l的 Unicode 字符ⅼ码点 U213CSMALL L WITH STROKE。由于同形异义字在绝大多数编辑器中与普通l几乎无法分辨开发者极易误以为这里调用的是import static引入的Objects.equal而实际上它命中了一个私有方法equaⅼ这个方法无条件返回true。这正是文档所警告的不安全所在一段看似正常执行的鉴权代码可能因为一个不可见的字符差异绕过真正的相等性判断。同理如果同形异义字出现在方法名、变量名、类名中还可能造成以为调用了 A 方法实际调用了 B 方法的误用甚至被用于恶意代码投毒在他人代码里植入视觉上相同的标识符以改变行为。检查器的判定规则什么算违规UnicodeInCode的检查器声明位于 core/src/main/java/com/google/errorprone/bugpatterns/UnicodeInCode.javaBugPattern( severity ERROR, summary Avoid using non-ASCII Unicode characters outside of comments and literals, as they can be confusing.) public final class UnicodeInCode extends BugChecker implements CompilationUnitTreeMatcher关键信息有三点严重级别为ERROR一旦触发直接以编译错误的形式报告而不是警告。允许区域明确注释comments和字面量literals即字符串字面量、字符字面量中的非 ASCII 字符是允许的检查只针对代码部分。实现方式该检查器实现了CompilationUnitTreeMatcher也就是说它在整个编译单元的层面工作逐字符扫描源码文本而不是匹配某个 AST 节点类型。可接受的 ASCII 的定义判定一个字符是否违规依据是源码 UnicodeInCode.java 中的isAcceptableAscii(char)private static boolean isAcceptableAscii(char c) { return (c 0x20 c 0x7E) || c \n || c \r || c \t; }也就是说只有以下字符被允许出现在代码区域范围含义0x20–0x7E可打印 ASCII空格、数字、字母、常见标点\n换行符\r回车符\t制表符除此之外的任何字符——包括全角标点、中文/日文/韩文等 CJK 字符、希腊字母π、数学符号、各种重音字符乃至零宽字符、方向性字符——一旦出现在注释和字面量之外都会触发违规报告。这也是为什么测试用例中把π用作 Java 标识符static final double π 3;会被判定为违规。允许豁免的区域注释与字面量主流程matchCompilationUnit的实现如下见 UnicodeInCode.java遍历整个源码文本用isAcceptableAscii标记出所有不允许的字符位置记录为一段段违规区间RangeSetInteger。通过commentsAndLiterals(state, sourceCode)计算所有注释、字符串字面量、字符字面量所在的区间。通过suppressedRegions(state)计算被SuppressWarnings覆盖的区域。将这两类允许区域取并集union凡是被允许区域**完整包裹encloses**的违规区间不再报告。commentsAndLiterals的实现见 UnicodeInCode.java借助 Error Prone 的词法层工具ErrorProneTokens.getTokens拿到全部词法单元对TokenKind.STRINGLITERAL字符串字面量和TokenKind.CHARLITERAL字符字面量取其pos()到endPos()的区间对每个词法单元附带的所有注释t.comments()按注释源码位置与文本长度构造区间。因此下面这些用法都不会报错/** π */ // Javadoc 注释中的 Unicode class Test { static final String pi π; // 字符串字面量 static final char pi π; // 字符字面量 final int x 1; // 粗略值 π ≈ 3.14 // 行尾注释 }上述场景正是测试文件中的四个负例见 UnicodeInCodeTest.javanegative纯 ASCII 代码、negativeInComment、negativeInStringLiteral、negativeInCharLiteral。同形异义字如何被精准命中文档中的示例代码在标识符中藏入了同形异义字。从实现上看检查器并不依赖任何同形异义字字典——它只是朴素地判定该字符是否为可接受的 ASCII。因此无论是ⅼU213C、πU03C0、全角空格还是 Unicode 双向控制字符只要是注释/字面量之外的非 ASCII 字符一律会被标记。这意味着它的覆盖面是全字符集的与其说它是同形异义字检测器不如说它是代码区域非 ASCII 字符禁区。同形异义字只是其防御目标中最重要的安全场景之一。报告信息本身也很有用见 UnicodeInCode.java错误消息会把违规字符用javaCharEscaper()转义后嵌入消息例如测试中期望的Unicode character (\u03c0)让开发者能直接看到问题字符的 Unicode 转义形式而不必在终端里面对一个看起来正常的字符发愁。默认开启ERROR 级别的内置检查UnicodeInCode是 Error Prone 开箱即用的内置检查器之一注册在 core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java。它位于ENABLED_ERRORS集合该集合从第 725 行开始定义也就是说只要在你的构建中启用了 Error Prone 插件这个检查就默认生效、默认以 ERROR 严重级别拦截问题无需额外配置。从 BuiltInCheckerSuppliers.java 可以看到ENABLED_ERRORS的语义通过allChecks().filter(...)只保留默认启用的错误级检查器供ScannerSupplier装配到编译流程中。如何抑制SuppressWarnings(UnicodeInCode)当某个代码区域确实需要比如出于科学计算约定标识符中要使用希腊字母时可以按 Error Prone 的通用规则用SuppressWarnings抑制。测试覆盖了两种粒度类级抑制UnicodeInCodeTest.javaSuppressWarnings(UnicodeInCode) class Test { static final double π 3; }方法级抑制UnicodeInCodeTest.javaclass Test { SuppressWarnings(UnicodeInCode) void test() { double π 3; } }这类被抑制的区域正是主流程中suppressedRegions(state)计算出的允许区域之一见 UnicodeInCode.java与注释/字面量区域合并后共同生效。一个易踩的实现边界javac 注入的 ASCII_SUB源码 UnicodeInCode.java 里有一段看似奇怪的豁免if (c 0x1a i sourceCode.length() - 1) { // javac inserts ASCII_SUB characters at the end of the input, see: // https://github.com/google/error-prone/issues/3092 return true; }0x1AASCII_SUB即CtrlZ本身不属于可打印 ASCII正常源码里出现必然违规。但 javac 词法分析器在处理某些输入时会在文件末尾自动注入一个 ASCII_SUB 字符如果检查器不豁免它就会对所有此类输入误报。对应测试asciiSubUnicodeInCodeTest.java手工构造了源码 结尾0x1a的输入并断言整个编译任务不应产生错误诊断。这个细节提醒我们编写基于原始文本的检查器时必须考虑宿主编译器的内部行为否则会引入误报。相邻检查方向性 Unicode 字符与UnicodeInCode相邻注册的还有UnicodeDirectionalityCharacters见 BuiltInCheckerSuppliers.java。二者目标互补前者针对代码区域的所有非 ASCII 字符后者专门针对 Unicode 双向算法控制字符如 RLO、LRI 等——这类字符能颠倒代码的视觉显示顺序是另一类更隐蔽的欺骗手法。如果你的项目同时开启两者就能同时覆盖形似与隐形两类 Unicode 陷阱。测试矩阵行为一览UnicodeInCodeTest.java 使用CompilationTestHelper对检查器行为做了完整验证可归纳如下测试输入要点期望结果negative纯 ASCII 代码无诊断negativeInCommentJavadoc / 行尾注释中的π无诊断negativeInStringLiteral字符串字面量中的π无诊断negativeInCharLiteral字符字面量中的π无诊断positive标识符π报告Unicode character (\u03c0)positiveMultiCharacterGivesOneFinding标识符ππ报告Unicode character (\u03c0\u03c0)suppressibleAtClassLevel类级SuppressWarnings无诊断suppressibleAtMethodLevel方法级SuppressWarnings无诊断asciiSub源码末尾注入0x1A无诊断其中positiveMultiCharacterGivesOneFinding还验证了一个细节同一位置连续多个非 ASCII 字符会合并为一条诊断因为所有违规区间被RangeSet合并处理消息里列出全部转义字符避免刷屏。落地实践建议基于以上机制在实际项目中使用UnicodeInCode时可以参考以下几点保持默认开启该检查位于默认启用的 ERROR 级检查集合无需任何配置即可防御同形异义字类问题升级依赖后即可自动获得覆盖。规范团队约定要求所有标识符类名、方法名、变量名、枚举常量与代码区域标点一律使用 ASCII非 ASCII 字符只允许出现在注释与字符串/字符字面量中这恰好与检查器的豁免区域一致。善用报错信息错误消息以\uXXXX转义形式展示违规字符遇到难辨别的字符可直接据其码点在 Unicode 表中核对身份。抑制要克制SuppressWarnings(UnicodeInCode)支持类级与方法级但应只在确有业务需要如数学符号标识符约定时使用并在注释中说明理由。配套其他检查如需更强防护可同时关注方向性字符检查UnicodeDirectionalityCharacters两者注册位置相邻理念互补共同构成对 Unicode 欺骗手法的防线。小结UnicodeInCode以一条简单而强硬的规则——代码区域只允许可打印 ASCII 与空白字符——从根上杜绝了同形异义字等 Unicode 混淆手法的生存空间。它的实现依赖 Error Prone 对编译单元级别的文本扫描精确豁免注释与字面量并妥善处理了 javac 注入字符等边界情况配套测试完整覆盖了正例、负例、多字符合并与两级抑制场景。对任何以安全性为优先的 Java 项目而言它都是一道低成本、高收益的默认防线。赞分享静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载相关推荐Error Prone 的 NullableOptional 检查禁止对 Optional 使用 Nullable从编译期杜绝空值陷阱Error Prone 的 NullableOptional 检查禁止对 Optional 使用 Nullable从编译期杜绝空值陷阱 Optional静态分析代码质量开发工具Error Prone 的 UnicodeEscape 检查如何阻止用 Unicode 转义隐藏恶意代码并自动还原为字面字符Error Prone 的 UnicodeEscape 检查如何阻止用 Unicode 转义隐藏恶意代码并自动还原为字面字符 导读 Unicode 转义是 J静态分析代码质量开发工具Error Prone 检查 LockOnNonEnclosingClassLiteral禁止对非包围类 Class 字面量加锁Error Prone 检查 LockOnNonEnclosingClassLiteral禁止对非包围类 Class 字面量加锁 本指南深入讲解 Error静态分析代码质量开发工具上一篇3分钟搞定Koikatu HF Patch终极安装与优化指南下一篇如何高效使用ModTheSpire杀戮尖塔模组加载器的实用快速入门指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表