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

wrong implementation of "inheritance" and "accessibility of methods"

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: P5 P5
    • 1.4.0
    • 1.3.0
    • specification
    • 1.4
    • generic
    • generic
    • Verified

      ------------------------------
       
      I hope my suggestions are useful.


      Norbert Schirmer



      Personal Remark:
      I only got to these sophisticated examples, which show some flaws in the Java
      Language Specification, due to the pedantic precision the theorem prover
      Isabelle enforces when I formally modeled the Java package/access concept.
      For such a large and complex specification as the Java Language Specification,
      the imprecise informal document is not strong enough to show up the subtle
      interaction between the class and the package concept. Therefor I think
      formal methods should be applied rigorously during the design of a new
      language. Maybe then the package/access concept would be a simpler one.

      ---- Glossary --------------------------------------------------------------

      I want to clarify some notions of the Java Language Specification and
      this bug report that may be confusing:

       <<<<<<<<<<<<<<<<<<< From the Java Language Specification >>>>>>>>>>>>>>>

        "code is responsible for the implementation"
        "class is involved in the implementation"

      A class is involved in the implementation of the class itself and of all
      its subclasses. Therefore the code in the body of a class is responsible
      for the implementation of the class itself and of all its subclasses.

        "class declares a member"

      The member is actually declared in the body of this class. It is not just
      inherited.

        "member of a class", "method of a class"

      The member/method can be inherited or actually be declared in the body of
      the class.

        "member of class C accessible from body of class S"
        "accessible to code"

      This notion is used to to determine whether access to a member of class C
      is allowed from body of class S or not. "Accessible to code" is an imprecise
      abbreviation for this. Note that possibly three different classes
      and their packages influence the behavior:
       - class C and its package
       - the class declaring the member and its package
       - the class S and its package

       <<<<<<<<<<<<<<<<<<< New in this bug report >>>>>>>>>>>>>>>>>>>>>>>>>

        "method of class C accessible from body of class S"

      This is a specialized version of "member of class C accessible..." for
      methods. It substitutes the general version in case the member actually is
      a method.

        "member of class C accessible for inheritance in subclass of C"

      This notion is used to determine whether the member can be inherited by
      a subclass of C or not. Also here possibly three different classes and their
      packages influence the behavior:
       - class C and its package
       - the class declaring the member and its package
       - the subclass of C and its package
      ----------------------------------------------------------------------------
      (Review ID: 130209)
      ======================================================================


      Name: boT120536 Date: 08/17/2001


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

      but also:

      java version "1.1.6"

      Hello,

      I have encountered some further inconsistency in the package/access
      concept as described in the Java Language Specification (Second Edition),
      besides the one I already reported in the bug report with bug id: 4485402.
      The JDK does not implement the specified behavior. However the implemented
      semantics seems to be the intended one. I want to describe two problems:

       1) Throughout the Java Language Specification the same notion of
          accessibility is used both to describe the legal access to a member and
          to determine which members are inherited. This is wrong. We have to
          distinguish both cases.

       2) Accessibility of members is defined uniformly for all kinds of members
          (class, interface, field, or method). This is wrong. Due to
          overriding methods actually have weaker access restrictions.
          
      I will explain these problems in detail and give some examples. I also
      want to propose some new definitions which hopefully will eliminate these
      problems. I have added a small glossary at the end, to clarify some notions
      which sound very similar and may be confusing.

        1) Accessibility for inheritance
           -----------------------------

      Let's first have a look at the definition of inheritance:

      ----------- Java Language Specification-------------------------------------
      8.4.6 Inheritance, Overriding, and Hiding
       A class inherits from its direct superclass and direct superinterfaces
      all the non-private methods (whether abstract or not) of the superclass and
      superinterfaces that are accessible to code in the class and are neither
      overridden (8.4.6.1) nor hidden (8.4.6.2) by a declaration in the class.
      ----------------------------------------------------------------------------

      I want to focus on the case, where no new declaration of the member is given,
      so that overriding or hiding does not occur. In that case
      all the accessible members are inherited.
      (By the way, the distinction between non-private and accessible members to
      describe inheritance seems to be completely unnecessary, since private
      members aren't accessible in a subclass. So it would suffice to talk only
      about accessible members here.)

      This is what the Java Language Specification says about accessibility:

      ----------- Java Language Specification-------------------------------------
        6.6.1 Determining Accessibility
        A package is always accessible. If a class or interface type is declared
        public, then it may be accessed by any code, provided that the compilation
        unit (7.3) in which it is declared is observable. If a top level class or
        interface type is not declared public, then it may be accessed only from
        within the package in which it is declared. An array type is accessible
        if and only if its element type is accessible.
        
        A member (class, interface, field, or method) of a reference
        (class, interface, or array) type or a constructor of a class type is
        accessible only if the type is accessible and the member or constructor
        is declared to permit access:
           If the member or constructor is declared public, then access is
           permitted.
           All members of interfaces are implicitly public.

           Otherwise, if the member or constructor is declared protected,
           then access is permitted only when one of the following is true:
              
               Access to the member or constructor occurs from within the package
               containing the class in which the protected member or constructor is
               declared.

               Access is correct as described in 6.6.2.

           Otherwise, if the member or constructor is declared private, then access
           is permitted if and only if it occurs within the body of the top level
           class (7.6) that encloses the declaration of the member.

           Otherwise, we say there is default access, which is permitted only when
           the access occurs from within the package in which the type is declared.
      ----------------------------------------------------------------------------

      Let me first point out a detail that can easily be overseen. For default
      (package) access and for protected access, access from within the package is
      allowed. But the package referred to is not necessarily the package of the
      class we currently look at. The Java Language Specification refers to the
      package of the class "declaring" the member.
      This is crucial since an inherited member can be declared in a different
      package.

      Let me now focus on the critical case were the inconsistency occurs. Its the
      sub-case of protected access described in an extra section (6.6.2). The member
      is declared protected and is accessed from outside the package:

      ----------- Java Language Specification-------------------------------------
      6.6.2 Details on protected Access
       A protected member or constructor of an object may be accessed from
      outside the package in which it is declared only by code that is
      responsible for the implementation of that object.

       6.6.2.1 Access to a protected Member
      Let C be the class in which a protected member m is declared. Access is
      permitted only within the body of a subclass S of C. In addition, if Id
      denotes an instance field or instance method, then:

        If the access is by a qualified name Q.Id, where Q is an ExpressionName,
       then the access is permitted if and only if the type of the expression Q is S
       or a subclass of S.

        If the access is by a field access expression E.Id, where E is a
       Primary expression, or by a method invocation expression E.Id(. . .), where
       E is a Primary expression, then the access is permitted if and only if the
       type of E is S or a subclass of S.
      ----------------------------------------------------------------------------

      Since it is hard to understand what exactly "responsible for implementation"
      means there is an example in the specification to clarify this aspect:

      ----------- Java Language Specification-------------------------------------
      6.6.7 Example: protected Fields, Methods, and Constructors
      Consider this example, where the points package declares:

      package points;
      public class Point {
              protected int x, y;
              void warp(threePoint.Point3d a) {
                      if (a.z > 0) // compile-time error: cannot access a.z
                              a.delta(this);
              }
      }
       
      and the threePoint package declares:

      package threePoint;
      import points.Point;
      public class Point3d extends Point {
              protected int z;
              public void delta(Point p) {
                      p.x += this.x; // compile-time error: cannot access p.x
                      p.y += this.y; // compile-time error: cannot access p.y
              }
              public void delta3d(Point3d q) {
                      q.x += this.x;
                      q.y += this.y;
                      q.z += this.z;
              }
      }

      which defines a class Point3d. A compile-time error occurs in the method delta
      here: it cannot access the protected members x and y of its parameter p,
      because while Point3d (the class in which the references to fields x and y
      occur) is a subclass of Point (the class in which x and y are declared), it
      is not involved in the implementation of a Point (the type of the
      parameter p). The method delta3d can access the protected members of its
      parameter q, because the class Point3d is a subclass of Point and is
      involved in the implementation of a Point3d.
      ----------------------------------------------------------------------------

      So a class is involved in the implementation of the class itself and of all
      its subclasses. Or from a more operational point of view, if a class has
      a protected member (independent if this member is declared in the class
      itself or is inherited from a superclass) the class can access this member
      from itself and from all of its subclasses. So on the other hand, if the
      protected member is inherited from a superclass the class is not allowed to
      access the member in the superclass. Let me illustrate this with a small
      example:
      Let A, B, C, D be classes. B extends A; C extends B; D extends C;
      A declares a protected member m. The member m is inherited by all the
      subclasses B, C and D.
      Lets focus on class B. Class B is is allowed to access member m from
      Objects of class B, C or D but not from Objects of class A.

           | --> B is allowed to access m from B, C, D but not from A
           |
        A B C D
           
      Lets return to the example of Point and Point3d.
      The crucial point is that the fields x and y of the superclass Point are not
      accessible in the subclass Point3d in the expressions p.x respectively p.y
      (inside the method delta). Since p has type Point, Point.x and Point.y are
      not accessible from Point3d.
      But with this in mind there is no reason why the members x and y of the
      superclass Point should be inherited by the subclass Point3d. Since
      accessibility of Point.x and Point.y is required for inheritance
      (refer to 8.4.6, cited above).

      But the intended behavior of a protected member is, that it is inherited by
      a subclass. Therefore to describe inheritance appropriately we must take a
      different definition of accessibility as a basis for inheritance.

       Accessible for inheritance:

      A member declared in class A is "accessible for inheritance" in a subclass C
      if
       Either
       1) a) class A is accessible from class C and
          b) the member of A is declared protected or public or if it is declared
             with default (package) access, the package of class C is the same as
             the package of class A. (If the member of A is declared with private
             access it is not accessible for inheritance)
       2) There is a intermediate class B (distinct from A and C), B is subclass of
          A and C is subclass of B and all the following conditions hold:
           a) class B is accessible from class C and
           b) the member of class A is accessible for inheritance in class B (so it
              is indeed inherited by class B if no overriding or hiding occurs) and
           c) the member of A is declared protected or public or if it is declared
              with default (package) access, the package of class C is the same as
              the package of class B. (If the member of A is declared with private
              access it is not accessible for inheritance)

      Condition 1 defines the obvious intended semantics for inheritance.
      All public and protected members are inherited and members with default
      (package) access are only inherited inside the package.
      Condition 2 is designed for the case that class A is not accessible
      directly (Condition 1 a) in class C, for example if class A is non public and
      class C is in a different package than A, there has to be an intermediate
      public class B which makes the members of A visible to C. This is in analogy
      to my correction of overriding proposed in bug report 4485402.

      This definition "accessible for inheritance" should replace the notion
      "accessible to code" in 8.4.6.

        2) Special accessibility for methods
           ---------------------------------

      Due to overriding there are some cases where methods are accessible but other
      members (especially fields) would not be accessible. This has to be added
      in the accessibility definition.
      Consider the following example:

      package APackage;
      public class A
      {
          protected void foo() {}
      }

      package BPackage;
      public class B extends A
      {
          protected void foo() {}
          protected void bar() {}
      }

      package APackage;
      import BPackage.B;
      public class C
      {
          public void call()
          {
              B b = new B();
              b.foo(); // allowed
              b.bar(); // not allowed
          }
      }


      Class B is a subclass of A and is declared in a different package than A.
      The class C is declared in the same package as A. All methods of A
      respectively B are declared with protected access. Since class C is neither
      subclass of A nor B it intuitively can only access the protected members of
      A but not those of B, since C and A are in the same package
      (refer to 6.6.1 cited above).
      We can't access B.bar() from C since B is in a different package than C.
      But C is allowed to access B.foo(). Why?
      Imagine that B would not override A.foo(). Then it would inherit foo() from
      class A. Since we are allowed to access A.foo() from C we expect that we can
      access the inherited version of foo() in B, too, because B.foo() is not
      freshly declared in B but inherited from A, where it is actually declared.
      The notion of accessibility of a protected member in this case is
      described with respect to the declaring class not the class inheriting the
      member.
      But when B overrides foo(), B freshly declares foo(). So strictly referring
      to the specification foo() should not be accessible from C as this is the
      case for method bar(). But intuitively we expect a method to be accessible
      if it overrides an accessible method.
      So we have to extend the general definition of accessibility for all members
      with a special case for methods. Whenever we want to express a
      "method is accessible from..." in the Java Language Definition we should
      take this specialized version not the general one:

      A method m of a class C is accessible from a class S if either
       1) The method is accessible due to existing definition of accessibility
          (Java Language Specification 6.6.1) or
       2) All the following conditions hold:
          a) The class C is accessible from S
          b) there is an old method m' of a class A with
             i) C is a subclass of A and
             ii) method m overrides method m' and
             iii) method m' of class A is accessible from class S

      Note that m must not necessarily be declared in class C (it can also be
      inherited from a superclass) and that m' must not necessarily be declared in
      class A (it can also be inherited from a superclass). This is crucial, since
      the declaring classes of m respectively m' must not necessarily be
      accessible from S (e.g. if they are non public and reside in a different
      package than S).

      Since I proposed a different definition of overriding in bug report 4485402
      what exactly do I refer to in case 2 a ii?

      If we take the definition of "accessible for inheritance" - I just introduced
      above - as a basis for overriding (instead of the ordinary accessibility
      notion) we can take the original definition of overriding of the
      Java Language Specification. The problematic case, that a method of an
      unaccessible class becomes accessible through to inheritance is already
      captured in case 2 of "accessible for inheritance".

      ----------- Java Language Specification---(modified)------------------------
        8.4.6.1 Overriding (by Instance Methods)
        An instance method m1 declared in a class C overrides another method
        with the same signature, m2, declared in class A if both
        1. C is a subclass of A.
        2. Either
           a) m2 is non-private and "accessible for inheritance" from C, or
           b) m1 overrides a method m3, m3 distinct from m1, m3 distinct from m2,
              such that m3 overrides m2.
      ----------------------------------------------

            gbrachasunw Gilad Bracha (Inactive)
            bonealsunw Bret O'neal (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: