This is an automated email from the ASF dual-hosted git repository.

rvesse pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit c9720833ffe545f1197ccbbd1aece16f646cf1b2
Author: Rob Vesse <[email protected]>
AuthorDate: Mon Sep 28 11:46:15 2026 +0100

    Avoid unnecessary math computation
    
    Various built-in SPARQL function libraries provide access to a range of
    math computations.  However some of these can result in generating very
    large outputs that won't fit into the target type.  This commit adds a
    new MathLimits static helper with pre-computation validation functions
    that are called to check whether a given computation should be computed
    and throw an error if its output would be excessive.
---
 .../apache/jena/sparql/function/MathLimits.java    | 118 +++++++++++++++++++++
 .../jena/sparql/function/library/Math_exp.java     |   2 +
 .../jena/sparql/function/library/Math_exp10.java   |   6 +-
 .../jena/sparql/function/library/Math_pow.java     |  22 ++--
 .../function/library/leviathan/cartesian.java      |   6 ++
 .../sparql/function/library/leviathan/cube.java    |   5 +-
 .../jena/sparql/function/library/leviathan/e.java  |   2 +
 .../function/library/leviathan/factorial.java      |   4 +-
 .../sparql/function/library/leviathan/pow.java     |   5 +-
 .../function/library/leviathan/pythagoras.java     |   3 +
 .../sparql/function/library/leviathan/root.java    |   2 +
 .../jena/sparql/function/library/leviathan/sq.java |   5 +-
 .../sparql/function/library/leviathan/ten.java     |   2 +
 .../optimize/TestTransformConstantFolding.java     |   5 +
 .../org/apache/jena/sparql/expr/LibTestExpr.java   |   9 +-
 .../jena/sparql/expr/TestLeviathanFunctions.java   |  38 ++++++-
 .../function/library/TS_LibraryFunctions.java      |   1 +
 .../function/library/TestFnFunctionsNumeric.java   |  23 ++++
 .../sparql/function/library/TestMathLimits.java    |  48 +++++++++
 19 files changed, 284 insertions(+), 22 deletions(-)

diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/MathLimits.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/MathLimits.java
new file mode 100644
index 0000000000..efe497d722
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/MathLimits.java
@@ -0,0 +1,118 @@
+package org.apache.jena.sparql.function;
+
+import org.apache.jena.sparql.expr.ExprEvalException;
+
+import java.math.BigInteger;
+
+/**
+ * Static limits that control what's permitted for some math computations 
accessible via the various SPARQL function
+ * libraries
+ */
+public class MathLimits {
+
+    private MathLimits() {
+
+    }
+
+    /**
+     * Default maximum digits used for {@link #MAX_EXPONENT_DIGITS} and {@link 
#MAX_FACTORIAL_DIGITS}
+     */
+    public static final int DEFAULT_MAX_DIGITS = 64;
+    /**
+     * Configurable maximum number of digits for an exponent calculation
+     */
+    public static int MAX_EXPONENT_DIGITS = DEFAULT_MAX_DIGITS;
+    /**
+     * Configurable maximum number of digits for a factorial calculation
+     */
+    public static int MAX_FACTORIAL_DIGITS = DEFAULT_MAX_DIGITS;
+
+    /**
+     * Calculates how many digits raising {@code a} to the power of {@code b} 
would require.  If the number of digits is
+     * very large then throw an expression evaluation error to prevent 
execution.
+     *
+     * @param a A, base number to raise by the exponent
+     * @param b B, exponent to raise by
+     * @see #preValidateExponentCalculation(double, double)
+     */
+    public static void preValidateExponentCalculation(BigInteger a, BigInteger 
b) {
+        double aDbl = a.doubleValue();
+        if (infiniteOrNaN(aDbl)) {
+            throw baseOutOfRange();
+        }
+        double bDbl = b.doubleValue();
+        if (infiniteOrNaN(bDbl)) {
+            throw exponentOutOfRange();
+        }
+        preValidateExponentCalculation(aDbl, bDbl);
+    }
+
+    private static ExprEvalException tooManyExponentDigits() {
+        return new ExprEvalException("Calculating this exponent would produce 
too many digits");
+    }
+
+    private static ExprEvalException baseOutOfRange() {
+        return new ExprEvalException("Cannot calculate exponent when base is 
out of double range");
+    }
+
+    private static ExprEvalException exponentOutOfRange() {
+        return new ExprEvalException("Cannot calculate exponent when base is 
outside double range");
+    }
+
+    private static boolean infiniteOrNaN(double aDbl) {
+        return Double.isInfinite(aDbl) || Double.isNaN(aDbl);
+    }
+
+    /**
+     * Calculates how many digits raising {@code a} to the power of {@code b} 
would require.  If the number of digits is
+     * very large then throw an expression evaluation error to prevent 
execution.
+     * <p>
+     * This is based upon the following mathematical formula, for {@code a^b} 
then Number of Digits is
+     * {@code floor(b * log10(a)) + 1}.
+     * </p>
+     *
+     * @param a A, base number to raise by exponent
+     * @param b B, exponent to raise by
+     * @throws ExprEvalException Thrown if the calculation would require too 
many digits
+     */
+    public static void preValidateExponentCalculation(double a, double b) {
+        double logA = Math.log10(a);
+        if (infiniteOrNaN(logA)) {
+            throw exponentOutOfRange();
+        }
+        double numDigits = Math.floor(b * Math.log10(a)) + 1.0d;
+        if (numDigits > MAX_EXPONENT_DIGITS) {
+            throw tooManyExponentDigits();
+        }
+    }
+
+    /**
+     * Calculates the Stirling approximation for the factorial of a number and 
uses it to validate whether calculating a
+     * factorial would produce too many digits throwing an {@link 
ExprEvalException} if it would.
+     * <p>
+     * Based on Python example from <a 
href="https://www.johndcook.com/blog/2015/10/06/number-of-digits-in-n/";>Number 
of
+     * Digits in n!</a>
+     * <pre>
+     * def stirling(n):
+     *   return floor( ((n+0.5)*log(n) - n + 0.5*log(2*pi))/log(10) ) + 1
+     * </pre>
+     * </p>
+     * <p>
+     * Note this method assumes the caller has already validated that the 
given input is non-negative.
+     * </p>
+     *
+     * @param n Number to compute the factorial of
+     * @throws ExprEvalException Thrown if calculating the factorial would 
produce too many digits
+     */
+    public static void preValidateFactorial(BigInteger n) {
+        double nDbl = n.doubleValue();
+        if (infiniteOrNaN(nDbl)) {
+            throw new ExprEvalException("Cannot calculate factorial when n is 
outside double range");
+        }
+        double nAdj = nDbl + 0.5;
+        double numDigits = Math.floor(((nAdj * Math.log(nDbl)) - (nAdj * 
Math.log(2 * Math.PI))) / Math.log(10)) + 1d;
+        if (numDigits > MAX_FACTORIAL_DIGITS) {
+            throw new ExprEvalException("Calculating this factorial would 
produce too many digits");
+        }
+    }
+}
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java
index 554cf45e55..b996b65f39 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java
@@ -23,12 +23,14 @@ package org.apache.jena.sparql.function.library;
 
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.function.FunctionBase1;
+import org.apache.jena.sparql.function.MathLimits;
 
 // Returns the value of e^x.
 public class Math_exp extends FunctionBase1 {
 
     @Override
     public NodeValue exec(NodeValue v) {
+        MathLimits.preValidateExponentCalculation(Math.E, v.getDouble());
         return NodeValue.makeDouble(Math.exp(v.getDouble()));
     }
 
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp10.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp10.java
index 70f9f87389..c9270157b2 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp10.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp10.java
@@ -27,6 +27,7 @@ import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp;
 import org.apache.jena.sparql.function.FunctionBase1;
+import org.apache.jena.sparql.function.MathLimits;
 
 // Returns the value of natural log(x)
 public class Math_exp10 extends FunctionBase1 {
@@ -37,13 +38,16 @@ public class Math_exp10 extends FunctionBase1 {
         {
             case OP_INTEGER:
                 int x = v.getInteger().intValue();
-                if ( x >= 0 )
+                if ( x >= 0 ) {
+                    MathLimits.preValidateExponentCalculation(BigInteger.TEN, 
v.getInteger());
                     return NodeValue.makeInteger(BigInteger.TEN.pow(x));
+                }
                 // Anything else -> double
                 //$FALL-THROUGH$
             case OP_DECIMAL:
             case OP_FLOAT:
             case OP_DOUBLE:
+                MathLimits.preValidateExponentCalculation(10, v.getDouble());
                 return NodeValue.makeDouble(Math.pow(10, v.getDouble()));
             default:
                 throw new ARQInternalErrorException("Unrecognized numeric 
operation : "+ v);
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java
index b5d0d5761c..e549aaf5f8 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java
@@ -27,6 +27,7 @@ import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp;
 import org.apache.jena.sparql.function.FunctionBase2;
+import org.apache.jena.sparql.function.MathLimits;
 
 // math:pow($x as xs:double?, $y as xs:numeric) as xs:double?
 //    except pow(integer,+ve integer) is an integer 
@@ -34,13 +35,14 @@ public class Math_pow extends FunctionBase2 {
 
     @Override
     public NodeValue exec(NodeValue v1, NodeValue v2) {
-        switch (XSDFuncOp.classifyNumeric("pow", v1, v2))
-        {
+        switch (XSDFuncOp.classifyNumeric("pow", v1, v2)) {
             case OP_INTEGER:
                 BigInteger x = v1.getInteger();
                 int y = v2.getInteger().intValue();
-                if ( y >= 0 )
-                    return NodeValue.makeInteger( x.pow(y));
+                if (y >= 0) {
+                    MathLimits.preValidateExponentCalculation(x, 
v2.getInteger());
+                    return NodeValue.makeInteger(x.pow(y));
+                }
                 // Anything else -> double
                 //$FALL-THROUGH$
             case OP_DECIMAL:
@@ -48,15 +50,17 @@ public class Math_pow extends FunctionBase2 {
             case OP_DOUBLE:
                 double d1 = v1.getDouble();
                 double d2 = v2.getDouble();
-                if ( d1 == 1 && d2 == Double.POSITIVE_INFINITY ) {
-                    if ( v1.isInteger() )
+                if (d1 == 1 && d2 == Double.POSITIVE_INFINITY) {
+                    if (v1.isInteger()) {
                         return NodeValue.nvONE;
-                    else
+                    } else {
                         return NodeValue.makeDouble(1);
+                    }
                 }
-                return NodeValue.makeDouble( Math.pow(v1.getDouble(), 
v2.getDouble()) );
+                MathLimits.preValidateExponentCalculation(d1, d2);
+                return NodeValue.makeDouble(Math.pow(v1.getDouble(), 
v2.getDouble()));
             default:
-                throw new ARQInternalErrorException("Unrecognized numeric 
operation : "+ v1);
+                throw new ARQInternalErrorException("Unrecognized numeric 
operation : " + v1);
         }
     }
 
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cartesian.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cartesian.java
index d5dee55f6d..e63cb4f366 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cartesian.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cartesian.java
@@ -29,6 +29,7 @@ import org.apache.jena.sparql.expr.ExprEvalException;
 import org.apache.jena.sparql.expr.ExprList;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.function.FunctionBase;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class cartesian extends FunctionBase {
 
@@ -42,6 +43,8 @@ public class cartesian extends FunctionBase {
             double dX = args.get(0).getDouble() - args.get(2).getDouble();
             double dY = args.get(1).getDouble() - args.get(3).getDouble();
 
+            MathLimits.preValidateExponentCalculation(dX, 2);
+            MathLimits.preValidateExponentCalculation(dY, 2);
             return NodeValue.makeDouble(Math.sqrt(Math.pow(dX, 2) + 
Math.pow(dY, 2)));
         }
         case 6: {
@@ -49,6 +52,9 @@ public class cartesian extends FunctionBase {
             double dY = args.get(1).getDouble() - args.get(4).getDouble();
             double dZ = args.get(2).getDouble() - args.get(5).getDouble();
 
+            MathLimits.preValidateExponentCalculation(dX, 2);
+            MathLimits.preValidateExponentCalculation(dY, 2);
+            MathLimits.preValidateExponentCalculation(dZ, 2);
             return NodeValue.makeDouble(Math.sqrt(Math.pow(dX, 2) + 
Math.pow(dY, 2) + Math.pow(dZ, 2)));
         }
         default:
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cube.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cube.java
index c4aee9f2f2..136afa9d7d 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cube.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/cube.java
@@ -27,6 +27,7 @@ import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp;
 import org.apache.jena.sparql.function.FunctionBase1;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class cube extends FunctionBase1 {
 
@@ -36,13 +37,15 @@ public class cube extends FunctionBase1 {
         {
             case OP_INTEGER:
                 BigInteger i = v.getInteger();
+                MathLimits.preValidateExponentCalculation(i, 
BigInteger.valueOf(3));
                 return NodeValue.makeInteger( i.pow(3) );
             case OP_DECIMAL:
                 double dec = v.getDecimal().doubleValue();
+                MathLimits.preValidateExponentCalculation(dec, 3d);
                 return NodeValue.makeDecimal( Math.pow(dec, 3d));
             case OP_FLOAT:
-                // TODO Should cubing a float keep it a float?
             case OP_DOUBLE:
+                MathLimits.preValidateExponentCalculation(v.getDouble(), 3d);
                 return NodeValue.makeDouble( Math.pow(v.getDouble(), 3d) );
             default:
                 throw new ARQInternalErrorException("Unrecognized numeric 
operation : "+v);
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
index 66abdf252f..18ca67e3f9 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
@@ -23,11 +23,13 @@ package org.apache.jena.sparql.function.library.leviathan;
 
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.function.FunctionBase1;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class e extends FunctionBase1 {
 
     @Override
     public NodeValue exec(NodeValue v) {
+        MathLimits.preValidateExponentCalculation(Math.E, v.getDouble());
         return NodeValue.makeDouble(Math.exp(v.getDouble()));
     }
 
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/factorial.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/factorial.java
index d8692e215a..17935d498b 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/factorial.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/factorial.java
@@ -26,6 +26,7 @@ import java.math.BigInteger;
 import org.apache.jena.sparql.expr.ExprEvalException;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.function.FunctionBase1;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class factorial extends FunctionBase1 {
 
@@ -43,13 +44,14 @@ public class factorial extends FunctionBase1 {
         case 1:
             BigInteger res = i.add(BigInteger.ZERO);
             i = i.subtract(BigInteger.ONE);
+            MathLimits.preValidateFactorial(i);
             while (i.compareTo(BigInteger.ZERO) != 0) {
                 res = res.multiply(i);
                 i = i.subtract(BigInteger.ONE);
             }
             return NodeValue.makeInteger(res);
         default:
-            throw new ExprEvalException("Unexpecte comparison result");
+            throw new ExprEvalException("Unexpected comparison result");
         }
     }
 
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
index b32a4e3369..38d84a818c 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
@@ -27,6 +27,7 @@ import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp;
 import org.apache.jena.sparql.function.FunctionBase2;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class pow extends FunctionBase2 {
 
@@ -36,13 +37,15 @@ public class pow extends FunctionBase2 {
         {
             case OP_INTEGER:
                 BigInteger i = v1.getInteger();
+                MathLimits.preValidateExponentCalculation(i, v2.getInteger());
                 return NodeValue.makeInteger( 
i.pow(v2.getInteger().intValue()) );
             case OP_DECIMAL:
                 double dec = v1.getDecimal().doubleValue();
+                MathLimits.preValidateExponentCalculation(dec, v2.getDouble());
                 return NodeValue.makeDecimal( Math.pow(dec, v2.getDouble()));
             case OP_FLOAT:
-                // TODO Should raising a float to a power keep it a float?
             case OP_DOUBLE:
+                MathLimits.preValidateExponentCalculation(v1.getDouble(), 
v2.getDouble());
                 return NodeValue.makeDouble( Math.pow(v1.getDouble(), 
v2.getDouble()) );
             default:
                 throw new ARQInternalErrorException("Unrecognized numeric 
operation : "+ v1);
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pythagoras.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pythagoras.java
index da691f9538..044419ed9f 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pythagoras.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pythagoras.java
@@ -23,6 +23,7 @@ package org.apache.jena.sparql.function.library.leviathan;
 
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.function.FunctionBase2;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class pythagoras extends FunctionBase2 {
 
@@ -31,6 +32,8 @@ public class pythagoras extends FunctionBase2 {
         double a = v1.getDouble();
         double b = v2.getDouble();
 
+        MathLimits.preValidateExponentCalculation(a, 2);
+        MathLimits.preValidateExponentCalculation(b, 2);
         return NodeValue.makeDouble(Math.sqrt(Math.pow(a, 2) + Math.pow(b, 
2)));
     }
 
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/root.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/root.java
index e133217c72..71636461f6 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/root.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/root.java
@@ -23,6 +23,7 @@ package org.apache.jena.sparql.function.library.leviathan;
 
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.function.FunctionBase2;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class root extends FunctionBase2 {
 
@@ -31,6 +32,7 @@ public class root extends FunctionBase2 {
         double value = v1.getDouble();
         double root = v2.getDouble();
 
+        MathLimits.preValidateExponentCalculation(value, 1d / root);
         return NodeValue.makeDouble(Math.pow(value, 1d / root));
     }
 
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/sq.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/sq.java
index 077f623cb5..d318869112 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/sq.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/sq.java
@@ -27,6 +27,7 @@ import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp;
 import org.apache.jena.sparql.function.FunctionBase1;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class sq extends FunctionBase1 {
 
@@ -36,13 +37,15 @@ public class sq extends FunctionBase1 {
         {
             case OP_INTEGER:
                 BigInteger i = v.getInteger();
+                MathLimits.preValidateExponentCalculation(i, 
BigInteger.valueOf(2));
                 return NodeValue.makeInteger( i.pow(2) );
             case OP_DECIMAL:
                 double dec = v.getDecimal().doubleValue();
+                MathLimits.preValidateExponentCalculation(dec, 2d);
                 return NodeValue.makeDecimal( Math.pow(dec, 2d));
             case OP_FLOAT:
-                // TODO Should squaring a float keep it a float?
             case OP_DOUBLE:
+                MathLimits.preValidateExponentCalculation(v.getDouble(), 2d);
                 return NodeValue.makeDouble( Math.pow(v.getDouble(), 2d) );
             default:
                 throw new ARQInternalErrorException("Unrecognized numeric 
operation : "+v);   
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
index 9afcf11015..bf88885922 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
@@ -23,11 +23,13 @@ package org.apache.jena.sparql.function.library.leviathan;
 
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.function.FunctionBase1;
+import org.apache.jena.sparql.function.MathLimits;
 
 public class ten extends FunctionBase1 {
 
     @Override
     public NodeValue exec(NodeValue v) {
+        MathLimits.preValidateExponentCalculation(10, v.getDouble());
         return NodeValue.makeDouble(Math.pow(10d, v.getDouble()));
     }
 
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestTransformConstantFolding.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestTransformConstantFolding.java
index 5cf9044355..c77b377068 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestTransformConstantFolding.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestTransformConstantFolding.java
@@ -174,4 +174,9 @@ public class TestTransformConstantFolding {
     public void constant_fold_leftjoin_01() {
         test("(leftjoin (table unit) (table unit) (+ 1 2))", "(leftjoin (table 
unit) (table unit) (exprlist 3))", transform);
     }
+
+    @Test
+    public void constant_fold_math_beyond_limits() {
+        testNoTransform("(extend (?x (math:pow 2 2000000000)) (table unit))", 
transform);
+    }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/LibTestExpr.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/LibTestExpr.java
index a40ef65e44..ce4ad057ed 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/LibTestExpr.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/LibTestExpr.java
@@ -21,9 +21,6 @@
 
 package org.apache.jena.sparql.expr;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
 import java.util.function.Predicate;
 
 import org.apache.jena.graph.Node;
@@ -39,6 +36,8 @@ import org.apache.jena.sparql.util.Context;
 import org.apache.jena.sparql.util.ExprUtils;
 import org.apache.jena.sparql.util.NodeFactoryExtra;
 
+import static org.junit.jupiter.api.Assertions.*;
+
 public class LibTestExpr {
 
     private static PrefixMapping pmap = new PrefixMappingImpl();
@@ -169,8 +168,8 @@ public class LibTestExpr {
     }
 
 
-    public static void testError(String exprString) {
+    public static <T extends Exception> T testError(String exprString, 
Class<T> expectedException) {
         Expr expr = ExprUtils.parse(exprString, pmap);
-        expr.eval(null, new FunctionEnvBase());
+        return assertThrows(expectedException, () -> expr.eval(null, new 
FunctionEnvBase()));
     }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
index 45161d3fd3..e0efa3665c 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
@@ -24,14 +24,21 @@ package org.apache.jena.sparql.expr;
 import static org.apache.jena.sparql.expr.LibTestExpr.test;
 import static org.apache.jena.sparql.expr.LibTestExpr.testDouble;
 import static org.apache.jena.sparql.expr.LibTestExpr.testError;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import org.apache.jena.sparql.util.NodeFactoryExtra;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
 
 public class TestLeviathanFunctions {
 
@@ -116,12 +123,12 @@ public class TestLeviathanFunctions {
 
     @Test
     public void factorial_05() {
-        assertThrows(ExprEvalException.class, 
()->testError("lfn:factorial(-1)"));
+        testError("lfn:factorial(-1)", ExprEvalException.class);
     }
 
     @Test
     public void factorial_06() {
-        assertThrows(ExprEvalException.class, 
()->testError("lfn:factorial(5.4)"));
+        testError("lfn:factorial(5.4)", ExprEvalException.class);
     }
 
     @Test
@@ -230,4 +237,29 @@ public class TestLeviathanFunctions {
     public void acos_01() {
         
testDouble("lfn:radians-to-degrees(lfn:cos-1(lfn:cos(lfn:degrees-to-radians(60))))",
 "60", DELTA);
     }
+
+    public static Stream<Arguments> outOfRangeInputs() {
+        return Stream.of(Arguments.of("lfn:factorial(2000000000)"),
+                         Arguments.of("lfn:pow(100, 100)"),
+                         Arguments.of("lfn:sq(100.0e100)"),
+                         Arguments.of("lfn:cube(100.0e100)"),
+                         Arguments.of("lfn:ten(2000000000)"),
+                         Arguments.of("lfn:cartesian(0, 0, 1, 2000000000)"),
+                         Arguments.of("lfn:cartesian(0, 0, 2000000000, 1)"),
+                         Arguments.of("lfn:cartesian(0, 0, 0, 1, 1, 
2000000000)"),
+                         Arguments.of("lfn:cartesian(0, 0, 0, 1, 2000000000, 
1)"),
+                         Arguments.of("lfn:cartesian(0, 0, 0, 2000000000, 1, 
1)"),
+                         Arguments.of("lfn:pythagoras(2.0e128, 2)"),
+                         Arguments.of("lfn:root(2" + StringUtils.repeat('0', 
64) + ", 1)"),
+                         Arguments.of("lfn:e(2000000000)")
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("outOfRangeInputs")
+    public void givenOutOfRangeInputsInExpr_whenExecuting_thenErrors(String 
expr) {
+        ExprEvalException error = testError(expr, ExprEvalException.class);
+        assertNotNull(error);
+        assertTrue(Strings.CI.containsAny(error.getMessage(), "too many 
digits", "outside double range"));
+    }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TS_LibraryFunctions.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TS_LibraryFunctions.java
index 75bbe27a64..b6eb8c7a12 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TS_LibraryFunctions.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TS_LibraryFunctions.java
@@ -38,6 +38,7 @@ import org.apache.jena.sparql.expr.NodeValue;
     , TestFnFunctionsOther.class
     , TestFnFunctionsFormat.class
     , TestFnFunctionsCollation.class
+    , TestMathLimits.class
 })
 public class TS_LibraryFunctions {
     // Expected warnings off.
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TestFnFunctionsNumeric.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TestFnFunctionsNumeric.java
index 5c08e9f8e4..821d512daf 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TestFnFunctionsNumeric.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TestFnFunctionsNumeric.java
@@ -22,11 +22,18 @@
 package org.apache.jena.sparql.function.library;
 
 import static org.apache.jena.sparql.expr.LibTestExpr.test;
+import static org.apache.jena.sparql.expr.LibTestExpr.testError;
 
+import org.apache.jena.sparql.expr.ExprEvalException;
 import org.junit.jupiter.api.Test;
 
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sys.JenaSystem;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
 
 public class TestFnFunctionsNumeric {
 
@@ -54,4 +61,20 @@ public class TestFnFunctionsNumeric {
     @Test public void exprRoundHalfEven_07()    { 
test("fn:round-half-to-even(35612.25, -2)",     NodeValue.makeDecimal(35600)); }
     // counter-intuitive -- would fail if float/double not translated to 
decimal
     @Test public void exprRoundHalfEven_08()    { 
test("fn:round-half-to-even('150.015'^^xsd:float, 2)",     
NodeValue.makeFloat((float)150.01)); }
+
+
+    public static Stream<Arguments> outOfRangeInputs() {
+        return Stream.of(Arguments.of("math:pow(2,2000000000)"),
+                         Arguments.of("math:pow(2000000000, 16)"),
+                         Arguments.of("math:pow(100,100)"),
+                         Arguments.of("math:pow(2, 2.0e16)"),
+                         Arguments.of("math:exp10(2000000000)"),
+                         Arguments.of("math:exp(2000000000)"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("outOfRangeInputs")
+    public void givenOutOfRangeInputsInExpr_whenExecuting_thenError(String 
expr) {
+        testError(expr, ExprEvalException.class);
+    }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TestMathLimits.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TestMathLimits.java
new file mode 100644
index 0000000000..9b058aa6a9
--- /dev/null
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/function/library/TestMathLimits.java
@@ -0,0 +1,48 @@
+package org.apache.jena.sparql.function.library;
+
+import org.apache.jena.sparql.expr.ExprEvalException;
+import org.apache.jena.sparql.function.MathLimits;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.math.BigInteger;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestMathLimits {
+
+    public static Stream<Arguments> bigIntegerExponents() {
+        return Stream.of(Arguments.of(BigInteger.valueOf(2), 
BigInteger.valueOf(16), true),
+                         Arguments.of(BigInteger.valueOf(100), 
BigInteger.valueOf(100), false),
+                         Arguments.of(BigInteger.valueOf(2), 
BigInteger.valueOf(2_000_000_000), false),
+                         Arguments.of(BigInteger.valueOf(2_000_000_000), 
BigInteger.valueOf(16), false));
+    }
+
+    @ParameterizedTest
+    @MethodSource("bigIntegerExponents")
+    public void 
givenBigIntegers_whenPreValidatingExponent_thenAsExpected(BigInteger a, 
BigInteger b, boolean valid) {
+        if (valid) {
+            MathLimits.preValidateExponentCalculation(a, b);
+        } else {
+            assertThrows(ExprEvalException.class, () -> 
MathLimits.preValidateExponentCalculation(a, b));
+        }
+    }
+
+    public static Stream<Arguments> factorials() {
+        return Stream.of(Arguments.of(BigInteger.valueOf(10), true),
+                         Arguments.of(BigInteger.valueOf(63), true),
+                         Arguments.of(BigInteger.valueOf(2_000_000_000), 
false));
+    }
+
+    @ParameterizedTest
+    @MethodSource("factorials")
+    public void 
givenFactorialInputs_whenPreValidating_thenAsExpected(BigInteger n, boolean 
valid) {
+        if (valid) {
+            MathLimits.preValidateFactorial(n);
+        } else {
+            assertThrows(ExprEvalException.class, () -> 
MathLimits.preValidateFactorial(n));
+        }
+    }
+}

Reply via email to