Skip to content

Commit ba79d50

Browse files
committed
feat(errorprone): enforce no-java.lang.Math rule at compile time
Replace the regex-based .github/workflows/math-check.yml scan with a custom ErrorProne BugChecker (ForbidJavaLangMath) in the :errorprone module. It resolves symbols on the type-attributed AST, so it catches every usage form (direct, fully-qualified, statically-imported, method references, field access) with no string/comment false positives. java.lang.StrictMath remains allowed. - add ForbidJavaLangMath BugChecker (auto-registered via @autoservice) - enable -Xep:ForbidJavaLangMath:ERROR in build.gradle - exempt the canonical x86 MathWrapper via @SuppressWarnings - delete .github/workflows/math-check.yml
1 parent 8bffa19 commit ba79d50

4 files changed

Lines changed: 121 additions & 93 deletions

File tree

.github/workflows/math-check.yml

Lines changed: 0 additions & 93 deletions
This file was deleted.

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ subprojects {
130130
errorproneArgs.addAll([
131131
'-Xep:StringCaseLocaleUsage:ERROR',
132132
'-Xep:StringCaseLocaleUsageMethodRef:ERROR',
133+
'-Xep:ForbidJavaLangMath:ERROR',
133134
])
134135
}
135136
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package errorprone;
2+
3+
import com.google.auto.service.AutoService;
4+
import com.google.errorprone.BugPattern;
5+
import com.google.errorprone.VisitorState;
6+
import com.google.errorprone.bugpatterns.BugChecker;
7+
import com.google.errorprone.matchers.Description;
8+
import com.google.errorprone.util.ASTHelpers;
9+
import com.sun.source.tree.IdentifierTree;
10+
import com.sun.source.tree.MemberReferenceTree;
11+
import com.sun.source.tree.MemberSelectTree;
12+
import com.sun.source.tree.MethodInvocationTree;
13+
import com.sun.source.tree.Tree;
14+
import com.sun.tools.javac.code.Symbol;
15+
16+
/**
17+
* Forbids any direct use of {@code java.lang.Math}.
18+
*
19+
* <p>{@code java.lang.Math} permits the JIT to use platform-specific intrinsics for some
20+
* methods (notably the transcendental and floating-point ones), so results are not
21+
* guaranteed to be bit-for-bit identical across CPUs / JVMs. In a consensus system that
22+
* non-determinism can fork the chain. All math must therefore go through
23+
* {@code org.tron.common.math.StrictMathWrapper}, which is backed by {@code java.lang.StrictMath}
24+
* and produces reproducible results.
25+
*
26+
* <p>This checker replaces the previous regex-based {@code .github/workflows/math-check.yml}
27+
* scan. It resolves symbols on the type-attributed AST, so it has no false positives from
28+
* strings/comments or unrelated classes named {@code Math}, and it catches every usage form:
29+
* <ul>
30+
* <li>direct calls: {@code Math.max(a, b)}</li>
31+
* <li>fully-qualified calls: {@code java.lang.Math.max(a, b)}</li>
32+
* <li>statically-imported calls: {@code import static java.lang.Math.max; ... max(a, b)}</li>
33+
* <li>method references: {@code Math::max}</li>
34+
* <li>field access: {@code Math.PI}, {@code Math.E}</li>
35+
* <li>statically-imported fields used bare: {@code import static java.lang.Math.PI; ... PI}</li>
36+
* <li>class literals (reflection back door): {@code Math.class}</li>
37+
* </ul>
38+
*
39+
* <p>{@code java.lang.StrictMath} itself is intentionally allowed — it is the deterministic
40+
* primitive that {@code StrictMathWrapper} and the deprecated {@code MathWrapper} are built on.
41+
* Those wrappers legitimately call {@code java.lang.Math}/{@code StrictMath} and are exempted via
42+
* {@code @SuppressWarnings("ForbidJavaLangMath")}.
43+
*/
44+
@AutoService(BugChecker.class)
45+
@BugPattern(
46+
name = "ForbidJavaLangMath",
47+
summary = "Direct use of java.lang.Math is forbidden: its results are not guaranteed to be "
48+
+ "bit-for-bit identical across platforms, which can break consensus. Use "
49+
+ "org.tron.common.math.StrictMathWrapper instead.",
50+
severity = BugPattern.SeverityLevel.ERROR
51+
)
52+
public class ForbidJavaLangMath extends BugChecker
53+
implements BugChecker.MethodInvocationTreeMatcher,
54+
BugChecker.MemberReferenceTreeMatcher,
55+
BugChecker.MemberSelectTreeMatcher,
56+
BugChecker.IdentifierTreeMatcher {
57+
58+
private static final String JAVA_LANG_MATH = "java.lang.Math";
59+
60+
@Override
61+
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
62+
return flagIfMathMember(ASTHelpers.getSymbol(tree), tree);
63+
}
64+
65+
@Override
66+
public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) {
67+
return flagIfMathMember(ASTHelpers.getSymbol(tree), tree);
68+
}
69+
70+
@Override
71+
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
72+
// Type-level references such as the class literal `Math.class` resolve to the Math
73+
// ClassSymbol rather than a member, and are a back door to java.lang.Math via reflection
74+
// (e.g. Math.class.getMethod("sin").invoke(...)). Flag them explicitly.
75+
if (tree.getIdentifier().contentEquals("class")
76+
&& isJavaLangMathClass(ASTHelpers.getSymbol(tree.getExpression()))) {
77+
return describeMatch(tree);
78+
}
79+
// Method selects (Math.max) are already reported via matchMethodInvocation /
80+
// matchMemberReference; only flag field selects (Math.PI, Math.E) here to avoid
81+
// double-reporting the same usage.
82+
Symbol sym = ASTHelpers.getSymbol(tree);
83+
if (!(sym instanceof Symbol.VarSymbol)) {
84+
return Description.NO_MATCH;
85+
}
86+
return flagIfMathMember(sym, tree);
87+
}
88+
89+
@Override
90+
public Description matchIdentifier(IdentifierTree tree, VisitorState state) {
91+
// Catches statically-imported constants referenced bare (e.g. `import static
92+
// java.lang.Math.PI; ... double c = PI;`). Statically-imported *methods* are caught by
93+
// matchMethodInvocation, so restrict this to fields to avoid double-reporting.
94+
Symbol sym = ASTHelpers.getSymbol(tree);
95+
if (!(sym instanceof Symbol.VarSymbol)) {
96+
return Description.NO_MATCH;
97+
}
98+
return flagIfMathMember(sym, tree);
99+
}
100+
101+
private static boolean isJavaLangMathClass(Symbol sym) {
102+
return sym instanceof Symbol.ClassSymbol
103+
&& ((Symbol.ClassSymbol) sym).getQualifiedName().contentEquals(JAVA_LANG_MATH);
104+
}
105+
106+
private Description flagIfMathMember(Symbol sym, Tree tree) {
107+
if (sym == null) {
108+
return Description.NO_MATCH;
109+
}
110+
Symbol.ClassSymbol enclosingClass = ASTHelpers.enclosingClass(sym);
111+
if (enclosingClass == null) {
112+
return Description.NO_MATCH;
113+
}
114+
if (enclosingClass.getQualifiedName().contentEquals(JAVA_LANG_MATH)) {
115+
return describeMatch(tree);
116+
}
117+
return Description.NO_MATCH;
118+
}
119+
}

platform/src/main/java/x86/org/tron/common/math/MathWrapper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* especially for floating-point calculations.
77
*/
88
@Deprecated
9+
@SuppressWarnings("ForbidJavaLangMath") // canonical wrapper: deliberately delegates to java.lang.Math
910
public class MathWrapper {
1011

1112
public static double pow(double a, double b) {

0 commit comments

Comments
 (0)