This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

Bug 267409 - Call Hierarchy ignores statically known instance type
Summary: Call Hierarchy ignores statically known instance type
Status: NEW
Alias: None
Product: java
Classification: Unclassified
Component: Refactoring (show other bugs)
Version: 8.1
Hardware: PC Windows 7
: P3 normal with 1 vote (vote)
Assignee: Svata Dedic
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-08-02 15:56 UTC by matthies
Modified: 2017-05-17 08:41 UTC (History)
0 users

See Also:
Issue Type: DEFECT
Exception Reporter:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description matthies 2016-08-02 15:56:47 UTC
Example:

    public class Caller
    {
        void method1() { new Derived1().foo(); }

        void method2() { new Derived2().foo(); }
    }

    abstract class Base
    {
        void foo() { bar(); }

        abstract void bar();
    }

    class Derived1 extends Base { @Override void bar() { } }

    class Derived2 extends Base { @Override void bar() { } }

Invoking Call Hierarchy on Derived1#bar() results in a tree with both Caller#method1() and Caller#method2() (via Base#foo()), although it is trivially clear that only method1() can result in calling Derived1#bar().

Apparently Call Hierarchy doesn't use its knowledge of the type of 'this' for subsequent method invocations on the current instance.
Comment 1 matthies 2016-08-02 16:07:33 UTC
Note that this can be generalized to any method arguments (as 'this' can be considered to be an implicit method argument). For example:

    public class Caller
    {
        void method1() { foo(new Derived1()); }

        void method2() { foo(new Derived2()); }

        void foo(Base base) { base.bar(); }
    }

    abstract class Base
    {
        abstract void bar();
    }

    class Derived1 extends Base { @Override void bar() { } }

    class Derived2 extends Base { @Override void bar() { } }

When tracing calls to Derived1#bar(), it is clear that the method argument of Caller#foo(Base) is really a Derived1 object, and therefore only method1() is a caller, but not method2().
Comment 2 matthies 2016-08-02 16:42:46 UTC
The problem is actually worse than I thought. Consider the following example:

    public class Caller
    {
        void method1() { new Derived1().bar(); }

        void method2() { new Derived2().bar(); }
    }

    abstract class Base
    {
        abstract void bar();
    }

    class Derived1 extends Base
    {
        void bar() { new Derived2().bar(); }
    }

    class Derived2 extends Base
    {
        void baz() { }

        void bar() { baz(); }
    }

Invoking Call hierarchy on Derived2#baz() results in the following hierarchy:

baz() :: Derived2
`- bar() :: Derived2
   |- method1() :: Caller
   |- method2() :: Caller
   `- bar() :: Derived1

Besides Derived2#bar() being calle by method1() is not true, there is no indication that Derived1#bar() (last line) is being called by method1(). Apparently Call Hierarchy is mixing up Derived1#bar() and Derived2#bar() because they both implement the same abstract method. Commenting out the abstract bar() method in Base (or removing the Base class alltogether) results in the correct call hierarchy:

baz() :: Derived2
`- bar() :: Derived2
   |- method2() :: Caller
   `- bar() :: Derived1
      `- method1() :: Caller

In this example, the result should not depend on whether Base#bar() exists or not, because none of the calls are made via Base.
Comment 3 Svata Dedic 2016-08-03 21:55:00 UTC
Note that while new Derived1().foo() can be analyzed so it definitely does not cll Derived2#foo, a call through another member method is most probably out of scope of the call hierarchy
Comment 4 matthies 2016-08-03 22:47:42 UTC
(In reply to Svata Dedic from comment #3)
> Note that while new Derived1().foo() can be analyzed so it definitely does
> not cll Derived2#foo, a call through another member method is most probably
> out of scope of the call hierarchy

What I would expect is that Call Hierarchy tracks the most-derived type of the method arguments (including 'this') that it encounters when tracing a call path.

Example:

    class Base { void callee() { } }
    
    class Derived1 extends Base { void callee() { } }
    
    class Derived2 extends Base { void callee() { } }
    
    class Caller
    {
        void caller1(Derived1 d) { caller(d); }

        void caller2(Derived2 d) { caller(d); }

        void intermediate(Base b) { b.callee(); }
    }

Consider the following two call paths:

    Caller#caller1 -> Caller#intermediate -> Derived1#callee
    Caller#caller2 -> Caller#intermediate -> Derived2#callee

When tracing these paths (from caller to callee or vice versa), one has to track that the argument b of Caller#intermediate(Base) is known to be a Derived1 for one path and a Derived2 for the other path. That is, the two nodes for Caller#intermediate have to be annotated with the more specific type of b that is known along each path. If that information is not tracked, then the two paths will be conflated into one graph:

    Caller#caller1 -.                            ,-> Derived1#callee
                     \                          /
                      }-> Caller#intermediate -{
                     /                          \
    Caller#caller2 -’                            `-> Derived2#callee

...yielding the following two additional paths although these can never happen:

    Caller#caller1 -> Caller#intermediate -> Derived2#callee
    Caller#caller2 -> Caller#intermediate -> Derived1#callee

Implementing that logic for a chain of more than one intermediate call should not be more difficult than implementing it for a single intermediate call.