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 f0cbd9ed1 Catch and wrap exceptions thrown in layer transformers.
f0cbd9ed1 is described below
commit f0cbd9ed193d30d995c33c2d5758936ee6285be2
Author: Mike McGann <[email protected]>
AuthorDate: Mon Jan 30 10:43:31 2023 -0500
Catch and wrap exceptions thrown in layer transformers.
If an unexpected exception is thrown in a layer transformation, the
exception is not caught and Daffodil reports this as a bug. This change
catches unhandled exceptions, wraps them in a LayerExecutionException,
and handles this in the CLI.
DAFFODIL-2614
---
.../main/scala/org/apache/daffodil/cli/Main.scala | 6 ++
...g.apache.daffodil.runtime1.layers.LayerCompiler | 16 +++++
.../org/apache/daffodil/layers/buggy.dfdl.xsd | 51 +++++++++++++++
.../apache/daffodil/layers/xsd/buggyLayer.dfdl.xsd | 44 +++++++++++++
.../daffodil/cli/cliTest/TestCLIParsing.scala | 18 +++++-
.../org/apache/daffodil/cli/cliTest/Util.scala | 8 +--
.../apache/daffodil/layers/BuggyTransformer.scala | 72 ++++++++++++++++++++++
.../runtime1/layers/LayerTransformer.scala | 4 +-
.../runtime1/processors/DataProcessor.scala | 3 +
.../processors/parsers/LayeredSequenceParser.scala | 3 +
10 files changed, 219 insertions(+), 6 deletions(-)
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
index 987d5802e..3ea8e5a95 100644
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
@@ -84,6 +84,7 @@ import
org.apache.daffodil.runtime1.externalvars.ExternalVariablesLoader
import org.apache.daffodil.io.DataDumper
import org.apache.daffodil.io.FormatInfo
import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.runtime1.layers.LayerExecutionException
import org.apache.daffodil.runtime1.processors.DataLoc
import org.apache.daffodil.runtime1.processors.ExternalVariableException
import org.apache.daffodil.lib.schema.annotation.props.gen.BitOrder
@@ -1433,6 +1434,7 @@ object Main {
val BadExternalVariable = Value(33)
val UserDefinedFunctionError = Value(34)
val UnableToCreateProcessor = Value(35)
+ val LayerExecutionError = Value(36)
val Usage = Value(64)
@@ -1486,6 +1488,10 @@ object Main {
Logger.log.error(e.message)
ExitCode.ConfigError
}
+ case e: LayerExecutionException => {
+ Logger.log.error(e.message, e)
+ ExitCode.LayerExecutionError
+ }
case e: Exception => {
bugFound(e)
ExitCode.BugFound
diff --git
a/daffodil-cli/src/test/resources/META-INF/services/org.apache.daffodil.runtime1.layers.LayerCompiler
b/daffodil-cli/src/test/resources/META-INF/services/org.apache.daffodil.runtime1.layers.LayerCompiler
new file mode 100644
index 000000000..51a19c3f0
--- /dev/null
+++
b/daffodil-cli/src/test/resources/META-INF/services/org.apache.daffodil.runtime1.layers.LayerCompiler
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.daffodil.layers.BuggyLayerCompiler
diff --git
a/daffodil-cli/src/test/resources/org/apache/daffodil/layers/buggy.dfdl.xsd
b/daffodil-cli/src/test/resources/org/apache/daffodil/layers/buggy.dfdl.xsd
new file mode 100644
index 000000000..e100e9196
--- /dev/null
+++ b/daffodil-cli/src/test/resources/org/apache/daffodil/layers/buggy.dfdl.xsd
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+
+<schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
+ xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions"
+ xmlns:fn="http://www.w3.org/2005/xpath-functions"
+ xmlns:daf="urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:ext"
+ xmlns:buggy="urn:org.apache.daffodil.layers.buggy"
+ xmlns:ex="http://example.com"
+ xmlns:tns="http://example.com"
+ targetNamespace="http://example.com">
+
+ <include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"
/>
+
+ <import
+ namespace="urn:org.apache.daffodil.layers.buggy"
+ schemaLocation="org/apache/daffodil/layers/xsd/buggyLayer.dfdl.xsd"/>
+
+ <annotation>
+ <appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:format ref="tns:GeneralFormat" encoding="ascii" />
+ </appinfo>
+ </annotation>
+
+ <element name="r">
+ <complexType>
+ <sequence dfdl:ref="buggy:buggyFormat" dfdlx:layerLength="10">
+ <element name="value" type="xs:string" dfdl:lengthKind="explicit"
dfdl:length="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+</schema>
diff --git
a/daffodil-cli/src/test/resources/org/apache/daffodil/layers/xsd/buggyLayer.dfdl.xsd
b/daffodil-cli/src/test/resources/org/apache/daffodil/layers/xsd/buggyLayer.dfdl.xsd
new file mode 100644
index 000000000..83b4ac4e9
--- /dev/null
+++
b/daffodil-cli/src/test/resources/org/apache/daffodil/layers/xsd/buggyLayer.dfdl.xsd
@@ -0,0 +1,44 @@
+<?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.
+-->
+<schema
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:fn="http://www.w3.org/2005/xpath-functions"
+ xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
+ xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions"
+ xmlns:buggy="urn:org.apache.daffodil.layers.buggy"
+ xmlns:tns="urn:org.apache.daffodil.layers.buggy"
+ targetNamespace="urn:org.apache.daffodil.layers.buggy"
+ elementFormDefault="unqualified">
+
+ <include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"
/>
+
+ <annotation>
+ <appinfo source="http://www.ogf.org/dfdl/">
+
+ <dfdl:format ref="buggy:GeneralFormat" />
+
+ <dfdl:defineFormat name="buggyFormat">
+ <dfdl:format dfdlx:layerTransform="buggy"
dfdlx:layerLengthKind="explicit" dfdlx:layerLengthUnits="bytes"
+ dfdlx:layerEncoding="ascii" />
+ </dfdl:defineFormat>
+
+ </appinfo>
+ </annotation>
+
+</schema>
diff --git
a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIParsing.scala
b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIParsing.scala
index ce79937ad..6f9aee7b3 100644
---
a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIParsing.scala
+++
b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIParsing.scala
@@ -24,7 +24,7 @@ import org.junit.Test
import org.apache.daffodil.cli.cliTest.Util._
import org.apache.daffodil.cli.Main.ExitCode
-class TestCLIparsing {
+class TestCLIParsing {
@Test def test_3677_CLI_Parsing_elementFormDefault_qualified(): Unit = {
val schema =
path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/elementFormDefaultQualified.dfdl.xsd")
@@ -716,4 +716,20 @@ class TestCLIparsing {
cli.expectErr("Unable to load configuration")
} (ExitCode.ConfigError)
}
+
+ @Test def test_Layer_Execution(): Unit = {
+ val schema =
path("daffodil-cli/src/test/resources/org/apache/daffodil/layers/buggy.dfdl.xsd")
+ runCLI(args"parse -s $schema") { cli =>
+ cli.sendLine("1", inputDone = true)
+ cli.expect("""<value>1</value>""")
+ } (ExitCode.Success)
+ }
+
+ @Test def test_Layer_Execution_Error(): Unit = {
+ val schema =
path("daffodil-cli/src/test/resources/org/apache/daffodil/layers/buggy.dfdl.xsd")
+ runCLI(args"parse -s $schema") { cli =>
+ cli.sendLine("0", inputDone = true)
+ cli.expectErr("Unexpected exception in layer transformer 'buggy'")
+ } (ExitCode.LayerExecutionError)
+ }
}
diff --git
a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/Util.scala
b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/Util.scala
index bc219ad68..cb0ef8963 100644
--- a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/Util.scala
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/Util.scala
@@ -70,7 +70,7 @@ object Util {
/**
* Convert the daffodilRoot + parameter to a java Path. The string
- * parameter should contain unix path sparators and it will be interpreted
+ * parameter should contain unix path separators and it will be interpreted
* correctly regardless of operating system. When converted to a string to
* send to the CLI, it will use the correct line separator for the
* operating system
@@ -102,7 +102,7 @@ object Util {
def withTempFile(f: (Path) => Unit) : Unit = withTempFile(null, f)
/**
- * Create a temporary file in /tmp/daffodil/ with a givin suffix, call a user
+ * Create a temporary file in /tmp/daffodil/ with a given suffix, call a user
* provided function passing in the Path to that new file, and delete the
* file when the function returns.
*/
@@ -443,7 +443,7 @@ object Util {
def closeInput(): Unit = { toIn.close() }
/**
- * Write a string to stdin. This does not incluede trailing newline. If
+ * Write a string to stdin. This does not include trailing newline. If
* inputDone is true, close stdin afterwards.
*/
def send(string: String, inputDone: Boolean = false): Unit = {
@@ -514,7 +514,7 @@ object Util {
*
* args"parse -s $schema $input".split(" ")
*
- * Becomes someething like this:
+ * Becomes something like this:
*
* Array("parse", "-s", "path/to/schema.dfdl.xsd", "path/to/input.bin")
*
diff --git
a/daffodil-cli/src/test/scala/org/apache/daffodil/layers/BuggyTransformer.scala
b/daffodil-cli/src/test/scala/org/apache/daffodil/layers/BuggyTransformer.scala
new file mode 100644
index 000000000..52e440776
--- /dev/null
+++
b/daffodil-cli/src/test/scala/org/apache/daffodil/layers/BuggyTransformer.scala
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package org.apache.daffodil.layers
+
+import java.io.InputStream
+import org.apache.daffodil.runtime1.layers.LayerCompiler
+import org.apache.daffodil.runtime1.layers.LayerCompileInfo
+import org.apache.daffodil.runtime1.layers.LayerRuntimeInfo
+import org.apache.daffodil.runtime1.layers.LayerTransformer
+import org.apache.daffodil.runtime1.layers.LayerTransformerFactory
+import org.apache.daffodil.runtime1.processors.ParseOrUnparseState
+
+
+final class BuggyLayerCompiler
+ extends LayerCompiler("buggy") {
+
+ override def compileLayer(layerCompileInfo: LayerCompileInfo):
BuggyTransformerFactory = {
+ new BuggyTransformerFactory(name)
+ }
+}
+
+final class BuggyTransformerFactory(name: String)
+ extends LayerTransformerFactory(name) {
+
+ override def newInstance(layerRuntimeInfo: LayerRuntimeInfo)= {
+ new BuggyTransformer(name, layerRuntimeInfo)
+ }
+}
+
+class BuggyTransformer(name: String, layerRuntimeInfo: LayerRuntimeInfo)
+ extends LayerTransformer(name, layerRuntimeInfo) {
+
+ override def wrapLayerDecoder(jis: InputStream) = {
+ new BuggyInputStream(jis)
+ }
+
+ override def wrapLimitingStream(state: ParseOrUnparseState, jis:
InputStream) = {
+ jis
+ }
+
+ override protected def wrapLayerEncoder(jos: java.io.OutputStream):
java.io.OutputStream = {
+ jos
+ }
+
+ override protected def wrapLimitingStream(state: ParseOrUnparseState, jis:
java.io.OutputStream) = {
+ jis
+ }
+}
+
+final class BuggyInputStream(is: InputStream) extends InputStream {
+
+ def read(): Int = {
+ val b = is.read()
+ if (b != '0') b else throw new java.io.IOException("bad input stream")
+ }
+
+}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/LayerTransformer.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/LayerTransformer.scala
index 1bcb6ac41..800e718e6 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/LayerTransformer.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/LayerTransformer.scala
@@ -62,7 +62,7 @@ import java.nio.charset.Charset
* A layer transformer is created at runtime as part of a single parse/unparse
call.
* Hence, they can be stateful without causing thread-safety issues.
*/
-abstract class LayerTransformer(layerName: String, layerRuntimeInfo:
LayerRuntimeInfo) {
+abstract class LayerTransformer(val layerName: String, layerRuntimeInfo:
LayerRuntimeInfo) {
protected def wrapLayerDecoder(jis: InputStream): InputStream
@@ -223,6 +223,8 @@ case class LayerNotEnoughDataException(sfl:
SchemaFileLocation, dataLocation: Da
override def modeName = "Parse"
}
+case class LayerExecutionException(message: String, cause: Throwable) extends
RuntimeException(message, cause)
+
/**
* Allows access to all the layer properties, if defined, including
* evaluating expressions if the properties values are defined as expressions.
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
index f099d2c70..4c1ec401b 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
@@ -27,6 +27,8 @@ import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.util.zip.GZIPOutputStream
import org.apache.daffodil.lib.Implicits._
+import org.apache.daffodil.runtime1.layers.LayerExecutionException
+
object INoWarn4 {
ImplicitsSuppressUnusedImportWarning() }
import org.apache.daffodil.runtime1.api.DFDL
@@ -455,6 +457,7 @@ class DataProcessor(
state.setFailed(e)
}
case us: UnsuppressableException => throw us
+ case le: LayerExecutionException => throw le
case x: Throwable => {
val sw = new java.io.StringWriter()
val pw = new java.io.PrintWriter(sw)
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/LayeredSequenceParser.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/LayeredSequenceParser.scala
index 8554b9e28..874453e18 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/LayeredSequenceParser.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/LayeredSequenceParser.scala
@@ -17,6 +17,7 @@
package org.apache.daffodil.runtime1.processors.parsers
+import org.apache.daffodil.runtime1.layers.LayerExecutionException
import org.apache.daffodil.runtime1.layers.LayerNotEnoughDataException
import org.apache.daffodil.runtime1.layers.LayerRuntimeInfo
import org.apache.daffodil.runtime1.layers.LayerTransformerFactory
@@ -54,6 +55,8 @@ class LayeredSequenceParser(
} catch {
case le: LayerNotEnoughDataException =>
PENotEnoughBits(state, le.schemaFileLocation, le.dataLocation,
le.nBytesRequired * 8, MaybeULong.Nope)
+ case e: Exception =>
+ throw LayerExecutionException(s"Unexpected exception in layer
transformer '${layerTransformer.layerName}': $e", e)
} finally {
state.dataInputStream = savedDIS
}