diff --git a/.github/workflows/math-check.yml b/.github/workflows/math-check.yml deleted file mode 100644 index a5db3351a94..00000000000 --- a/.github/workflows/math-check.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Check Math Usage - -on: - push: - branches: [ 'master', 'release_**' ] - pull_request: - branches: [ 'develop', 'release_**' ] - workflow_dispatch: - -jobs: - check-math: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - - - name: Check for java.lang.Math usage - id: check-math - shell: bash - run: | - echo "Checking for java.lang.Math usage..." - - touch math_usage.txt - - while IFS= read -r file; do - filename=$(basename "$file") - if [[ "$filename" == "StrictMathWrapper.java" || "$filename" == "MathWrapper.java" ]]; then - continue - fi - - perl -0777 -ne ' - s/"([^"\\]|\\.)*"//g; - s/'\''([^'\''\\]|\\.)*'\''//g; - s!/\*([^*]|\*[^/])*\*/!!g; - s!//[^\n]*!!g; - $hasMath = 0; - $hasMath = 1 if /^[\s]*import[\s]+java\.lang\.Math\b/m; - $hasMath = 1 if /\bjava\s*\.\s*lang\s*\.\s*Math\s*\./; - $hasMath = 1 if /(?> math_usage.txt - done < <(find . -type f -name "*.java") - - sort -u math_usage.txt -o math_usage.txt - - if [ -s math_usage.txt ]; then - echo "❌ Error: Forbidden Math usage found in the following files:" - cat math_usage.txt - echo "math_found=true" >> $GITHUB_OUTPUT - echo "Please use org.tron.common.math.StrictMathWrapper instead of direct Math usage." - else - echo "✅ No forbidden Math usage found" - echo "math_found=false" >> $GITHUB_OUTPUT - fi - - - name: Upload findings - if: steps.check-math.outputs.math_found == 'true' - uses: actions/upload-artifact@v6 - with: - name: math-usage-report - path: math_usage.txt - - - name: Create comment - if: github.event_name == 'pull_request' && steps.check-math.outputs.math_found == 'true' - uses: actions/github-script@v8 - with: - script: | - const fs = require('fs'); - const findings = fs.readFileSync('math_usage.txt', 'utf8'); - const body = `### ❌ Math Usage Detection Results - - Found forbidden usage of \`java.lang.Math\` in the following files: - - \`\`\` - ${findings} - \`\`\` - - **Please review if this usage is intended.** - > [!CAUTION] - > Note: You should use \`org.tron.common.math.StrictMathWrapper\`. - > If you need to use \`java.lang.Math\`, please provide a justification. - `; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: body - }); - - - name: Fail if Math usage found - if: steps.check-math.outputs.math_found == 'true' - run: exit 1 diff --git a/build.gradle b/build.gradle index e143ab3a947..ad71450e055 100644 --- a/build.gradle +++ b/build.gradle @@ -130,6 +130,7 @@ subprojects { errorproneArgs.addAll([ '-Xep:StringCaseLocaleUsage:ERROR', '-Xep:StringCaseLocaleUsageMethodRef:ERROR', + '-Xep:ForbidJavaLangMath:ERROR', ]) } } diff --git a/errorprone/src/main/java/errorprone/ForbidJavaLangMath.java b/errorprone/src/main/java/errorprone/ForbidJavaLangMath.java new file mode 100644 index 00000000000..e3caf53898e --- /dev/null +++ b/errorprone/src/main/java/errorprone/ForbidJavaLangMath.java @@ -0,0 +1,119 @@ +package errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Symbol; + +/** + * Forbids any direct use of {@code java.lang.Math}. + * + *
{@code java.lang.Math} permits the JIT to use platform-specific intrinsics for some + * methods (notably the transcendental and floating-point ones), so results are not + * guaranteed to be bit-for-bit identical across CPUs / JVMs. In a consensus system that + * non-determinism can fork the chain. All math must therefore go through + * {@code org.tron.common.math.StrictMathWrapper}, which is backed by {@code java.lang.StrictMath} + * and produces reproducible results. + * + *
This checker replaces the previous regex-based {@code .github/workflows/math-check.yml} + * scan. It resolves symbols on the type-attributed AST, so it has no false positives from + * strings/comments or unrelated classes named {@code Math}, and it catches every usage form: + *
{@code java.lang.StrictMath} itself is intentionally allowed — it is the deterministic + * primitive that {@code StrictMathWrapper} and the deprecated {@code MathWrapper} are built on. + * Those wrappers legitimately call {@code java.lang.Math}/{@code StrictMath} and are exempted via + * {@code @SuppressWarnings("ForbidJavaLangMath")}. + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "ForbidJavaLangMath", + summary = "Direct use of java.lang.Math is forbidden: its results are not guaranteed to be " + + "bit-for-bit identical across platforms, which can break consensus. Use " + + "org.tron.common.math.StrictMathWrapper instead.", + severity = BugPattern.SeverityLevel.ERROR +) +public class ForbidJavaLangMath extends BugChecker + implements BugChecker.MethodInvocationTreeMatcher, + BugChecker.MemberReferenceTreeMatcher, + BugChecker.MemberSelectTreeMatcher, + BugChecker.IdentifierTreeMatcher { + + private static final String JAVA_LANG_MATH = "java.lang.Math"; + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + return flagIfMathMember(ASTHelpers.getSymbol(tree), tree); + } + + @Override + public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) { + return flagIfMathMember(ASTHelpers.getSymbol(tree), tree); + } + + @Override + public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) { + // Type-level references such as the class literal `Math.class` resolve to the Math + // ClassSymbol rather than a member, and are a back door to java.lang.Math via reflection + // (e.g. Math.class.getMethod("sin").invoke(...)). Flag them explicitly. + if (tree.getIdentifier().contentEquals("class") + && isJavaLangMathClass(ASTHelpers.getSymbol(tree.getExpression()))) { + return describeMatch(tree); + } + // Method selects (Math.max) are already reported via matchMethodInvocation / + // matchMemberReference; only flag field selects (Math.PI, Math.E) here to avoid + // double-reporting the same usage. + Symbol sym = ASTHelpers.getSymbol(tree); + if (!(sym instanceof Symbol.VarSymbol)) { + return Description.NO_MATCH; + } + return flagIfMathMember(sym, tree); + } + + @Override + public Description matchIdentifier(IdentifierTree tree, VisitorState state) { + // Catches statically-imported constants referenced bare (e.g. `import static + // java.lang.Math.PI; ... double c = PI;`). Statically-imported *methods* are caught by + // matchMethodInvocation, so restrict this to fields to avoid double-reporting. + Symbol sym = ASTHelpers.getSymbol(tree); + if (!(sym instanceof Symbol.VarSymbol)) { + return Description.NO_MATCH; + } + return flagIfMathMember(sym, tree); + } + + private static boolean isJavaLangMathClass(Symbol sym) { + return sym instanceof Symbol.ClassSymbol + && ((Symbol.ClassSymbol) sym).getQualifiedName().contentEquals(JAVA_LANG_MATH); + } + + private Description flagIfMathMember(Symbol sym, Tree tree) { + if (sym == null) { + return Description.NO_MATCH; + } + Symbol.ClassSymbol enclosingClass = ASTHelpers.enclosingClass(sym); + if (enclosingClass == null) { + return Description.NO_MATCH; + } + if (enclosingClass.getQualifiedName().contentEquals(JAVA_LANG_MATH)) { + return describeMatch(tree); + } + return Description.NO_MATCH; + } +} diff --git a/platform/src/main/java/x86/org/tron/common/math/MathWrapper.java b/platform/src/main/java/x86/org/tron/common/math/MathWrapper.java index 758a0f18370..4e5d0cf6717 100644 --- a/platform/src/main/java/x86/org/tron/common/math/MathWrapper.java +++ b/platform/src/main/java/x86/org/tron/common/math/MathWrapper.java @@ -6,6 +6,7 @@ * especially for floating-point calculations. */ @Deprecated +@SuppressWarnings("ForbidJavaLangMath") // canonical wrapper: deliberately delegates to java.lang.Math public class MathWrapper { public static double pow(double a, double b) {