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 1e26e2999 Ensure we set elements as final only once and when they have 
values
1e26e2999 is described below

commit 1e26e299965e56774a8e1c38ef5cbd5b4675c250
Author: Steve Lawrence <[email protected]>
AuthorDate: Wed Aug 13 13:35:44 2025 -0400

    Ensure we set elements as final only once and when they have values
    
    Our current logic to handle setting elements as final during parse is
    pretty broken. The core issue is that we primarily call setFinal in the
    sequence parser where it is difficult to know which elements are
    actually final--we had to make assumptions based on the results of the
    things actualy parsing elements. This usually worked, but in some cases
    could lead to setting an element final multiple times, or setting an
    element as final when it wasn't actually final.
    
    A case where this flawed logic could lead to issues is with
    InfosetInputters that aren't designed to receive invalid infosets, such
    as JDOM or SAX inputters. Note that InfosetInputters aren't required to
    handle invalid infosets, except for those that are used by the debugger.
    This could lead to incomplete error diagnostics or misleading reasons
    for an error.
    
    To make the final logic more clear and avoid these kinds of errors,
    DINode.isFinal is made private with new getFinal and setFinal method
    used instead. These functions allow us to place a number of assertions
    throughout the infoset logic to ensure we set things as final
    appropriately.
    
    This also moves where we set elements as final. DISimple and DIComplex
    elements are set final in the ElementCombinatorParser and only when they
    successfully parse. This avoids cases where an element could be set
    final even though it failed to parse and removes any ambiguities about
    what sholud be set final. DIArrays are set final in the sequence parser
    that handles the ending of arrays, with special logic to handle arrays
    in unordered sequences. Note that the sequence parser is still
    responsible for attempting to walk the infoset to project the internal
    infoset into the target infoset--keeping all the walk logic in one
    place.
    
    DAFFODIL-3006
---
 .../org/apache/daffodil/cli/cli_schema_05.dfdl.xsd | 52 +++++++++++++++
 .../daffodil/cli/cliTest/TestCLIParsing.scala      | 25 +++++++
 .../daffodil/runtime1/infoset/InfosetImpl.scala    | 51 +++++++++++++--
 .../runtime1/processors/DataProcessor.scala        |  9 +--
 .../processors/parsers/ElementCombinator1.scala    |  7 ++
 .../processors/parsers/ElementKindParsers.scala    | 11 ----
 .../runtime1/processors/parsers/Parser.scala       | 13 ----
 .../processors/parsers/SequenceChildBases.scala    | 15 +++++
 .../processors/parsers/SequenceParserBases.scala   | 76 ++--------------------
 .../unparsers/runtime1/ElementUnparser.scala       | 18 +++--
 10 files changed, 163 insertions(+), 114 deletions(-)

diff --git 
a/daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_05.dfdl.xsd
 
b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_05.dfdl.xsd
new file mode 100644
index 000000000..fc286ac61
--- /dev/null
+++ 
b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_05.dfdl.xsd
@@ -0,0 +1,52 @@
+<?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="http://www.w3.org/2001/XMLSchema";
+  xmlns:xs="http://www.w3.org/2001/XMLSchema";
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/";
+  xmlns:ex="http://example.com";
+  targetNamespace="http://example.com"; >
+
+  <include 
schemaLocation="/org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+
+  <annotation>
+    <appinfo source="http://www.ogf.org/dfdl/";>
+      <dfdl:format ref="ex:GeneralFormat" representation="binary" />
+    </appinfo>
+  </annotation>
+
+  <element name="root">
+    <complexType>
+      <sequence>
+        <element name="dispatch" type="xs:int" />
+        <choice dfdl:choiceDispatchKey="{ xs:string(./dispatch) }">
+          <sequence dfdl:choiceBranchKey="1">
+            <element name="i1" type="xs:int" />
+            <element name="i2" type="xs:int" />
+          </sequence>
+          <sequence dfdl:choiceBranchKey="2">
+            <element name="l1" type="xs:long" />
+            <element name="l2" type="xs:long" />
+          </sequence>
+        </choice>
+      </sequence>
+    </complexType>
+  </element>
+
+</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 4e66be222..eeb214881 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
@@ -1012,4 +1012,29 @@ class TestCLIParsing {
       e.getMessage.contains("""Input Buffer: <?xml version="1.0" 
encoding="UTF-8"?>""")
     )
   }
