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") }
}