JDK-5023550 : LTP: XMLEncoder throws NPE when Expression for arg != Expression.getTarget() w/ owner
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.beans
  • Affected Version: 1.4.2
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: linux
  • CPU: x86
  • Submitted: 2004-03-30
  • Updated: 2010-02-24
  • Resolved: 2010-02-24
Related Reports
Duplicate :  
Description
Name: jl125535			Date: 03/30/2004


FULL PRODUCT VERSION :
java version "1.5.0-beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta-b32c)
Java HotSpot(TM) Client VM (build 1.5.0-beta-b32c, mixed mode)

java version "1.4.2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-b28)
Java HotSpot(TM) Client VM (build 1.4.2-b28, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
OS independent

A DESCRIPTION OF THE PROBLEM :
If an object A has an instantiation Expression aexp with
aexp.getTarget() == Encoder.getOwner()
and an argument arg0 with instantiation Expression arg0exp and arg0exp.getTarget() is arg0Target, and if the instantiation Expression for arg0Target, arg0TargetExp.getTarget() == Encoder.getOwner(), then XMLEncoder throws an NPE.

This may sound completely bizarre, but I actually had to solve this problem in this set of bugs in the XMLEncoder.

The problem is that for a given value v, while all the super-targets of v do not share a target with outer, it continues to walk up the target tree.  If v's "eldest target" is the owner, then it asks what the target for the owner is.  The target for the owner is the XMLEncoder, and when it tries to write the reference to the XMLEncoder, it finds a null reference to an Expression, so it throws an NPE

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
javac PT4.java
java PT4

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.4.2_04" class="java.beans.XMLDecoder">
 <void property="owner">
  <void method="newB">
   <void id="PT4$C0" method="newC"/>
  </void>
  <void id="PT4$A0" method="newA">
   <object idref="PT4$C0"/>
  </void>
 </void>
 <object idref="PT4$A0"/>
</java>

ACTUAL -
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.4.2_04" class="java.beans.XMLDecoder">
 <void property="owner">
  <void id="PT4$A0" method="newA">
Exception in thread "main" java.lang.NullPointerException
        at java.beans.XMLEncoder.outputStatement(XMLEncoder.java:540)
        at java.beans.XMLEncoder.outputValue(XMLEncoder.java:535)
        at java.beans.XMLEncoder.outputStatement(XMLEncoder.java:571)
        at java.beans.XMLEncoder.outputValue(XMLEncoder.java:535)
        at java.beans.XMLEncoder.outputStatement(XMLEncoder.java:571)
        at java.beans.XMLEncoder.outputValue(XMLEncoder.java:535)
        at java.beans.XMLEncoder.outputStatement(XMLEncoder.java:571)
        at java.beans.XMLEncoder.outputValue(XMLEncoder.java:535)
        at java.beans.XMLEncoder.outputStatement(XMLEncoder.java:602)
        at java.beans.XMLEncoder.outputStatement(XMLEncoder.java:607)
        at java.beans.XMLEncoder.flush(XMLEncoder.java:392)
        at java.beans.XMLEncoder.close(XMLEncoder.java:417)
        at PT4.main(PT4.java:27)


ERROR MESSAGES/STACK TRACES THAT OCCUR :
See "Actual Result" section.


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.beans.*;
import java.io.*;

public class PT4
{
    public static void main(String[] argv)
        throws Exception
    {
        XMLEncoder encoder = new XMLEncoder(System.out);
        Owner owner = new Owner();
        encoder.setOwner(owner);
        encoder.setPersistenceDelegate(A.class, new ADelegate());
        encoder.setPersistenceDelegate(B.class, new BDelegate());
        encoder.setPersistenceDelegate(C.class, new CDelegate());
        encoder.setExceptionListener(new ExceptionListener()
                                     {
                                         public void
                                         exceptionThrown(Exception e)
                                         {  e.printStackTrace(); }
                                     });

        B b = owner.newB();
        C c = b.newC();
        A a = owner.newA(c);

        encoder.writeObject(a);
        encoder.close();
    }

    public static class Owner
    {
        public Owner()
        {}

        public A newA(C c)
        {   return new A(c); }

        public B newB()
        {   return new B(); }
    }

    public static class A
    {
        private final C m_c;
        public A(C c)
        {   m_c = c; }

        public C getC()
        {   return m_c; }
    }

    public static class B
    {
        public B()
        {}

        public C newC()
        {   return new C(this); }
    }


    public static class C
    {
        private final B m_b;

        C(B b)
        {   m_b = b; }

        public B getB()
        {   return m_b; }
    }

    public static class ADelegate
        extends DefaultPersistenceDelegate
    {
        protected Expression instantiate(Object p_old, Encoder p_out)
        {
            XMLEncoder encoder = (XMLEncoder)p_out;
            A a = (A)p_old;
            return new Expression(p_old, encoder.getOwner(), "newA",
                                  new Object[] { a.getC() } );
        }
    }

    public static class BDelegate
        extends DefaultPersistenceDelegate
    {
        protected Expression instantiate(Object p_old, Encoder p_out)
        {
            XMLEncoder encoder = (XMLEncoder)p_out;
            B b = (B)p_old;
            return new Expression(p_old, encoder.getOwner(), "newB",
                                  new Object[0]);
        }
    }

    public static class CDelegate
        extends DefaultPersistenceDelegate
    {
        protected Expression instantiate(Object p_old, Encoder p_out)
        {
            C c = (C)p_old;
            return new Expression(c, c.getB(), "newC", new Object[0]);
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
  Fixing all the other bugs I've submitted fixes a number of bugs that fixing this bug exposes, but to fix the NPE *only*, the following patch works.

Insert the following line at the bottom of outputValue()

        if (value != this) // this line is new
            outputStatement(d.exp, outer, isArgument);

This prevents the recursion from walking up the "target tree" any further than it should.
(Incident Review ID: 245076) 
======================================================================

Comments
EVALUATION The problem could be solved together with the 6921644 CR.
24-02-2010

EVALUATION To fix NPE we should change XMLEncoder.outputStatement(Statement, Object, boolean): if (outer == target) { ... } else if (...) { ... } else if (...) { ... } else { d.refs = 2; if (target != this) { // ----- NEW LINE! getValueData(target).refs++; outputValue(target, outer, false); } // ----- NEW LINE! if (isArgument) { outputValue(value, outer, false); } return; } But this changes shows another problem. Element with attribute idref="some name" is generated before element with attribute id="some name". Such XML could not be read by XMLDecoder. We should find the way to sort such elements...
19-07-2007

CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: dragon
14-06-2004

EVALUATION Reproducible as described in 1.4 through 1.5. ###@###.### 2004-03-31 Thanks for an excellently written set of bugs. I'm committing these. ###@###.### 2004-04-08
31-03-2004