Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4477877

Introspection depends on method order, method order depends on class load order

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • P3
    • 1.3.1_08
    • 1.2.2, 1.3.0, 1.3.1, 1.4.0
    • client-libs
    • 08
    • generic, x86, sparc
    • generic, solaris_8, windows_95

    Backports

      Description



        Name: bsC130419 Date: 07/09/2001


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


        java.beans.Introspector returns results that depend on the
        order that classes are loaded by the Virtual Machine.

        The bug is caused by code in java.beans.Introspector method
        getTargetPropertyInfo that considers which methods
        correspond to bean properties, and code in addProperty,
        which resolves (crudely) conflicting properties.
        getTargetPropertyInfo is invoked by getBeanInfo, which is in
        turn invoked in base-to-derived order by recursive
        invocations of the three-argument private Introspector
        constructor. In addProperty, when a candidate new property
        conflicts (different type) with an existing property, the
        new property is ignored. This resolution is order-dependent
        -- the first method visited wins. The method order is
        "chosen" (or not) by getPublicDeclaredMethods, which simply
        filters for public methods returned by
        java.lang.Class.getDeclaredMethods.

        The method ordering returned by java.lang.Class.getDeclaredMethods:

        1) is explicitly undefined
        2) DOES differ between different JVM platforms
        3) in the case of this JDK/JRE version DOES differ
           depending upon the order in which classes are loaded.

        This bug appears to be the root cause (or very nearly
        related to) a number of similar bugs, and may explain some
        of the difficulty encountered in reproducing these bugs
        under varying VMs. Related bugs are 4277957, 4387842,
        4407050, 4168833, 4408406, 4460327, and closed bug 4144543.
        This bug (depending upon whether it is triggered or not) can
        cause Java Pet Store 1.1.2 to run or fail.

        The program "IntrospectorTest2.Main" is appended.

        Under jdk.1.3.0 and jdk1.3.1, if it is run like so:

           java IntrospectorTest2.Main test X

        it produces this output, with "foo" type int and
        writeMethod null;

        --------------------------------------------
        trying to load IntrospectorTest2.X
        ...
        foo displayName = foo
        foo propertyType = int
        foo readMethod = public int IntrospectorTest2.X.getFoo()
        foo writeMethod = null
        --------------------------------------------

        If run as follows, instead (loading Y before loading and
        testing X) then the order of the methods in their common
        base class is changed, and the foo type becomes String,
        and the readMethod is null instead.

           java IntrospectorTest2.Main load Y test X

        --------------------------------------------
        trying to load IntrospectorTest2.Y
        trying to load IntrospectorTest2.X
        ...
        foo displayName = foo
        foo propertyType = class java.lang.String
        foo readMethod = null
        foo writeMethod = public void
        IntrospectorTest2.Base.setFoo(java.lang.String)
        --------------------------------------------

        The same results appear when run under the interpreter.

        We encountered this bug attempting to run the J2EE Java Pet
        Store demo (version 1.1.2) where (in package
        com.sun.j2ee.blueprints.petstore.taglib.list) classes
        ListTag and ProductListTag have defined two pairs of
        type-conflicting getters and setters (startIndex and
        numItems).

        ProductListTag contains this code and comment:

          // setters (overloaded to fix bug in tomcat)
          public void setNumItems(String numItemsStr) {
            super.setNumItems(numItemsStr);
          }

          public void setStartIndex(String startIndexStr) {
            super.setNumItems(startIndexStr);
          }

        however the comment is wrong -- the bug is in
        java.beans.Introspector (and the method is
        overridden, not overloaded).

        The fix used in the pet store is to add a method to the
        derived class (ProductListTag) to modify the order that the
        methods are returned by java.lang.Class.getDeclaredMethods().
        This is not documented or expected behavior -- in this
        version of Sun's VM, it just happens, and it only happens
        because the derived type triggers the loading of the base
        type (if the base type is explicitly loaded first, it does
        not happen. If a sibling derived type is loaded first, it
        happens for the sibling, but not this class). In the case
        of Jdk1.3.0, overridden methods of the super class (ListTag)
        are put at the beginning of the list of methods returned, IF
        the derived class (ProductListTag) is loaded by the VM
        before the parent class (ListTag). Basically, in this
        version of Sun's VM, the method order is (roughly)
        alphabetical, except that in any base class, the methods
        overridden in the first derived class loaded before the base
        class appear first. This is generally true, not always
        true. This appears to also be true for JDK1.3.1.

        Note that because JPS depends on conficting-type getters and
        setters, it is not (short-term) possible to simply ban such
        getters and setters, and any proposed "fix" should reliably
        emulate the current "working" behavior. Because JPS is a
        net-accessible J2EE example, it wouldn't be surprising if
        this idiom appeared in other places.

        We can suggest a bug fix, based on our examination of the
        source for Introspector.java in JDK 1.3 and 1.3.1.
        There are two goals for a bug fix. First, Introspector
        behavior should not depend on the VM, VM version, or class
        load order. Second, some of the existing behavior should be
        retained. Overriding a bean accessor method in a derived
        class should make that one "take priority" (be scanned first
        in the base class). This behavior is exploited in Java Pet
        Store. It is unclear how widely this technique is
        "understood" and used in other applications.

        A good implementation-independent order is alphabetical sort
        on the method signature. This change could be inserted into
        getPublicDeclaredMethods. However, simple alphabetical sort
        is not enough to retain the overriding-wins behavior that is
        (sometimes) supported in the current world, and it breaks
        Java Pet Store (it needs to see the setters before the
        getters in the base class where they are defined, but g
        comes before s).

        There are two somewhat obvious alternatives to doing this.
        One is to order the base class methods so that ALL methods
        overriden in any derived class come first. This has some
        problems; the information is not readily available, changes
        over time as new classes are loaded, and is not necessarily
        well-defined (if two independent derived classes override
        different methods, which should come first?) A second order
        is to consider only the methods overridden in the derived
        classes for a particular introspection request; that is,
        those between startClass and stopClass. This has several
        advantages; it is independent of any other class loading,
        the information is available, and a "best order"
        (overridden-in-most-derived-first) is well-defined.

        Here's a potential implementation strategy for the second
        choice. It requires something that looks a hair expensive,
        namely a List of Sets, for each request to Introspector.
        However, because results for base classes are cached, this
        cost is incurred only once.

        1. getPublicDeclaredMethods should sort the methods after
           filtering out the non-public methods. This will guarantee
           that there are no more VM-specific method-order dependences.

        2. the private Introspector constructor (takes start class,
           stop class, flags) should be modified to take a fourth
           parameter that is a list of sets of overriding methods
           defined in derived classes (for this request).
           This list is augmented in each recursive call of the
           constructor as it walks up the super class chain by adding
           those overriding methods in the current class not overridden in any
           more-derived class, and the list is passed to getBeanInfo
           which in turn passes it to getTargetPropertyInfo.

           getTargetPropertyInfo makes multiple passes (instead of one)
           over the current class's public declared methods; in the
           first, it only considers those methods overriden in the
           most derived (start) class, and in the second pass it only
           considers those methods overridden in the next most
           derived class, etc, all the way up to the current class,
           where only those methods not overridden in any derived classes
           are considered. Notice that "most-derived" is relative
           to a particular introspection request -- there may be
           classes derived from the start class, but they are not
           considered (they may change over time, they may change
           concurrent with a request). Each scan will either need
           to make a scratch copy of the current class's public
           declared methods (so that methods can be filtered out
           as they are considered in order) or maintain a set
           (HashMap, or perhaps a bit vector) of methods that
           have already been considered so that they are not
           considered a second time.
         
           Within the sets of derived class methods, methods are
           identified by name, return type, and parameters, but not
           by their declaring class or throws list (same rules as
           those for identifying overriding methods)

        This solution is still not perfect, because two beans with a
        common base type may yield different properties depending on
        the order in which they are introspected. This can be
        avoided by turning off caching of parent properties, but
        that would take more time (this is basically a tradeoff,
        and there is the possibility of either not caching only
        in the case that there is a getter/setter type conflict,
        or the possibility of a more efficient algorithm for sorting
        out the overriding methods).

        For example, suppose there are two derived classes, B1 and
        B2 with a parent A. If A has conflicting getters and setters
        and B1 overrides the getters and B2 overrides the setters,
        the first class to compute the bean info will determine who
        wins in base class A, and that result will be cached and
        reused for the second class. However, this is better than
        what there is now, because the behavior will not vary from
        VM to VM, nor will it be affected by the load order of
        classes in the current VM. The proposed solution also has
        the advantage of being something with behavior that can be
        completely described in terms of the classes passed to
        Introspector, and the order of calls to Introspector.

        Test program follows:

        -------------------------------------------------------------
        package IntrospectorTest2;

        import java.beans.*;
        import java.lang.reflect.*;
        import java.io.*;
        import java.util.*;

        class Base {
          public int getFoo() { return 0; }
          public void setFoo(String x) {}
        }

        class X extends Base {
          public int getFoo() { return 0; }
        }

        class Y extends Base {
          public void setFoo(int x) {}
        }

        class Z extends Base {
          public void setFoo(String x) {}
        }

        // See also (probably root cause of)
        // 4277957, 4387842, 4407050, 4168833, 4408406, 4460327
        // See also closed bug 4144543
        // This can be seen running Java Pet Store 1.1.2 on alternate
        // platforms with different method orders.

        class Main {
          public static void main(String[] args) throws IOException {
            for (int i = 0; i < args.length; i+=2) {
              try {
        String clname = "IntrospectorTest2." + args[i+1];
        System.err.println("trying to load " + clname);
        Class c = Class.forName(clname);
        if (args[i].equalsIgnoreCase("test")) {
        test(c);
        } else if (args[i].equalsIgnoreCase("load")) {
        // intentionally left blank.
        } else {
        System.err.println("Expect to see pairs of parameters [load,test]
        [X,Y,Z]");
        }
              } catch (Throwable v) {
        v.printStackTrace();
              }
            }
          }
          
          static void test(Class c) {
            try {
              BeanInfo tagClassInfo = Introspector.getBeanInfo(c);
              PropertyDescriptor[] pd = tagClassInfo.getPropertyDescriptors();
              for(int i = 0; i < pd.length; i++) {
        String name = pd[i].getName();
        System.err.println(name + " displayName = " +
        pd[i].getDisplayName());
        System.err.println(name + " propertyType = " +
        pd[i].getPropertyType());
        System.err.println(name + " readMethod = " +
        pd[i].getReadMethod());
        System.err.println(name + " writeMethod = " +
        pd[i].getWriteMethod());
              }
            } catch (Throwable v) {
              v.printStackTrace();
            }
          }
        }
        ---------------------------------------------------------------
        (Review ID: 127180)
        ======================================================================

        Attachments

          Issue Links

            Activity

              People

                mdavidsosunw Mark Davidson (Inactive)
                bstrathesunw Bill Strathearn (Inactive)
                Votes:
                0 Vote for this issue
                Watchers:
                0 Start watching this issue

                Dates

                  Created:
                  Updated:
                  Resolved:
                  Imported:
                  Indexed: