[ 
https://issues.apache.org/jira/browse/XERCESJ-1248?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Michael Glavassevich resolved XERCESJ-1248.
-------------------------------------------

       Resolution: Fixed
    Fix Version/s: 2.10.0

Ludger, thanks for the patch. I reviewed it yesterday evening. It looked good 
and I just committed it to SVN (see rev 924245) with one minor change: setting 
the data field directly on the text node instead of calling 
setNodeValueInternal().

> Static textNode on AttrImpl breaks thread-safety, mutations of independant 
> documents from within a mutation listener, and introduces potential memory 
> leaks
> -----------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: XERCESJ-1248
>                 URL: https://issues.apache.org/jira/browse/XERCESJ-1248
>             Project: Xerces2-J
>          Issue Type: Bug
>          Components: DOM (Level 2 Events)
>    Affects Versions: 2.9.0
>         Environment: Applies to all environments
>            Reporter: Stephen White
>            Assignee: Michael Glavassevich
>             Fix For: 2.10.0
>
>         Attachments: staticTextNodePatch.txt
>
>
> AttrImpl has two modes of operation, dependant on the hasStringValue() 
> method.  This difference allows faster, lighter, operation in many cases .. 
> as attribute values can be stored as Strings.  In some situations, notably 
> DOM Events and the inclusion of entity references in Attribute values, the 
> value must be stored 'correctly' as one or more DOM Nodes rather than as a 
> String.
> In some Situations an AttrImpl may be operating in the 'String based' mode, 
> but then switch to the 'Node based' mode.  This happens when a Mutation 
> listener is registered on the Document and then a mutation event affecting 
> the attribution in question is fired.  This requires the creation of a 
> TextImpl Node to pass to the event listener.  This is all fine; however the 
> author appears to have been concerned about the number of TextImpls that 
> could potentially be constructed to pass to the event listener and then 
> discarded immediately afterwards.  Unfortunately the author introduced a 
> static TextImpl called textNode which is used, and re-used, to avoid the 
> repeated creation of TextImpls.  This static field introduces several 
> problems.
> Threading:
> This static field could potential be used by multiple independant nodes in 
> independant documents.  If these documents are manipulated in seperate 
> threads then this same static field could potentially be used concurrently by 
> multiple threads and so introduce a problem.
> Mutations of independant documents from within a mutation listener:
> This is a subtle issue, but is 100% re-producable.  A mutation listener for 
> one document can modify an independant document.  As the documents are 
> totally independant this shouldn't cause a problem.  However, if both the 
> mutation that triggered the event and the mutation that is initiated inside 
> the event listener both utilise the static field on AttrImpl then the event 
> listener will see its 'related node' change as an erroneous side-effect of 
> the mutation to the independant document.
> Memory leak:
> If the static field is utilised during the mutation of a DOM document then 
> that document will potentially be held in memory forever, though the 
> 'owerNode' reference on the TextImpl object referenced from the static field. 
>  As this static field is only used in certain specific circumstances it is 
> possible for an application to continue processing new documents without this 
> field being reset, and therefore the application will unwittingly continue to 
> reference the old document.
> I have produced an example application which demonstrates that manipulating 
> an independant document from within a mutation listener can have a side 
> effect that modifies the original event object.
> ---- BEGIN SAMPLE CODE ---
> package com.decisionsoft.xercesbug;
> import java.io.ByteArrayInputStream;
> import javax.xml.parsers.DocumentBuilder;
> import org.apache.xerces.dom.AttrImpl;
> import org.apache.xerces.dom.DocumentImpl;
> import org.apache.xerces.dom.ElementImpl;
> import org.apache.xerces.dom.events.MutationEventImpl;
> import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
> import org.w3c.dom.Document;
> import org.w3c.dom.Element;
> import org.w3c.dom.events.Event;
> import org.w3c.dom.events.EventListener;
> public class Bug {
>   // Null EventListener.  Just so that we can register something.
>   static class NullEventListener implements EventListener {
>     public void handleEvent(Event evt) {
>       System.err.println("[ NullEventListener: ignoring event ]");
>     }
>   }
>   // EventListener that mutates an unrelated document when an event is 
> received.
>   static class MyEventListener implements EventListener {
>     private Document _doc;
>     public MyEventListener(Document doc) {
>       _doc = doc;
>     }
>     public void handleEvent(Event evt) {
>       System.err.println("MyEventListener: start *******");
>       displayEvent(evt);
>       System.err.println("MyEventListener: changing unrelated document");
>       Element ele = (Element) _doc.getDocumentElement().getFirstChild();
>       ele.setAttribute("xml2attr", "NewValueSetInEventListener");
>       System.err.println("MyEventListener: displaying original event again");
>       displayEvent(evt);
>       System.err.println("MyEventListener: end *******");
>     }
>   }
>   // Utility method to display an event
>   public static void displayEvent(Event evt) {
>     System.err.println("  Event: " + evt + ", on " + evt.getTarget());
>     if (evt instanceof MutationEventImpl) {
>       MutationEventImpl mutation = (MutationEventImpl) evt;
>       System.err.println("  + Related: " + mutation.getRelatedNode());
>     }
>   }
>   /**
>    * @param args
>    */
>   public static void main(String[] args) throws Exception {
>     String xml1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root><e 
> xml1attr=\"xml1AttrValue\"/></root>\n";
>     String xml2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root><f 
> xml2attr=\"xml2AttrValue\"/></root>\n";
>     DocumentBuilder db = 
> DocumentBuilderFactoryImpl.newInstance().newDocumentBuilder();
>     DocumentImpl doc1 = (DocumentImpl) db.parse(new 
> ByteArrayInputStream(xml1.getBytes()));
>     DocumentImpl doc2 = (DocumentImpl) db.parse(new 
> ByteArrayInputStream(xml2.getBytes()));
>     ElementImpl ele = (ElementImpl) doc1.getDocumentElement().getFirstChild();
>     AttrImpl attr = (AttrImpl) ele.getAttributeNode("xml1attr");
>     attr.addEventListener(MutationEventImpl.DOM_NODE_REMOVED, new 
> MyEventListener(doc2), false);
>     doc2.addEventListener(MutationEventImpl.DOM_ATTR_MODIFIED, new 
> NullEventListener(), true);
>     Element e = (Element) doc1.getDocumentElement().getFirstChild();
>     e.setAttribute("xml1attr", "xml1AttrNewValue");
>   }
> }
> ---- END SAMPLE CODE ----
> Running this with Xerces 2.9.0 gives:
> MyEventListener: start *******
>   Event: org.apache.xerces.dom.events.mutationeventi...@17172ea, on [#text: 
> xml1AttrValue]
>   + Related: xml1attr="xml1AttrValue"
> MyEventListener: changing unrelated document
> [ NullEventListener: ignoring event ]
> MyEventListener: displaying original event again
>   Event: org.apache.xerces.dom.events.mutationeventi...@17172ea, on [#text: 
> xml2AttrValue]
>   + Related: xml1attr="xml2AttrValue"
> MyEventListener: end *******
> Notice that the event has changed during handleEvent method.  At the end of 
> the handleEvent method the 'relatedNode' on the event is wrong, it proclaims 
> to relate to the attirubte 'xml1attr' with the value 'xml2AttrValue' (a value 
> which that attribute never contains).
> The corrent output would be:
> MyEventListener: start *******
>   Event: org.apache.xerces.dom.events.mutationeventi...@17172ea, on [#text: 
> xml1AttrValue]
>   + Related: xml1attr="xml1AttrValue"
> MyEventListener: changing unrelated document
> [ NullEventListener: ignoring event ]
> MyEventListener: displaying original event again
>   Event: org.apache.xerces.dom.events.mutationeventi...@17172ea, on [#text: 
> xml1AttrValue]
>   + Related: xml1attr="xml1AttrValue"
> MyEventListener: end *******
> Here the mutation of the independant DOM document has not had the side effect 
> of corrupting the event being handled by the mutation listener.
> The patch I created to correct this issue is:
> diff -Naur xerces-2_9_0.orig/src/org/apache/xerces/dom/AttrImpl.java 
> xerces-2_9_0/src/org/apache/xerces/dom/AttrImpl.java
> --- xerces-2_9_0.orig/src/org/apache/xerces/dom/AttrImpl.java 2006-11-22 
> 23:36:58.000000000 +0000
> +++ xerces-2_9_0/src/org/apache/xerces/dom/AttrImpl.java  2007-05-04 
> 12:20:22.000000000 +0100
> @@ -138,8 +138,6 @@
>      // REVISIT: we are losing the type information in DOM during 
> serialization
>      transient Object type;
> -    protected static TextImpl textNode = null;
> -
>      //
>      // Constructors
>      //
> @@ -361,13 +359,8 @@
>                      oldvalue = (String) value;
>                      // create an actual text node as our child so
>                      // that we can use it in the event
> -                    if (textNode == null) {
> -                        textNode = (TextImpl)
> +                    TextImpl textNode = (TextImpl)
>                              ownerDocument.createTextNode((String) value);
> -                    }
> -                    else {
> -                        textNode.data = (String) value;
> -                    }
>                      value = textNode;
>                      textNode.isFirstChild(true);
>                      textNode.previousSibling = textNode;
> I believe that this patch corrects the issue described in the bug.  The 
> corrected code will instantiate a greater number of TextImpl objects in some 
> situations, however I believe these situations rare and the potential (small) 
> performance penalty is outweighed by the desire for the code to be correct.

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to