Repository navigation
Code Analysis Java Unicode
Tu Shaokun edited this page Nov 6, 2026
·
2 revisions
Guava v33.4.8 的 JRE、Android 两个生产源码目录中,公开 Labs bf0a29e7 各报告七份文件需要 Unicode 转义预处理。受影响的文件包括 CharMatcher、Stopwatch、Cut、GeneralRange、InternetDomainName、Chars 和 XmlEscapers。例如 Stopwatch 只在字符串中用转义表示微秒单位;Chars 的两处标记位于 Javadoc,前面各有两个原始反斜线。两种写法都让原版跳过了整份文件。Stopwatch · Chars
Java 在词法分析前处理 Unicode 转义,注释中的换行转义也能露出一条导入。恢复覆盖需要先判断转义是否生效、是否会改变 token 边界,再确认它所在的源码范围。用 JDK 17 编译了字面量、双反斜线、三反斜线、注释结束符、引号、反斜线和标识符对照。另一个注释里的不完整转义被 JDK 判为语法错误。Java 语言规范
本地候选 9dc6bea08 直接基于 Labs。它利用已有解析器提供的字符串、字符、文本块和注释范围,只接纳其中保留词法边界的转义,原始源码继续用于定位。换行、引号、注释结束符、反斜线、标识符转义和不完整转义保留诊断。Java 提取修订号升至 2,让基线对照能识别分析器自身的变化。
| 单独分析的目录 | 源文件清单 | Labs 跳过 | 候选跳过 | Labs 函数记录 | 候选函数记录 |
|---|---|---|---|---|---|
guava/src |
615 | 7 | 0 | 11,774 | 12,126 |
android/guava/src |
614 | 7 | 0 | 11,492 | 11,843 |
- 对恢复的 14 份原始文件,JDK 独立读出了 162 条显式导入和 701 个带方法体的方法。逐条核对导入位置、方法名及起止行,全部与候选一致。候选还记录了两份
XmlEscapers的静态初始化块。这里验证的是源码声明与位置。 - 对原来已解析的目标和原有方法记录逐条复核,通过对照。两个目录共同扫描时,前一案例中的六处同名导入仍标为未解析。SLF4J 对照的事实与原版相同,差异只在提取修订号及其快照标识。
- 两个新增回归在父版本失败,在候选通过。230 项模块测试通过。重复构建得到相同 ZIP;解包 CLI 对两套 Guava 源码共同扫描的原始结果与源码版逐字节一致。另用含换行、引号、注释结束符和不完整转义的文件检查失败隔离,其他文件仍能提供有效事实。
用 JDK 17 从原始 Guava 源码重新读出这 863 条声明位置
准备 Git、JDK 17 和 Python 3。以下只运行编译器的解析步骤,记录原始源码的声明范围。输出分别为 JRE 的 81 条导入、351 个方法,以及 Android 的 81 条导入、350 个方法,逐条结果保存在临时目录的 jdk.tsv。Labs 的三种扫描范围可按前一页复现。Guava 源码范围对照
set -eu
w=$(mktemp -d)
git init -q "$w/guava"
git -C "$w/guava" fetch -q --no-tags --depth=1 https://github.com/google/guava.git f06690fa3e874f65515e8fd338a74d636e2c792f
git -C "$w/guava" checkout -q --detach FETCH_HEAD
cat > "$w/Declarations.java" <<'JAVA'
import java.nio.file.*;
import java.util.*;
import javax.tools.*;
import com.sun.source.tree.*;
import com.sun.source.util.*;
class Declarations {
public static void main(String[] args) throws Exception {
Path root = Path.of(args[0]).toRealPath();
List<Path> paths = Arrays.stream(args).skip(1).map(root::resolve).toList();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
try (StandardJavaFileManager files = compiler.getStandardFileManager(diagnostics, null, null)) {
JavacTask task = (JavacTask)compiler.getTask(null, files, diagnostics,
List.of("-proc:none", "-encoding", "UTF-8"), null, files.getJavaFileObjectsFromPaths(paths));
SourcePositions ranges = Trees.instance(task).getSourcePositions();
for (CompilationUnitTree unit : task.parse()) {
Path path = root.relativize(Path.of(unit.getSourceFile().toUri()).toRealPath());
new TreePathScanner<Void, Void>() {
void emit(String kind, Tree tree, String name) {
long first = ranges.getStartPosition(unit, tree);
long last = ranges.getEndPosition(unit, tree) - 1;
System.out.printf("%s\t%s\t%d\t%d\t%s%n", kind, path,
unit.getLineMap().getLineNumber(first), unit.getLineMap().getLineNumber(last), name);
}
public Void visitImport(ImportTree node, Void p) {
emit("import", node, node.getQualifiedIdentifier().toString());
return super.visitImport(node, p);
}
public Void visitMethod(MethodTree node, Void p) {
if (node.getBody() != null) {
String name = node.getName().toString();
if (name.equals("<init>")) name = ((ClassTree)getCurrentPath().getParentPath().getLeaf()).getSimpleName().toString();
emit("method", node, name);
}
return super.visitMethod(node, p);
}
}.scan(unit, null);
}
}
for (var diagnostic : diagnostics.getDiagnostics()) {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) throw new IllegalStateException(diagnostic.toString());
}
}
}
JAVA
python3 - "$w" <<'PY'
from collections import Counter
from pathlib import Path
import subprocess, sys
w = Path(sys.argv[1])
files = [
'base/CharMatcher.java', 'base/Stopwatch.java',
'collect/Cut.java', 'collect/GeneralRange.java',
'net/InternetDomainName.java', 'primitives/Chars.java', 'xml/XmlEscapers.java',
]
paths = [root + '/com/google/common/' + file
for root in ('guava/src', 'android/guava/src') for file in files]
rows = subprocess.check_output(['java', str(w / 'Declarations.java'), str(w / 'guava'), *paths], text=True)
(w / 'jdk.tsv').write_text(rows)
counts = Counter()
for row in rows.splitlines():
kind, file, first, last, name = row.split('\t')
variant = 'Android' if file.startswith('android/') else 'JRE'
counts[variant, kind] += 1
for variant in ('JRE', 'Android'):
print(variant, counts[variant, 'import'], 'imports,', counts[variant, 'method'], 'methods')
print('Per-declaration source locations', w / 'jdk.tsv')
PY