You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sl...@apache.org on 2022/09/29 17:19:35 UTC

[daffodil] branch main updated: Handle validation of float/double with INF/NaN values

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 8a6898dd3 Handle validation of float/double with INF/NaN values
8a6898dd3 is described below

commit 8a6898dd3a0cfaa50ae53ffc833d97b48fbb3827
Author: Steve Lawrence <sl...@apache.org>
AuthorDate: Wed Sep 28 07:26:39 2022 -0400

    Handle validation of float/double with INF/NaN values
    
    Currently, min/max/Inclusive/Exclusive restriction facets are converted
    to BigDecimal during compilation, and parsed values are converted to
    BigDecimal to compare against those values when validation is enabled.
    However, float and double types can parse to infinity or not-a-number,
    which cannot be converted to a BigDecimal and causes an abort.
    
    To fix this, when checking min/max/Inclusive/Exclusive restrictions we
    first check if the data is a float or a double, and if so we convert the
    facet BigDecimal value to a float or double, and use the Float/Double
    compareTo function which handles Inf/NaN appropriately.
    
    DAFFODIL-2720
---
 .../apache/daffodil/processors/RuntimeData.scala   |  42 +++-
 .../section02/validation_errors/Validation.tdml    | 273 +++++++++++++++++++++
 .../validation_errors/TestValidationErr.scala      |  18 ++
 3 files changed, 321 insertions(+), 12 deletions(-)

diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
index 36611294f..005979e7a 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
@@ -17,6 +17,8 @@
 
 package org.apache.daffodil.processors
 
+import java.lang.{ Double => JDouble, Float => JFloat }
+
 import scala.xml.NamespaceBinding
 import org.apache.daffodil.Implicits.ImplicitsSuppressUnusedImportWarning
 import org.apache.daffodil.dpath.NodeInfo