+
+  @Test def test_CLI_Parsing_parse_error_infoset_walker_jdom(): Unit = {
+    val schema = path(
+      
"daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_05.dfdl.xsd"
+    )
+
+    runCLI(args"parse -s $schema -I jdom -TinfosetWalkerSkipMin=0 
-TinfosetWalkerSkipMax=0") {
+      cli =>
+        // this is not enough data for the scema, which leads to a parse error 
about insufficient bits
+        cli.sendBytes(Array[Byte](0, 0, 0, 1), inputDone = true)
+
+        // there was a bug Daffodil that is most easily observed using the 
jdom infoset outputter
+        // with a non skipping infoset walker. With this setup, when an 
element fails to parse
+        // inside a choice dispatch (and no surrounding points of uncertainty) 
the infoset walker
+        // could walk into the failed element, which leads to an SDE when 
using the JDOM infoset
+        // outputter. This SDE prevents backtracking so we do not see a 
diagnostic about the
+        // choice dispatch branch failing. If the bug is fixed, we should 
never walk into the
+        // invalid element, we should not get an SDE, and we should get a 
diagnostic about choice
+        // dispatch.
+        cli.expectErr("Parse Error: Choice dispatch branch failed")
+
+        // this is the core failure diagnostic, which we see regardless of bug
+        cli.expectErr("Parse Error: Insufficient bits in data.")
+    }(ExitCode.ParseError)
+  }
 }
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala
index 6dbd2278e..7d14db9c1 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala
@@ -161,13 +161,32 @@ sealed trait DINode {
   var wouldHaveBeenFreed: Boolean = false
 
   /**
-   * When unparsing, arrays and complex elements have a notion of being 
finalized.
-   * This is when we know that no more elements will be added to them, so 
things
-   * like fn:count can return a value knowing it won't change, and fn:exists 
can
-   * return false, knowing nobody will subsequently append the item that was 
being
-   * questioned.
+   * All node types have a concept of being finalized. When parsing, this is 
used
+   * by the InfosetWalker to know if the element could change (e.g. simple type
+   * modifying the element value, complex or array adding a child) and thus if 
it
+   * can project the element to the target infoset. When unparsing, this is 
used
+   * for comlplex and arrays, so we know if more elements could be added to 
them,
+   * so functions like fn:count can return a value knowing it won't change, and
+   * fn:exists can return false, knowing nobody will subsequently append the 
item
+   * that was being questioned.
    */
-  var isFinal: Boolean = false
+  private var _isFinal: Boolean = false
+
+  /**
+  * Use to mark a node as final, indicating that its value will not change or 
have
+  * any children added to it. Setting an element as final does not preclude it 
from
+  * being discarded by backtracking, i.e. it is only locally final, but might 
still
+  * be inside an enclosing PoU.
+  *
+  * This cannot be called if an element is already marked as final to help 
ensure
+  * correct use.
+   */
+  def setFinal(): Unit = {
+    Assert.invariant(!_isFinal)
+    _isFinal = true
+  }
+
+  def isFinal: Boolean = _isFinal
 
   /**
    * use to require it be finalized or throw the appropriate
@@ -1134,7 +1153,7 @@ sealed trait DIElement
   }
 
   def setNilled(): Unit = {
-    Assert.invariant(erd.isNillable)
+    Assert.invariant(erd.isNillable && !isFinal)
     _isNilled = true
   }
 
@@ -1260,6 +1279,7 @@ final class DIArray(
   }
 
   def append(ie: DIElement): Unit = {
+    Assert.invariant(!isFinal)
     _contents += ie
     ie.setArray(this)
   }
@@ -1341,6 +1361,11 @@ sealed class DISimple(override val erd: 
ElementRuntimeData)
     this.setValid(true)
   }
 
+  override def setFinal(): Unit = {
+    Assert.invariant(hasValue || _isNilled)
+    super.setFinal()
+  }
+
   /**
    * Parsing of a text number first does setDataValue to a string, then a 
conversion does overwrite data value
    * with a number. Unparsing does setDataValue to a value, then 
overwriteDataValue to a string.
@@ -1351,6 +1376,7 @@ sealed class DISimple(override val erd: 
ElementRuntimeData)
   }
 
   def overwriteDataValue(x: DataValuePrimitiveNullable): Unit = {
+    Assert.invariant(!isFinal)
     //
     // let's find places where we're putting a string in the infoset
     // but the simple type is not string. That happens when parsing or 
unparsing text Numbers, text booleans, text Date/Times.
@@ -1801,6 +1827,15 @@ sealed class DIComplex(override val erd: 
ElementRuntimeData)
               occurrence
             )
 
+          // SequenceParserBase uses special logic for unordered sequences and 
arrays, so it
+          // never sets DIArrays in unordered as final. Now that we have 
finished parsing the
+          // unordered sequence and flattened its arrays, we can mark the 
arrays as final and
+          // allow the infoset walker to project it to an infoset. Note that 
the InfosetWalker
+          // will not walk past active points of uncertainty, so even though 
this is final we
+          // don't have to worry that we might backtrack and remove 
it--setFinal is just about
+          // local finality.
+          a.setFinal()
+
         } else {
           if (nodes.length > 1) {
             val diag = new InfosetMultipleScalarError(erd)
@@ -1821,6 +1856,8 @@ sealed class DIComplex(override val erd: 
ElementRuntimeData)
    * When slot contains an array, this appends to the end of the array.
    */
   def addChild(e: DIElement, tunable: DaffodilTunables): Unit = {
+    Assert.invariant(!isFinal)
+
     if (e.runtimeData.isArray) {
       val childERD = e.runtimeData
       val needsNewArray =
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
index 51a2e1265..79d047998 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
@@ -368,12 +368,9 @@ class DataProcessor(
         state.setMaybeProcessor(Maybe(p))
 
         if (state.processorStatus == Success) {
-          // At this point all infoset nodes have been set final, all infoset
-          // walker blocks released, and all elements walked. The one exception
-          // is the root node has not been set final because isFinal is handled
-          // by the sequence parser and there is no sequence around the root
-          // node. So mark it final and do one last walk to end the document.
-          state.infoset.child(0).isFinal = true
+          // At this point all infoset nodes have been set final, all PoUs
+          // resolved, and all infoset walker blocks released. Do one last walk
+          // to project any unwalked elements to the target infoset
           state.walker.walk(lastWalk = true)
           Assert.invariant(state.walker.isFinished)
         }
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementCombinator1.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementCombinator1.scala
index 5cdd7dd32..f6ab4d3b3 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementCombinator1.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementCombinator1.scala
@@ -206,6 +206,13 @@ abstract class ElementParserBase(
         validate(pstate)
       }
 
+      // We successfully finished parsing this element. There are no more 
changes expected to be
+      // made so we can mark it as final and allow the InfosetWalker to 
project it to the target
+      // infoset. Note that the InfosetWalker will not walk past active points 
of uncertainty,
+      // so even though this is final we don't have to worry that it might we 
might backtrack
+      // and remove it--setFinal is just about local finality.
+      pstate.infoset.setFinal()
+
     } finally {
       parseEnd(pstate)
       if (pstate.dataProc.isDefined) pstate.dataProc.value.endElement(pstate, 
this)
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementKindParsers.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementKindParsers.scala
index ccdda5de0..1f9934df1 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementKindParsers.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ElementKindParsers.scala
@@ -213,17 +213,6 @@ abstract class ChoiceDispatchCombinatorParserBase(
 
           if (pstate.processorStatus eq Success) {
             Logger.log.debug(s"Choice dispatch success: ${parser}")
-
-            // We usually rely on the sequence parser to set elements as final.
-            // But choices with scalar elements do not necessarily have a
-            // sequence surrounding them and so they aren't set final. In order
-            // to set these elements final, we do it here as well. We will
-            // attempt to walk the infoset after the PoU is discarded.
-            val newLastChildNode = pstate.infoset.maybeLastChild
-            if (newLastChildNode.isDefined) {
-              newLastChildNode.get.isFinal = true
-            }
-
           } else {
             Logger.log.debug(s"Choice dispatch failed: ${parser}")
             val diag =
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/Parser.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/Parser.scala
index 20f5305c6..c90d57f98 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/Parser.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/Parser.scala
@@ -276,17 +276,6 @@ class ChoiceParser(ctxt: RuntimeData, val childParsers: 
Array[Parser])
           // Choice branch was successfull. Break out of the loop and let
           // withPointOfUncertainty discard the pou
           successfullyParsedChildBranch = true
-
-          // We usually rely on the sequence parser to set elements as final.
-          // But choices with scalar elements do not necessarily have a
-          // sequence surrounding them and so they aren't set final. In order
-          // to set these elements final, we do it here as well. We will
-          // attempt to walk the infoset after the PoU is discarded.
-          val newLastChildNode = pstate.infoset.maybeLastChild
-          if (newLastChildNode.isDefined) {
-            newLastChildNode.get.isFinal = true
-          }
-
         } else {
           // Failed to parse this branch alternative. Create diagnostic and
           // check if anything resolved the associated point of uncertainty
@@ -310,8 +299,6 @@ class ChoiceParser(ctxt: RuntimeData, val childParsers: 
Array[Parser])
           }
         }
       }
-
-      pstate.walker.walk()
     }
 
     if (!successfullyParsedChildBranch) {
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala
index 738ffd80e..cce424fdf 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala
@@ -357,6 +357,21 @@ abstract class RepeatingChildParser(
     state.mpstate.arrayIterationIndexStack.pop()
     val occurrences = state.mpstate.occursIndexStack.pop() - 1
     super.endArray(state, occurrences)
+
+    // if there were no issues parsing this array, and we actually created 
some elements, we
+    // need to mark the DIArray as final to allow the InfosetWalker to end the 
array and walk to
+    // later siblings. Note that the InfosetWalker will not walk past active 
points of
+    // uncertainty, so even though this is final we don't have to worry that 
it might we might
+    // backtrack and remove it--setFinal is just about local finality. Also, 
this
+    // RepeatingChildParser is used for both arrays and optional elements. We 
only need to do
+    // this for arrays since optional elements are set final in the 
ElementParser
+    if ((state.processorStatus eq Success) && occurrences > 0 && erd.isArray) {
+      // state.infoset is the parent of the array we need to set as final. We 
know we added an
+      // array and we know its the last child of the parent, so we can just 
get that directly
+      val parent = state.infoset
+      val array = parent.maybeLastChild.get
+      array.setFinal()
+    }
   }
 
 }
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala
index 58c2868da..a0366cf81 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala
@@ -114,9 +114,6 @@ abstract class SequenceParserBase(
       //
       while (!isDone && (scpIndex < limit) && (pstate.processorStatus eq 
Success)) {
 
-        // keep track of the current last child node. If the last child changes
-        // while parsing, we know a new child was added in this loop
-
         child = children(scpIndex).asInstanceOf[SequenceChildParser]
 
         child match {
@@ -216,29 +213,9 @@ abstract class SequenceParserBase(
                 pstate.mpstate.moveOverOneGroupIndexOnly()
               }
 
-              val newLastChildNode = pstate.infoset.maybeLastChild
-              if (newLastChildNode.isDefined) {
-                // We have potentially added a child to to this complex during
-                // this array loop.
-                //
-                // If the new child is a DIArray, we know this DIArray has at
-                // least one element, but we don't know if we actually added a
-                // new one in this loop or not. So just get the last array
-                // element and set it as final anyways.
-                //
-                // If it's not a DIArray, that means it's just an optional
-                // simple/complex and that will get set final below where all
-                // other non-array elements get set as final.
-                val lastChild = newLastChildNode.get
-                if (lastChild.isArray) {
-                  // not simple or complex, must be an array
-                  val lastArrayElem = lastChild.maybeLastChild
-                  if (lastArrayElem.isDefined) {
-                    lastArrayElem.get.isFinal = true
-                    pstate.walker.walk()
-                  }
-                }
-              }
+              // we might have added a new instance to the array. Attempt to 
project it to an
+              // infoset if there are no PoU's or anything blocking it
+              pstate.walker.walk()
 
             } // end while for each repeat
             parser.endArray(pstate)
@@ -335,50 +312,9 @@ abstract class SequenceParserBase(
           } // end case scalarParser
         } // end match case parser
 
-        // now that we have finished parsing a single instance of this 
sequence,
-        // we need to potentially set things as final, get the last child to
-        // determine if it changed from the saved last child, which lets us 
know
-        // if a new child was actually added.
-        val newLastChildNode = pstate.infoset.maybeLastChild
-
-        if (!isOrdered) {
-          // In the special case of unordered sequences with arrays, we do not
-          // use the RepatingChildParser. Instead we parse on instance at a 
time
-          // in this loop. So array elements aren't set final above like normal
-          // arrays are.
-          //
-          // So if the last child node is a DIArray, we must set new array
-          // elements as final here. We can't know if we actually added a new
-          // DIArray element or not, so just set the last one as final
-          // regardless.
-          //
-          // Note that we do not need to do a null check because in an 
unordered
-          // sequence we are blocking, so we can't possibly walk/free any of
-          // these newly added elements.
-          if (newLastChildNode.isDefined && newLastChildNode.get.isArray) {
-            // we have a new last child, and it's not simple or complex, so 
must
-            // be an array. Set its last child final
-            newLastChildNode.get.maybeLastChild.get.isFinal = true
-          }
-        }
-
-        // We finished parsing one part of a sequence, which could either be an
-        // array, simple, or complex. We aren't sure if we actually added a new
-        // element or not, but in case we did, mark the last node as final.
-        //
-        // Additionally, if this is an ordered sequence, try to walk the 
infoset
-        // to output events for this potentially new element. If this is an
-        // unordered sequence, walking is unnecessary. This is because we may
-        // need to reorder the infoset once this unordered sequence is complete
-        // (via flattenAndValidateChildNodes below) and cannot walk until that
-        // happens. To ensure we don't walk even if a child parser tries to 
call
-        // walk() we incremented infosetWalkerBlockCount at the beginning of 
this
-        // function, so the walker is effectively blocked from making any
-        // progress. So we don't even bother calling walk() in this case.
-        if (newLastChildNode.isDefined) {
-          newLastChildNode.get.isFinal = true
-          if (isOrdered) pstate.walker.walk()
-        }
+        // we finished parsing one whole thing (scalar element, entire array, 
etc). Attempt to
+        // project it to an infoset if there are no PoU's or anything blocking 
it
+        pstate.walker.walk()
 
         scpIndex += 1
 
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ElementUnparser.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ElementUnparser.scala
index e86dc802c..7c5c2e5a9 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ElementUnparser.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ElementUnparser.scala
@@ -466,7 +466,7 @@ sealed trait RegularElementUnparserStartEndStrategy extends 
ElementUnparserStart
         if (lastChildMaybe.isDefined) {
           val lastChild = lastChildMaybe.get
           if (lastChild.isArray && (lastChild.erd ne newElem.erd)) {
-            lastChild.isFinal = true
+            lastChild.setFinal()
             parentComplex.freeChildIfNoLongerNeeded(
               parentComplex.numChildren - 1,
               state.releaseUnneededInfoset
@@ -527,7 +527,7 @@ sealed trait RegularElementUnparserStartEndStrategy extends 
ElementUnparserStart
         // is no sibling following the array, so it must be set here.
         val lastChild = cur.maybeLastChild
         if (lastChild.isDefined && lastChild.get.isArray) {
-          lastChild.get.isFinal = true
+          lastChild.get.setFinal()
           cur.freeChildIfNoLongerNeeded(cur.numChildren - 1, 
state.releaseUnneededInfoset)
         }
       }
@@ -535,8 +535,12 @@ sealed trait RegularElementUnparserStartEndStrategy 
extends ElementUnparserStart
       // cur is finished, mark it as final and free if possible. Note that we
       // need the container and not the parent of the current element to free
       // it. This way if this element is in an array, we free this element
-      // from the array
-      cur.isFinal = true
+      // from the array. We also do not set hidden IVC elements as
+      // final--although we allow hidden IVC elements when unparsing, they
+      // never get a value so we can't set them as final without breaking
+      // assertions. Nothing can access hidden IVC elements, so this should
+      // not break anything
+      if (!state.withinHiddenNest || erd.isRepresented) cur.setFinal()
       val curContainer =
         if (cur.erd.isArray) cur.diParent.maybeLastChild.get
         else cur.diParent
@@ -551,7 +555,7 @@ sealed trait RegularElementUnparserStartEndStrategy extends 
ElementUnparserStart
         // so mark the DIDocument as final
         val doc = state.documentElement
         Assert.invariant(!doc.isFinal)
-        doc.isFinal = true
+        doc.setFinal()
       }
 
       move(state)
@@ -614,7 +618,7 @@ trait OVCStartEndStrategy extends 
ElementUnparserStartEndStrategy {
     if (lastChildMaybe.isDefined) {
       val lastChild = lastChildMaybe.get
       if (lastChild.isArray) {
-        lastChild.isFinal = true
+        lastChild.setFinal()
         parentComplex.freeChildIfNoLongerNeeded(
           parentComplex.numChildren - 1,
           state.releaseUnneededInfoset
@@ -645,7 +649,7 @@ trait OVCStartEndStrategy extends 
ElementUnparserStartEndStrategy {
       // so mark the DIDocument as final
       val doc = state.documentElement
       Assert.invariant(!doc.isFinal)
-      doc.isFinal = true
+      doc.setFinal()
     }
 
     move(state)

Reply via email to