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 d0d80b8f9 Warn when expression cannot be compiled because of no context
d0d80b8f9 is described below

commit d0d80b8f968e1d8a37fe2c0eb1185e2fc57a2a5e
Author: Steve Lawrence <[email protected]>
AuthorDate: Wed Jun 22 14:01:36 2022 -0400

    Warn when expression cannot be compiled because of no context
    
    In some cases it is possible for an expression to exist without any
    compilation context needed for paths, such as when in a global group or
    complex type that is never referenced. Daffodil still attempts to
    compile all expressions for correctness, but it cannot do so in these
    cases.
    
    When this is detected, we provide a warning that compilation is skipped
    for this expression. Even though the expression is never used, the
    Daffodil codebase still expects to have a CompiledExpression in these
    cases. To support this without major changes, this creates a new
    RuntimeAbortOp expression recipe, which causes the expression to be
    treated as non-constant during compilation but leads to an error if ever
    evaluated at runtime.
    
    DAFFODIL-2654
---
 .../daffodil/dpath/DFDLExpressionParser.scala      | 10 ++++-
 .../org/apache/daffodil/dpath/Expression.scala     | 16 ++++++++
 .../resources/org/apache/daffodil/xsd/dafext.xsd   |  1 +
 .../org/apache/daffodil/dpath/DPathRuntime.scala   | 25 ++++++++++++
 .../section23/dfdl_expressions/expressions2.tdml   | 17 ++++++++
 .../expressions_unused_path_no_context.dfdl.xsd    | 47 ++++++++++++++++++++++
 .../dfdl_expressions/TestDFDLExpressions.scala     |  2 +
 7 files changed, 117 insertions(+), 1 deletion(-)

diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
index 85649a2df..d48f89e66 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
@@ -24,6 +24,7 @@ import scala.util.parsing.combinator.RegexParsers
 import scala.util.parsing.input.CharSequenceReader
 import scala.xml.NamespaceBinding
 
+import org.apache.daffodil.api.WarnID
 import org.apache.daffodil.dsom.CompiledExpression
 import org.apache.daffodil.dsom.ConstantExpression
 import org.apache.daffodil.dsom.DPathCompileInfo