@@ -428,27 +430,43 @@ final class SimpleTypeRuntimeData(
   }
 
   private def checkMinInc(diNode: DISimple, minValue: java.math.BigDecimal, primType: PrimType, e: ThrowsSDE): Boolean = {
-    val bdData = diNode.dataValueAsBigDecimal
-    val isDataGreaterThanEqToMinInc = bdData.compareTo(minValue) >= 0
-    isDataGreaterThanEqToMinInc
+    // we must handle float and double separately because diNode.dataValue
+    // could be Inf/Nan, which cannot be converted to BigDecimal
+    diNode.dataValue.getAnyRef match {
+      case f: JFloat => f.compareTo(minValue.floatValue) >= 0
+      case d: JDouble => d.compareTo(minValue.doubleValue) >= 0
+      case _ => diNode.dataValueAsBigDecimal.compareTo(minValue) >= 0
+    }
   }
 
   private def checkMinExc(diNode: DISimple, minValue: java.math.BigDecimal, primType: PrimType, e: ThrowsSDE): Boolean = {
-    val bdData = diNode.dataValueAsBigDecimal
-    val isDataGreaterThanEqToMinExc = bdData.compareTo(minValue) > 0
-    isDataGreaterThanEqToMinExc
+    // we must handle float and double separately because diNode.dataValue
+    // could be Inf/Nan, which cannot be converted to BigDecimal
+    diNode.dataValue.getAnyRef match {
+      case f: JFloat => f.compareTo(minValue.floatValue) > 0
+      case d: JDouble => d.compareTo(minValue.doubleValue) > 0
+      case _ => diNode.dataValueAsBigDecimal.compareTo(minValue) > 0
+    }
   }
 
   private def checkMaxInc(diNode: DISimple, maxValue: java.math.BigDecimal, primType: PrimType, e: ThrowsSDE): Boolean = {
-    val bdData = diNode.dataValueAsBigDecimal
-    val isDataLessThanEqToMaxInc = bdData.compareTo(maxValue) <= 0
-    isDataLessThanEqToMaxInc
+    // we must handle float and double separately because diNode.dataValue
+    // could be Inf/Nan, which cannot be converted to BigDecimal
+    diNode.dataValue.getAnyRef match {
+      case f: JFloat => f.compareTo(maxValue.floatValue) <= 0
+      case d: JDouble => d.compareTo(maxValue.doubleValue) <= 0
+      case _ => diNode.dataValueAsBigDecimal.compareTo(maxValue) <= 0
+    }
   }
 
   private def checkMaxExc(diNode: DISimple, maxValue: java.math.BigDecimal, primType: PrimType, e: ThrowsSDE): Boolean = {
-    val bdData = diNode.dataValueAsBigDecimal
-    val isDataLessThanMaxExc = bdData.compareTo(maxValue) < 0
-    isDataLessThanMaxExc
+    // we must handle float and double separately because diNode.dataValue
+    // could be Inf/Nan, which cannot be converted to BigDecimal
+    diNode.dataValue.getAnyRef match {
+      case f: JFloat => f.compareTo(maxValue.floatValue) < 0
+      case d: JDouble => d.compareTo(maxValue.doubleValue) < 0
+      case _ => diNode.dataValueAsBigDecimal.compareTo(maxValue) < 0
+    }
   }
 
   private def checkTotalDigits(diNode: DISimple, digits: Long): Boolean = {
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section02/validation_errors/Validation.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section02/validation_errors/Validation.tdml
index 292a96a6c..b37b6cb37 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section02/validation_errors/Validation.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section02/validation_errors/Validation.tdml
@@ -579,6 +579,42 @@
         <xs:totalDigits value="2" />
       </xs:restriction>
     </xs:simpleType>
+
+    <xs:element name="floatExclusive" dfdl:lengthKind="delimited">
+      <xs:simpleType>
+        <xs:restriction base="xs:float">
+          <xs:minExclusive value="-5" />
+          <xs:maxExclusive value="5" />
+        </xs:restriction>
+      </xs:simpleType>
+    </xs:element>
+
+    <xs:element name="floatInclusive" dfdl:lengthKind="delimited">
+      <xs:simpleType>
+        <xs:restriction base="xs:float">
+          <xs:minInclusive value="-5" />
+          <xs:maxInclusive value="5" />
+        </xs:restriction>
+      </xs:simpleType>
+    </xs:element>
+
+    <xs:element name="doubleExclusive" dfdl:lengthKind="delimited">
+      <xs:simpleType>
+        <xs:restriction base="xs:double">
+          <xs:minExclusive value="-5" />
+          <xs:maxExclusive value="5" />
+        </xs:restriction>
+      </xs:simpleType>
+    </xs:element>
+
+    <xs:element name="doubleInclusive" dfdl:lengthKind="delimited">
+      <xs:simpleType>
+        <xs:restriction base="xs:double">
+          <xs:minInclusive value="-5" />
+          <xs:maxInclusive value="5" />
+        </xs:restriction>
+      </xs:simpleType>
+    </xs:element>
     
   </tdml:defineSchema>
   
@@ -1884,5 +1920,242 @@
 		</tdml:validationErrors>
 
   </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatExclusiveValid"
+    root="floatExclusive" model="TestFacets"
+    validation="on">
+
+    <tdml:document>1.23</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatExclusive>1.23</floatExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatExclusiveInf"
+    root="floatExclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatExclusive>INF</floatExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxExclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatExclusiveNegInf"
+    root="floatExclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>-Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatExclusive>-INF</floatExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>minExclusive</tdml:error>
+      <tdml:error>-5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatExclusiveNaN"
+    root="floatExclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>NaN</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatExclusive>NaN</floatExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxExclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatInclusiveValid"
+    root="floatInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>1.23</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatInclusive>1.23</floatInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatInclusiveInf"
+    root="floatInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatInclusive>INF</floatInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxInclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatInclusiveNegInf"
+    root="floatInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>-Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatInclusive>-INF</floatInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>minInclusive</tdml:error>
+      <tdml:error>-5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="floatInclusiveNaN"
+    root="floatInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>NaN</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <floatInclusive>NaN</floatInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxInclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleExclusiveValid"
+    root="doubleExclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>1.23</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleExclusive>1.23</doubleExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleExclusiveInf"
+    root="doubleExclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleExclusive>INF</doubleExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxExclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleExclusiveNegInf"
+    root="doubleExclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>-Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleExclusive>-INF</doubleExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>minExclusive</tdml:error>
+      <tdml:error>-5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleExclusiveNaN"
+    root="doubleExclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>NaN</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleExclusive>NaN</doubleExclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxExclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleInclusiveValid"
+    root="doubleInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>1.23</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleInclusive>1.23</doubleInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleInclusiveInf"
+    root="doubleInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleInclusive>INF</doubleInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxInclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleInclusiveNegInf"
+    root="doubleInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>-Inf</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleInclusive>-INF</doubleInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>minInclusive</tdml:error>
+      <tdml:error>-5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="doubleInclusiveNaN"
+    root="doubleInclusive" model="TestFacets"
+    validation="on">
+    <tdml:document>NaN</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <doubleInclusive>NaN</doubleInclusive>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:validationErrors>
+      <tdml:error>Validation Error</tdml:error>
+      <tdml:error>maxInclusive</tdml:error>
+      <tdml:error>5.0</tdml:error>
+    </tdml:validationErrors>
+  </tdml:parserTestCase>
   
 </tdml:testSuite>
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section02/validation_errors/TestValidationErr.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section02/validation_errors/TestValidationErr.scala
index 4ce28df1d..4604aec87 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section02/validation_errors/TestValidationErr.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section02/validation_errors/TestValidationErr.scala
@@ -89,4 +89,22 @@ class TestValidationErr {
   @Test def test_validation_inputValueCalc_01(): Unit = { runner.runOneTest("validation_inputValueCalc_01") }
   @Test def test_validation_inputValueCalc_02(): Unit = { runner.runOneTest("validation_inputValueCalc_02") }
   @Test def test_validation_inputValueCalc_03(): Unit = { runner.runOneTest("validation_inputValueCalc_03") }
+
+  @Test def test_floatExclusiveValid(): Unit = { runner.runOneTest("floatExclusiveValid") }
+  @Test def test_floatExclusiveInf(): Unit = { runner.runOneTest("floatExclusiveInf") }
+  @Test def test_floatExclusiveNegInf(): Unit = { runner.runOneTest("floatExclusiveNegInf") }
+  @Test def test_floatExclusiveNaN(): Unit = { runner.runOneTest("floatExclusiveNaN") }
+  @Test def test_floatInclusiveValid(): Unit = { runner.runOneTest("floatInclusiveValid") }
+  @Test def test_floatInclusiveInf(): Unit = { runner.runOneTest("floatInclusiveInf") }
+  @Test def test_floatInclusiveNegInf(): Unit = { runner.runOneTest("floatInclusiveNegInf") }
+  @Test def test_floatInclusiveNaN(): Unit = { runner.runOneTest("floatInclusiveNaN") }
+
+  @Test def test_doubleExclusiveValid(): Unit = { runner.runOneTest("doubleExclusiveValid") }
+  @Test def test_doubleExclusiveInf(): Unit = { runner.runOneTest("doubleExclusiveInf") }
+  @Test def test_doubleExclusiveNegInf(): Unit = { runner.runOneTest("doubleExclusiveNegInf") }
+  @Test def test_doubleExclusiveNaN(): Unit = { runner.runOneTest("doubleExclusiveNaN") }
+  @Test def test_doubleInclusiveValid(): Unit = { runner.runOneTest("doubleInclusiveValid") }
+  @Test def test_doubleInclusiveInf(): Unit = { runner.runOneTest("doubleInclusiveInf") }
+  @Test def test_doubleInclusiveNegInf(): Unit = { runner.runOneTest("doubleInclusiveNegInf") }
+  @Test def test_doubleInclusiveNaN(): Unit = { runner.runOneTest("doubleInclusiveNaN") }
 }