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.
Summary: | Call Hierarchy ignores statically known instance type | ||
---|---|---|---|
Product: | java | Reporter: | matthies <matthies> |
Component: | Refactoring | Assignee: | Svata Dedic <sdedic> |
Status: | NEW --- | ||
Severity: | normal | ||
Priority: | P3 | ||
Version: | 8.1 | ||
Hardware: | PC | ||
OS: | Windows 7 | ||
Issue Type: | DEFECT | Exception Reporter: |
Description
matthies
2016-08-02 15:56:47 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(). 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. 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 (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. |