@@ -57,7 +58,14 @@ class DFDLPathExpressionParser[T <: AnyRef](
   def compile(expr: String): CompiledExpression[T] = {
     val tree = getExpressionTree(expr)
 
-    val recipe = tree.compiledDPath // if we cannot get one this will fail by 
throwing out of here.
+    val recipe = try {
+      tree.compiledDPath // if we cannot get one this will fail by throwing 
out of here.
+    } catch {
+      case e: PathExpressionNoContextError => {
+        host.SDW(WarnID.ExpressionCompilationSkipped, s"Expression compilation 
skipped due to path expression in unreferenced group or complex type: $expr")
+        new CompiledDPath(RuntimeAbortOp(expr))
+      }
+    }
 
     val value = recipe.runExpressionForConstant(context.schemaFileLocation, 
context, host.tunable)
     val res: CompiledExpression[T] = value.getOptionAnyRef match {
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala 
b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala
index 7f437b055..4199fb32a 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala
@@ -1142,6 +1142,12 @@ case class Up2(s: String, predArg: 
Option[PredicateExpression])
 
 }
 
+/**
+ * Exception that could be thrown by a NamedStep if it is determined that 
there is not
+ * enough context to evaluate the step.
+ */
+class PathExpressionNoContextError extends ThinException
+
 case class NamedStep(s: String, predArg: Option[PredicateExpression])
   extends DownStepExpression(s, predArg) {
 
@@ -1228,6 +1234,16 @@ case class NamedStep(s: String, predArg: 
Option[PredicateExpression])
           val nc = compileInfo.elementCompileInfos.map {
             _.findNamedChild(stepQName, this) // will SDE on not found.
           }
+
+          // If the Seq of named children is empty, that means the 
elementCompileInfos Seq
+          // must have been empty. This only happens when the context of this 
path
+          // expression is unknown. An example of this is a path expression in 
a global
+          // group where the group is never referenced. Because it is not 
referenced we
+          // have no element context, and so cannot evaluate the path for 
correctness. In
+          // cases like this, we throw an exception that is caught elsewere to 
handle this
+          // issue
+          if (nc.isEmpty) throw new PathExpressionNoContextError()
+
           nc
         }
       } else {
diff --git 
a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd 
b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
index 55258eb9b..806f6a8f5 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
@@ -629,6 +629,7 @@
           <xs:enumeration value="emptyElementParsePolicyError" />
           <xs:enumeration value="encodingErrorPolicyError" />
           <xs:enumeration value="escapeSchemeRefUndefined" />
+          <xs:enumeration value="expressionCompilationSkipped" />
           <xs:enumeration value="facetExplicitLengthOutOfRange" />
           <xs:enumeration value="floatingError" />
           <xs:enumeration value="ignoreImport" />
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
index f856b821d..7d9e803ad 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
@@ -311,3 +311,28 @@ abstract class Converter extends RecipeOp {
 trait ToString extends Converter {
   override def computeValue(a: DataValuePrimitive, dstate: DState): 
DataValueString = a.getAnyRef.toString
 }
+
+
+/**
+ * In some cases, expressions that are never used lack context and so cannot 
be compiled.
+ * An example of this is when a schema has an unused global complex type or 
group
+ * definition with an expression and compileAllTopLevel is enabled. Compiling 
such
+ * expressions requires knowledge about where the complex type or group is 
used. But if
+ * they are never used then we don't have that context and can't correctly 
compile the
+ * expression.
+ *
+ * Fortunately, because the global complex types/group is never used it it 
safe to ignore
+ * the expression. Unfortunately, parts of Daffodil code still require a valid
+ * CompiledExpression. In these cases, we can use this RuntimeAbort recipe as 
the recipe
+ * of the the CompiledExpression. This recipe throws an IllegalStateException 
during
+ * constant folding so that the expression is treated as non-constant and 
makes the rest
+ * of Daffodil happy, but throws an invariant if it is ever evaluated at 
runtime.
+ */
+case class RuntimeAbortOp(expr: String) extends RecipeOp {
+  override def run(dstate: DState): Unit = {
+    dstate match {
+      case _: DStateForConstantFolding => throw new 
java.lang.IllegalStateException
+      case _ => Assert.invariantFailed(s"Expression should not have been 
evaluated during runtime: $expr")
+    }
+  }
+}
diff --git 
a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions2.tdml
 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions2.tdml
index 46ff6c7cb..6aea266dd 100644
--- 
a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions2.tdml
+++ 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions2.tdml
@@ -260,5 +260,22 @@
     </tdml:infoset>
 
   </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="unused_path_no_context_01" 
model="expressions_unused_path_no_context.dfdl.xsd"
+    description="Section 06 - DFDL Expressions - DFDL-6-083R" root="e1">
+    <tdml:document>
+      <tdml:documentPart type="text">1</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <e1>1</e1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:warnings>
+      <tdml:warning>Expression compilation skipped</tdml:warning>
+      <tdml:warning>value1</tdml:warning>
+      <tdml:warning>value2</tdml:warning>
+    </tdml:warnings>
+  </tdml:parserTestCase>
  
 </tdml:testSuite>
diff --git 
a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions_unused_path_no_context.dfdl.xsd
 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions_unused_path_no_context.dfdl.xsd
new file mode 100644
index 000000000..b685f89b3
--- /dev/null
+++ 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions_unused_path_no_context.dfdl.xsd
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<xs:schema
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/";
+  xmlns:xs="http://www.w3.org/2001/XMLSchema";>
+
+  <xs:include 
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+
+  <xs:annotation>
+    <xs:appinfo source="http://www.ogf.org/dfdl/";>
+      <dfdl:format ref="GeneralFormat" />
+    </xs:appinfo>
+  </xs:annotation>
+
+  <xs:element name="e1" type="xs:int" dfdl:lengthKind="explicit" 
dfdl:length="1" />
+
+  <xs:group name="unused_group">
+    <xs:sequence>
+      <xs:element name="value1" type="xs:string" dfdl:lengthKind="explicit" 
dfdl:length="1" />
+      <xs:sequence dfdl:terminator="{ ./value1 }" />
+    </xs:sequence>
+  </xs:group>
+
+  <xs:complexType name="unused_complex_type">
+    <xs:sequence>
+      <xs:element name="value2" type="xs:string" dfdl:lengthKind="explicit" 
dfdl:length="1" />
+      <xs:sequence dfdl:terminator="{ ./value2 }"/>
+    </xs:sequence>
+  </xs:complexType>
+
+</xs:schema>
diff --git 
a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
 
b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
index f10f77e15..fa0b2da0a 100644
--- 
a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
+++ 
b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
@@ -1036,4 +1036,6 @@ class TestDFDLExpressions {
 
   @Test def test_DoubleFromRawLong(): Unit = { 
runner2.runOneTest("DoubleFromRawLong") }
   @Test def test_DoubleToRawLong(): Unit = { 
runner2.runOneTest("DoubleToRawLong") }
+
+  @Test def test_unused_path_no_context_01(): Unit = { 
runner7.runOneTest("unused_path_no_context_01") }
 }

Reply via email to