Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Archie L . Cobbs
On Fri, 13 Jan 2023 00:57:14 GMT, Archie L. Cobbs  wrote:

>> Ok - I thought false negative was the thing to absolutely avoid - and that 
>> was the no. 1 concern. I think a possible approach to keep both the 
>> filtering and the code more or less similar to what you have, is to save the 
>> type of the expression in the ExprRef. Then, I believe that, at the end of 
>> scan() you can just get whatever type is there, and check that it's the 
>> correct one, if not drop it.
>
>>  Ok - I thought false negative was the thing to absolutely avoid - and that 
>> was the no. 1 concern.
> 
> You're right. I think at the time I reasoned that it would be unusual enough 
> for the type of an expression to start as an instanceof X, then change to not 
> being an instanceof X, and then change back, that it was worth it to go ahead 
> and filter these out. But admittedly that was based on intuition, not science.
> 
>> I think a possible approach to keep both the filtering and the code more or 
>> less similar to what you have, is to save the type of the expression in the 
>> ExprRef. Then, I believe that, at the end of scan() you can just get 
>> whatever type is there, and check that it's the correct one, if not drop it.
> 
> That's a nice idea... thanks. I'll play around with it some.

I was curious how much of a difference this type filtering makes, so I built 
the JDK with and without it.

The results were identical except for one case:

package java.lang.invoke;
...
public abstract sealed class CallSite permits ConstantCallSite, 
MutableCallSite, VolatileCallSite {
...
CallSite(MethodType targetType, MethodHandle createTargetHook) throws 
Throwable {
this(targetType); // need to initialize target to make CallSite.type() 
work in createTargetHook
ConstantCallSite selfCCS = (ConstantCallSite) this;
MethodHandle boundTarget = (MethodHandle) 
createTargetHook.invokeWithArguments(selfCCS);
setTargetNormal(boundTarget); // ConstantCallSite doesn't publish 
CallSite.target
UNSAFE.storeStoreFence(); // barrier between target and isFrozen updates
}

When we do type filtering, `(ConstantCallSite) this` causes the 'this' 
reference to be discarded so no leak is reported on the next line for 
`invokeWithArguments(selfCCS)`. Just a side note, there is also a leak on the 
next line because final method `setTargetNormal()` invokes 
`MethodHandleNatives.setCallSiteTargetNormal(this, newTarget)`, so a leak does 
get reported anyway even with type filtering.

When type filtering is disabled, we report a leak at 
`invokeWithArguments(selfCCS)` - which is accurate.

So what did we learn?

First it looks like type filtering has very minimal effect. I think this is 
because it requires some version of two casts in a row in and out of type 
compatibility, and this is probably very rare.

But looking at the one data point we do have, the type filtering did in fact 
cause a false negative.

And when building the entire JDK, it causes zero net new false positives.

So to me this is evidence that we should just remove the type filtering 
altogether...

@vicente-romero-oracle your thoughts?

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Archie L . Cobbs
On Fri, 13 Jan 2023 16:06:04 GMT, Maurizio Cimadamore  
wrote:

>>> Something seems to be up with the lint description for this-escape - 
>>> compare this:
>> 
>> Oops, will fix - thanks.
>
>> The decision was to go with drawing the "warning boundary" at the 
>> compilation unit. The reasoning is that (a) this aligns with the compiler's 
>> "knowledge boundary", i.e., we can know for sure from code inspection, and 
>> also (b) focuses the warning on the particularly pernicious aspect of these 
>> bugs, which is that they arise from the non-obvious interaction among two or 
>> more files
> 
> Sorry for being picky - you mention this "compilation unit" boundary before, 
> but this is not really the reason here. Note that in my example B constructor 
> calls out to a static method that is "outside" the boundary. The reason as to 
> why my example is not flagged is simply that "escaping" is defined as 
> "escaping into a subclass method", not just "escaping from the constructor 
> (into some other compilation unit)".

Oops, you're right, I answered the wrong question so to speak.

The "must involve a subclass" requirement is another dimension on which a 
"boundary" was declared. This was also part of the original discussion.

So yes the requirement is: "requires involvement of a subclass" **AND** "that 
subclass lives in a separate compilation unit".

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Maurizio Cimadamore
On Fri, 13 Jan 2023 15:08:59 GMT, Archie L. Cobbs  wrote:

>>> I guess I was confused because, while subclasses are a particularly sneaky 
>>> case where uninitialized values can show up, the above leak seems 
>>> potentially dangerous as well...
>> 
>> Yes - and this very question did come up in the discussions around this 
>> warning (see amber-dev).
>> 
>> The decision was to go with drawing the "warning boundary" at the 
>> compilation unit. The reasoning is that (a) this aligns with the compiler's 
>> "knowledge boundary", i.e., we can know for sure from code inspection, and 
>> also (b) focuses the warning on the particularly pernicious aspect of these 
>> bugs, which is that they arise from the non-obvious interaction among two or 
>> more files - even when looking at any single one of those files, there 
>> doesn't seem to be any apparent problem. In other words, we decided "not to 
>> try to save any single source code from itself".
>> 
>> But I think it's still an interesting question. Maybe experience will 
>> provide more guidance over time.
>
>> Something seems to be up with the lint description for this-escape - compare 
>> this:
> 
> Oops, will fix - thanks.

> The decision was to go with drawing the "warning boundary" at the compilation 
> unit. The reasoning is that (a) this aligns with the compiler's "knowledge 
> boundary", i.e., we can know for sure from code inspection, and also (b) 
> focuses the warning on the particularly pernicious aspect of these bugs, 
> which is that they arise from the non-obvious interaction among two or more 
> files

Sorry for being picky - you mention this "compilation unit" boundary before, 
but this is not really the reason here. Note that in my example B constructor 
calls out to a static method that is "outside" the boundary. The reason as to 
why my example is not flagged is simply that "escaping" is defined as "escaping 
into a subclass method", not just "escaping from the constructor (into some 
other compilation unit)".

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Archie L . Cobbs
On Fri, 13 Jan 2023 15:08:43 GMT, Archie L. Cobbs  wrote:

>> Something seems to be up with the lint description for this-escape - compare 
>> this:
>> 
>> 
>>   serial   Warn about Serializable classes that do not have a 
>> serialVersionUID field. 
>>  Also warn about other suspect declarations in 
>> Serializable and Externalizable classes and interfaces.
>> 
>> with this:
>> 
>> 
>>   this-escape  Warn when a constructor invokes a method that could 
>> be overriden in a subclass;
>> such a method would execute before the subclass constructor completes its 
>> initialization.
>> 
>> 
>> Indentation seems to be missing, which causes readability issues in the 
>> `--help-lint` output.
>
>> I guess I was confused because, while subclasses are a particularly sneaky 
>> case where uninitialized values can show up, the above leak seems 
>> potentially dangerous as well...
> 
> Yes - and this very question did come up in the discussions around this 
> warning (see amber-dev).
> 
> The decision was to go with drawing the "warning boundary" at the compilation 
> unit. The reasoning is that (a) this aligns with the compiler's "knowledge 
> boundary", i.e., we can know for sure from code inspection, and also (b) 
> focuses the warning on the particularly pernicious aspect of these bugs, 
> which is that they arise from the non-obvious interaction among two or more 
> files - even when looking at any single one of those files, there doesn't 
> seem to be any apparent problem. In other words, we decided "not to try to 
> save any single source code from itself".
> 
> But I think it's still an interesting question. Maybe experience will provide 
> more guidance over time.

> Something seems to be up with the lint description for this-escape - compare 
> this:

Oops, will fix - thanks.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Archie L . Cobbs
On Fri, 13 Jan 2023 11:08:33 GMT, Maurizio Cimadamore  
wrote:

>> Perhaps my confusion might come from the name `this-escape` of the lint 
>> warning - which seems overpromising in this respect. But I looked at the 
>> description of the lint warning using `javac --help-lint` and I got this:
>> 
>> 
>> this-escape  Warn when a constructor invokes a method that could 
>> be overriden in a subclass;
>> 
>> 
>> Which indeed aligns well with what this PR is doing. So that's ok.
>
> Something seems to be up with the lint description for this-escape - compare 
> this:
> 
> 
>   serial   Warn about Serializable classes that do not have a 
> serialVersionUID field. 
>  Also warn about other suspect declarations in 
> Serializable and Externalizable classes and interfaces.
> 
> with this:
> 
> 
>   this-escape  Warn when a constructor invokes a method that could be 
> overriden in a subclass;
> such a method would execute before the subclass constructor completes its 
> initialization.
> 
> 
> Indentation seems to be missing, which causes readability issues in the 
> `--help-lint` output.

> I guess I was confused because, while subclasses are a particularly sneaky 
> case where uninitialized values can show up, the above leak seems potentially 
> dangerous as well...

Yes - and this very question did come up in the discussions around this warning 
(see amber-dev).

The decision was to go with drawing the "warning boundary" at the compilation 
unit. The reasoning is that (a) this aligns with the compiler's "knowledge 
boundary", i.e., we can know for sure from code inspection, and also (b) 
focuses the warning on the particularly pernicious aspect of these bugs, which 
is that they arise from the non-obvious interaction among two or more files - 
even when looking at any single one of those files, there doesn't seem to be 
any apparent problem. In other words, we decided "not to try to save any single 
source code from itself".

But I think it's still an interesting question. Maybe experience will provide 
more guidance over time.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Maurizio Cimadamore
On Fri, 13 Jan 2023 10:58:33 GMT, Maurizio Cimadamore  
wrote:

>> Caring about the proper initialization of any class in the _current_ 
>> compilation unit is an explicit non-goal.
>> 
>> We only care about bugs where a superclass and subclass are in separate 
>> compilation units.
>
> So, to clarify, in this case:
> 
> 
> import java.util.*;
> 
> class B {
> final Object ref;
> 
>  private B(Object ref) {
>   Foo.consume(this);
>   this.ref = ref;
>   }
>  }
> 
> 
> Even though `this` leaks to a method clearly before it is fully initialized, 
> we do not care because there can be no subclass involved observing this. I 
> guess I was confused because, while subclasses are a particularly sneaky case 
> where uninitialized values can show up, the above leak seems potentially 
> dangerous as well - we're effectively leaking a class whose final field has 
> not been initialized!
> 
> That said, if that was discussed, and it was decided for the warning not to 
> deal with this case, that's ok.

Perhaps my confusion might come from the name `this-escape` of the lint warning 
- which seems overpromising in this respect. But I looked at the description of 
the lint warning using `javac --help-lint` and I got this:


this-escape  Warn when a constructor invokes a method that could be 
overriden in a subclass;


Which indeed aligns well with what this PR is doing. So that's ok.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Maurizio Cimadamore
On Fri, 13 Jan 2023 11:05:51 GMT, Maurizio Cimadamore  
wrote:

>> So, to clarify, in this case:
>> 
>> 
>> import java.util.*;
>> 
>> class B {
>> final Object ref;
>> 
>>  private B(Object ref) {
>>   Foo.consume(this);
>>   this.ref = ref;
>>   }
>>  }
>> 
>> 
>> Even though `this` leaks to a method clearly before it is fully initialized, 
>> we do not care because there can be no subclass involved observing this. I 
>> guess I was confused because, while subclasses are a particularly sneaky 
>> case where uninitialized values can show up, the above leak seems 
>> potentially dangerous as well - we're effectively leaking a class whose 
>> final field has not been initialized!
>> 
>> That said, if that was discussed, and it was decided for the warning not to 
>> deal with this case, that's ok.
>
> Perhaps my confusion might come from the name `this-escape` of the lint 
> warning - which seems overpromising in this respect. But I looked at the 
> description of the lint warning using `javac --help-lint` and I got this:
> 
> 
> this-escape  Warn when a constructor invokes a method that could 
> be overriden in a subclass;
> 
> 
> Which indeed aligns well with what this PR is doing. So that's ok.

Something seems to be up with the lint description for this-escape - compare 
this:


  serial   Warn about Serializable classes that do not have a 
serialVersionUID field. 
 Also warn about other suspect declarations in 
Serializable and Externalizable classes and interfaces.

with this:


  this-escape  Warn when a constructor invokes a method that could be 
overriden in a subclass;
such a method would execute before the subclass constructor completes its 
initialization.


Indentation seems to be missing, which causes readability issues in the 
`--help-lint` output.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-13 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 21:04:09 GMT, Archie L. Cobbs  wrote:

>> but what if `m` is a static method in a separate compilation unit? Should it 
>> be able to observe a partially initialized Foo?
>
> Caring about the proper initialization of any class in the _current_ 
> compilation unit is an explicit non-goal.
> 
> We only care about bugs where a superclass and subclass are in separate 
> compilation units.

So, to clarify, in this case:


import java.util.*;

class B {
final Object ref;

 private B(Object ref) {
  Foo.consume(this);
  this.ref = ref;
  }
 }


Even though `this` leaks to a method clearly before it is fully initialized, we 
do not care because there can be no subclass involved observing this. I guess I 
was confused because, while subclasses are a particularly sneaky case where 
uninitialized values can show up, the above leak seems potentially dangerous as 
well - we're effectively leaking a class whose final field has not been 
initialized!

That said, if that was discussed, and it was decided for the warning not to 
deal with this case, that's ok.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 21:47:28 GMT, Maurizio Cimadamore  
wrote:

>  Ok - I thought false negative was the thing to absolutely avoid - and that 
> was the no. 1 concern.

You're right. I think at the time I reasoned that it would be unusual enough 
for the type of an expression to start as an instanceof X, then change to not 
being an instanceof X, and then change back, that it was worth it to go ahead 
and filter these out. But admittedly that was based on intuition, not science.

> I think a possible approach to keep both the filtering and the code more or 
> less similar to what you have, is to save the type of the expression in the 
> ExprRef. Then, I believe that, at the end of scan() you can just get whatever 
> type is there, and check that it's the correct one, if not drop it.

That's a nice idea... thanks. I'll play around with it some.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 19:12:27 GMT, Archie L. Cobbs  wrote:

>> Uhm. Turns out I probably did not understand the filter correctly, and now 
>> I'm more dubious about what it actually does. Say you have this hierarchy:
>> 
>> 
>> interface A { }
>> class B {
>>  B() {
>>   A a = (A)this;
>>   ...
>>   }
>>  }
>>  class C extends B implements A { }
>>  ```
>> 
>> Pathological case, I know. But the filtering will end up dropping the 
>> expression Ref on the floor, right? (because B and A are unrelated).
>
>> But the filtering will end up dropping the expression Ref on the floor, 
>> right? (because B and A are unrelated).
> 
> Ah, I see what you mean.
> 
> Here's a more complete example:
> 
> public class CastLeak {
> 
> public CastLeak() {
> ((CastLeak)(Runnable)this).mightLeak();
> }
> 
> public void mightLeak() {
> }
> }
> 
> That would be a leak for any subclass that implements `Runnable`. Yet no 
> warning is generated.
> 
> So the filtering by expression type is going to potentially create false 
> negatives. But it also eliminates a bunch of false positives. And the false 
> negatives are probably all somewhat pathological like the example above.
> 
> So I still think it's worth doing.

Ok - I thought false negative was the thing to absolutely avoid - and that was 
the no. 1 concern. I think a possible approach to keep both the filtering and 
the code more or less similar to what you have, is to save the type of the 
expression in the ExprRef. Then, I believe that, at the end of scan() you can 
just get whatever type is there, and check that it's the correct one, if not 
drop it.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 21:28:12 GMT, Archie L. Cobbs  wrote:

>> My point is about who puts ThisRef in the set to begin with. It seems to me 
>> that ThisRef is put there at the start of a method analysis. After which, 
>> there's several code points where we say "if there's a direct ThisRef in the 
>> set, do this, otherwise, if there's an indirect ThisRef in the set, do 
>> that". But the ThisRef (whether indirect or direct) seems set once and for 
>> all (when we start the analysis, and then inside visitApply). 
>> 
>> There is also this bit in `visitReference`:
>> 
>> 
>> case SUPER:
>> if (this.refs.contains(ThisRef.direct()))
>> receiverRefs.add(ThisRef.direct());
>> if (this.refs.contains(ThisRef.indirect()))
>> receiverRefs.add(ThisRef.indirect());
>> break;
>> 
>> 
>> But this doesn't change what I'm saying - there seems to be a general 
>> property when we execute this analysis which says whether the current 
>> execution context has a direct `this` or not. This seems to me better 
>> captured with a boolean, which is then fed back to all the other various 
>> downstream ref creation.
>> 
>> The main observation, at least for me, is that the code unifies everything 
>> under refs, when in reality there are different aspects:
>> 
>> * the set of variables that can point to this, whether directly or 
>> indirectly (this is truly a set)
>> * whether the current context has a direct or indirect this (this seems more 
>> a flag to me)
>> * whether the expression on top of stack is direct/indirect this reference 
>> or not (again, 3 possible values here) - but granted, there `depth` here to 
>> take into account, so you probably end up with a set (given that we don't 
>> want to model a scope with its own set)
>> 
>> When reading the code, seeing set expression like containment checks or 
>> removals for things that doesn't seem to be truly sets (unless I'm missing 
>> something) was genuinely confusing me.
>
> I get what you're saying - it seems silly to model what is essentially a 
> fixed, boolean property with the membership of a singleton in a set field, 
> rather than with a simple boolean field.
> 
> There is a conceptual trade-off though... a lot of the code relates to 
> converting `Ref`'s from one type to another. For example, as pointed out 
> above, a method invocation might convert a `ExprRef` to a `ThisRef`, then to 
> a `ReturnRef`'s, etc. Having these things all be considered part of the same 
> family helps conceptually. The fact that a direct `ThisRef` is a singleton is 
> just a coincidence in this way of looking at things.
> 
> The benefit is the simplicity of being able to think of the data model as 
> "just a set of references".
> 
> For example, methods like `RefSet.replaceExprs()` become less elegant (or 
> basically impossible) if there have to be special cases for each type of 
> reference, whereas currently we can do clean stuff like this:
> 
> 
> @Override
> public void visitReturn(JCReturn tree) {
> scan(tree.expr);
> refs.replaceExprs(depth, ReturnRef::new);
> }
> 
> 
> But I'm also realizing now that several places can be cleaned up by taking 
> this event further. E.g., we should replace this code:
> 
> if (refs.contains(ThisRef.direct()))
> outerRefs.add(OuterRef.direct());
> if (refs.contains(ThisRef.indirect()))
> outerRefs.add(OuterRef.indirect());
> 
> with something like this:
> 
> refs.mapInto(outerRefs, ThisRef.class, OuterRef::new);
> 
> 
> I will go through and refactor to do that and clean things up a little more.
> 
>> When reading the code, seeing set expression like containment checks or 
>> removals for things that doesn't seem to be truly sets (unless I'm missing 
>> something) was genuinely confusing me.
> 
> Probably my fault for not providing better documentation of the overall "set 
> of references" conceptual approach. FWIW I added a little bit more in 
> f83a9cf0.

> ```java
> ```java
>  if (refs.contains(ThisRef.direct()))
> outerRefs.add(OuterRef.direct());
> if (refs.contains(ThisRef.indirect()))
> outerRefs.add(OuterRef.indirect());
> ```
> 
> 
> 
>   
> 
> 
>   
> 
> 
> 
>   
> with something like this:
> ```java
> refs.mapInto(outerRefs, ThisRef.class, OuterRef::new);
> ```
> ```

This sounds like a good idea, that idiom is quite widespread, so it should help 
avoiding repetition.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 19:01:10 GMT, Maurizio Cimadamore  
wrote:

>> The code you quoted has nothing specifically to do with method invocations. 
>> This code is simply handing the evaluation of the expressions `this` and 
>> `super`. For example, `this` could just be a parameter we're passing to 
>> another method.
>> 
>> When `this` or `super` expressions are evaluated, the thing left on top of 
>> the stack is a direct/indirect reference (i.e., an `ExprRef`) exactly when 
>> the there is a direct/indirect reference of type `ThisRef` in the current 
>> reference set.
>> 
>> In cases where `this` is then "invoked", e.g., `this()`, the `ExprRef` (top 
>> of Java stack) becomes the method receiver, and when the method is "invoked" 
>> it turns back into a `ThisRef` (see earlier question).
>> 
>> Regarding the optimization you mention, in light of the above I'm not sure 
>> it would still work.
>
> My point is about who puts ThisRef in the set to begin with. It seems to me 
> that ThisRef is put there at the start of a method analysis. After which, 
> there's several code points where we say "if there's a direct ThisRef in the 
> set, do this, otherwise, if there's an indirect ThisRef in the set, do that". 
> But the ThisRef (whether indirect or direct) seems set once and for all (when 
> we start the analysis, and then inside visitApply). 
> 
> There is also this bit in `visitReference`:
> 
> 
> case SUPER:
> if (this.refs.contains(ThisRef.direct()))
> receiverRefs.add(ThisRef.direct());
> if (this.refs.contains(ThisRef.indirect()))
> receiverRefs.add(ThisRef.indirect());
> break;
> 
> 
> But this doesn't change what I'm saying - there seems to be a general 
> property when we execute this analysis which says whether the current 
> execution context has a direct `this` or not. This seems to me better 
> captured with a boolean, which is then fed back to all the other various 
> downstream ref creation.
> 
> The main observation, at least for me, is that the code unifies everything 
> under refs, when in reality there are different aspects:
> 
> * the set of variables that can point to this, whether directly or indirectly 
> (this is truly a set)
> * whether the current context has a direct or indirect this (this seems more 
> a flag to me)
> * whether the expression on top of stack is direct/indirect this reference or 
> not (again, 3 possible values here) - but granted, there `depth` here to take 
> into account, so you probably end up with a set (given that we don't want to 
> model a scope with its own set)
> 
> When reading the code, seeing set expression like containment checks or 
> removals for things that doesn't seem to be truly sets (unless I'm missing 
> something) was genuinely confusing me.

I get what you're saying - it seems silly to model what is essentially a fixed, 
boolean property with the membership of a singleton in a set field, rather than 
with a simple boolean field.

There is a conceptual trade-off though... a lot of the code relates to 
converting `Ref`'s from one type to another. For example, as pointed out above, 
a method invocation might convert a `ExprRef` to a `ThisRef`, then to a 
`ReturnRef`'s, etc. Having these things all be considered part of the same 
family helps conceptually. The fact that a direct `ThisRef` is a singleton is 
just a coincidence in this way of looking at things.

The benefit is the simplicity of being able to think of the data model as "just 
a set of references".

For example, methods like `RefSet.replaceExprs()` become less elegant (or 
basically impossible) if there have to be special cases for each type of 
reference, whereas currently we can do clean stuff like this:


@Override
public void visitReturn(JCReturn tree) {
scan(tree.expr);
refs.replaceExprs(depth, ReturnRef::new);
}


But I'm also realizing now that several places can be cleaned up by taking this 
event further. E.g., we should replace this code:

if (refs.contains(ThisRef.direct()))
outerRefs.add(OuterRef.direct());
if (refs.contains(ThisRef.indirect()))
outerRefs.add(OuterRef.indirect());

with something like this:

refs.mapInto(outerRefs, ThisRef.class, OuterRef::new);


I will go through and refactor to do that and clean things up a little more.

> When reading the code, seeing set expression like containment checks or 
> removals for things that doesn't seem to be truly sets (unless I'm missing 
> something) was genuinely confusing me.

Probably my fault for not providing better documentation of the overall "set of 
references" conceptual approach. FWIW I added a little bit more in f83a9cf0.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 19:24:50 GMT, Archie L. Cobbs  wrote:

>> This patch passes all tests:
>> 
>> 
>> diff --git 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>> index 9d35c2fbc0a..e755c54d0c8 100644
>> --- 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>> +++ 
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>> @@ -451,7 +451,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>>  }
>>  
>>  // If the expression type is incompatible with 'this', discard 
>> it
>> -if (type != null && !this.isSubtype(this.targetClass.sym.type, 
>> type))
>> +if (type != null && !this.targetClass.sym.isSubClass(type.tsym, 
>> types))
>>  this.refs.remove(ExprRef.direct(this.depth));
>>  }
>>  }
>> @@ -672,7 +672,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>>  if (explicitOuterThis != null) {
>>  this.scan(explicitOuterThis);
>>  this.refs.removeExprs(this.depth, direct -> outerRefs.add(new 
>> OuterRef(direct)));
>> -} else if (this.types.hasOuterClass(type, this.methodClass.type)) {
>> +} else if (type.tsym.isEnclosedBy(this.methodClass.sym)) {
>>  if (this.refs.contains(ThisRef.direct()))
>>  outerRefs.add(OuterRef.direct());
>>  if (this.refs.contains(ThisRef.indirect()))
>> @@ -801,9 +801,8 @@ class ThisEscapeAnalyzer extends TreeScanner {
>>  // Explicit outer 'this' reference?
>>  final Type selectedType = this.types.erasure(tree.selected.type);
>>  if (selectedType.hasTag(CLASS)) {
>> -final Type.ClassType selectedClassType = 
>> (Type.ClassType)selectedType;
>>  if (tree.name == this.names._this &&
>> -this.types.hasOuterClass(currentClassType, 
>> selectedClassType)) {
>> +
>> currentClassType.tsym.isEnclosedBy((ClassSymbol)selectedType.tsym)) {
>>  if (this.refs.contains(OuterRef.direct()))
>>  this.refs.add(ExprRef.direct(this.depth));
>>  if (this.refs.contains(OuterRef.indirect()))
>> @@ -895,9 +894,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>>  final MethodSymbol sym = (MethodSymbol)tree.sym;
>>  
>>  // Check for implicit 'this' reference
>> -final Type.ClassType currentClassType = 
>> (Type.ClassType)this.methodClass.sym.type;
>> -final Type methodOwnerType = sym.owner.type;
>> -if (this.isSubtype(currentClassType, methodOwnerType)) {
>> +if (methodClass.sym.isSubClass(sym.owner, types)) {
>>  if (this.refs.contains(ThisRef.direct()))
>>  this.refs.add(ExprRef.direct(this.depth));
>>  if (this.refs.contains(ThisRef.indirect()))
>> @@ -906,7 +903,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>>  }
>>  
>>  // Check for implicit outer 'this' reference
>> -if (this.types.hasOuterClass(currentClassType, 
>> methodOwnerType)) {
>> +if (methodClass.sym.isEnclosedBy((ClassSymbol)sym.owner)) {
>>  if (this.refs.contains(OuterRef.direct()))
>>  this.refs.add(ExprRef.direct(this.depth));
>> 
>> 
>> Btw, I believe a similar trick can be used in 
>> TreeInfo.isExplicitThisReference. If I'm correct, `hasOuterClass` should 
>> probably be removed as it duplicates already existing functionality. 
>> 
>> Since I'm bringing this up, as TreeInfo.isExplicitThisReference is only used 
>> by the new analyzer, it would be cleaner if it was defined there, at least 
>> until it's clear it might be needed by some other client.
>
> Weird. I don't get that build failure.
> 
> Neither does github... I have been relying on the github build "Actions" 
> succeeding to determine if "the build works" and according to that it is 
> building.
> 
> I will add the `DISABLED_WARNINGS` in any case.

Thanks for the patch!

The semantics of `hasOuterClass()` returns false if A and B are the same class, 
while `isEnclosedBy()` returns true if A and B are the same class.

However, I don't think it would actually matter in practice...

Regardless, I'll add the extra equality comparison and apply your patch and 
also the suggestions to integrate `isExplicitThisReference()` and eliminate 
`hasOuterClass()`.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 18:48:25 GMT, Maurizio Cimadamore  
wrote:

>>> I guess what I'm thinking about:
>> 
>> No leak is possible in that example.
>> * `new Foo()` creates an instance of `Foo` (not a subclass of `Foo`) 
>> therefore `m()` is not overridden
>> * Any subclass of `Foo` that may exist in the outside world cannot use the 
>> `Foo()` constructor that leaks because it's private
>
> but what if `m` is a static method in a separate compilation unit? Should it 
> be able to observe a partially initialized Foo?

Caring about the proper initialization of any class in the _current_ 
compilation unit is an explicit non-goal.

We only care about bugs where a superclass and subclass are in separate 
compilation units.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 18:40:38 GMT, Maurizio Cimadamore  
wrote:

>> This patch:
>> 
>> 
>> diff --git a/make/test/BuildMicrobenchmark.gmk 
>> b/make/test/BuildMicrobenchmark.gmk
>> index 1c89328a388..7c3f0293edc 100644
>> --- a/make/test/BuildMicrobenchmark.gmk
>> +++ b/make/test/BuildMicrobenchmark.gmk
>> @@ -76,7 +76,7 @@ $(eval $(call SetupJavaCompilation, BUILD_INDIFY, \
>>  TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \
>>  SRC := $(TOPDIR)/test/jdk/java/lang/invoke, \
>>  INCLUDE_FILES := indify/Indify.java, \
>> -DISABLED_WARNINGS := rawtypes serial options, \
>> +DISABLED_WARNINGS := this-escape rawtypes serial options, \
>>  BIN := $(MICROBENCHMARK_TOOLS_CLASSES), \
>>  JAVAC_FLAGS := -XDstringConcat=inline -Xprefer:newer, \
>>  ))
>> @@ -91,7 +91,7 @@ $(eval $(call SetupJavaCompilation, 
>> BUILD_JDK_MICROBENCHMARK, \
>>  TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \
>>  SMALL_JAVA := false, \
>>  CLASSPATH := $(MICROBENCHMARK_CLASSPATH), \
>> -DISABLED_WARNINGS := processing rawtypes cast serial preview, \
>> +DISABLED_WARNINGS := this-escape processing rawtypes cast serial 
>> preview, \
>>  SRC := $(MICROBENCHMARK_SRC), \
>>  BIN := $(MICROBENCHMARK_CLASSES), \
>>  JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \
>> diff --git 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>> index 9d35c2fbc0a..4e2b1e558e7 100644
>> --- 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>> +++ 
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>> @@ -897,6 +897,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>>  // Check for implicit 'this' reference
>>  final Type.ClassType currentClassType = 
>> (Type.ClassType)this.methodClass.sym.type;
>>  final Type methodOwnerType = sym.owner.type;
>> +//if (currentClassType.tsym.isSubClass(sym.owner, types)) {
>>  if (this.isSubtype(currentClassType, methodOwnerType)) {
>>  if (this.refs.contains(ThisRef.direct()))
>>  this.refs.add(ExprRef.direct(this.depth));
>> @@ -906,6 +907,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>>  }
>>  
>>  // Check for implicit outer 'this' reference
>> +// if 
>> (currentClassType.tsym.isEnclosedBy((ClassSymbol)sym.owner)) {
>>  if (this.types.hasOuterClass(currentClassType, 
>> methodOwnerType)) {
>>  if (this.refs.contains(OuterRef.direct()))
>>  this.refs.add(ExprRef.direct(this.depth));
>> 
>> 
>> Fixes the build failure.
>
> This patch passes all tests:
> 
> 
> diff --git 
> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  
> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
> index 9d35c2fbc0a..e755c54d0c8 100644
> --- 
> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
> +++ 
> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
> @@ -451,7 +451,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>  }
>  
>  // If the expression type is incompatible with 'this', discard it
> -if (type != null && !this.isSubtype(this.targetClass.sym.type, 
> type))
> +if (type != null && !this.targetClass.sym.isSubClass(type.tsym, 
> types))
>  this.refs.remove(ExprRef.direct(this.depth));
>  }
>  }
> @@ -672,7 +672,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>  if (explicitOuterThis != null) {
>  this.scan(explicitOuterThis);
>  this.refs.removeExprs(this.depth, direct -> outerRefs.add(new 
> OuterRef(direct)));
> -} else if (this.types.hasOuterClass(type, this.methodClass.type)) {
> +} else if (type.tsym.isEnclosedBy(this.methodClass.sym)) {
>  if (this.refs.contains(ThisRef.direct()))
>  outerRefs.add(OuterRef.direct());
>  if (this.refs.contains(ThisRef.indirect()))
> @@ -801,9 +801,8 @@ class ThisEscapeAnalyzer extends TreeScanner {
>  // Explicit outer 'this' reference?
>  final Type selectedType = this.types.erasure(tree.selected.type);
>  if (selectedType.hasTag(CLASS)) {
> -final Type.ClassType selectedClassType = 
> (Type.ClassType)selectedType;
>  if (tree.name == this.names._this &&
> -this.types.hasOuterClass(currentClassType, 
> selectedClassType)) {
> +
> currentClassType.tsym.isEnclosedBy((ClassSymbol)selectedType.tsym)) {
>  if (this.refs.contains(OuterRef.direct()))
>  this.refs.add(ExprRef.direct(this.depth));
>  if (this.refs.conta

Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 17:40:36 GMT, Maurizio Cimadamore  
wrote:

> But the filtering will end up dropping the expression Ref on the floor, 
> right? (because B and A are unrelated).

Ah, I see what you mean.

Here's a more complete example:

public class CastLeak {

public CastLeak() {
((CastLeak)(Runnable)this).mightLeak();
}

public void mightLeak() {
}
}

That would be a leak for any subclass that implements `Runnable`. Yet no 
warning is generated.

So the filtering by expression type is going to potentially create false 
negatives. But it also eliminates a bunch of false positives. And the false 
negatives are probably all somewhat pathological like the example above.

So I still think it's worth doing.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 17:33:48 GMT, Archie L. Cobbs  wrote:

>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  line 875:
>> 
>>> 873: // Reference to this?
>>> 874: if (tree.name == names._this || tree.name == names._super) {
>>> 875: if (this.refs.contains(ThisRef.direct()))
>> 
>> This idiom occurs quite a lot. If I'm correct, this basically amounts at 
>> asking as to whether the receiver of the method we're currently evaluating 
>> is direct or not (which is an invariant, given a method body - e.g. for a 
>> given method this "fact" should stay the same). If that's the case, perhaps 
>> capturing this in a flag could be better - then you could have just have a 
>> single method e.g. `XYZRef.create(boolean direct)`, and remove the branching 
>> (here and elsewhere).
>
> The code you quoted has nothing specifically to do with method invocations. 
> This code is simply handing the evaluation of the expressions `this` and 
> `super`. For example, `this` could just be a parameter we're passing to 
> another method.
> 
> When `this` or `super` expressions are evaluated, the thing left on top of 
> the stack is a direct/indirect reference (i.e., an `ExprRef`) exactly when 
> the there is a direct/indirect reference of type `ThisRef` in the current 
> reference set.
> 
> In cases where `this` is then "invoked", e.g., `this()`, the `ExprRef` (top 
> of Java stack) becomes the method receiver, and when the method is "invoked" 
> it turns back into a `ThisRef` (see earlier question).
> 
> Regarding the optimization you mention, in light of the above I'm not sure it 
> would still work.

My point is about who puts ThisRef in the set to begin with. It seems to me 
that ThisRef is put there at the start of a method analysis. After which, 
there's several code points where we say "if there's a direct ThisRef in the 
set, do this, otherwise, if there's an indirect ThisRef in the set, do that". 
But the ThisRef (whether indirect or direct) seems set once and for all (when 
we start the analysis, and then inside visitApply). 

There is also this bit in `visitReference`:


case SUPER:
if (this.refs.contains(ThisRef.direct()))
receiverRefs.add(ThisRef.direct());
if (this.refs.contains(ThisRef.indirect()))
receiverRefs.add(ThisRef.indirect());
break;


But this doesn't change what I'm saying - there seems to be a general property 
when we execute this analysis which says whether the current execution context 
has a direct `this` or not. This seems to me better captured with a boolean, 
which is then fed back to all the other various downstream ref creation.

The main observation, at least for me, is that the code unifies everything 
under refs, when in reality there are different aspects:

* the set of variables that can point to this, whether directly or indirectly 
(this is truly a set)
* whether the current context has a direct or indirect this (this seems more a 
flag to me)
* whether the expression on top of stack is direct/indirect this reference or 
not (again, 3 possible values here) - but granted, there `depth` here to take 
into account, so you probably end up with a set (given that we don't want to 
model a scope with its own set)

When reading the code, seeing set expression like containment checks or 
removals for things that doesn't seem to be truly sets (unless I'm missing 
something) was genuinely confusing me.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 17:29:22 GMT, Maurizio Cimadamore  
wrote:

>> I put it there because of switch expressions and `yeild`... ?
>
> Well, yield can... yield a value - `case` doesn't. So I'm confused. Also 
> because the variable is called `referenceExpressionNode` and `CASE` is not an 
> expression. Can `CASE` leave anything on the stack? YIELD does, but CASE?

It's just an artifact of the way switch expressions are handled:

@Override
public void visitSwitchExpression(JCSwitchExpression tree) {
visitScoped(tree, true, t -> {
scan(t.selector);
refs.discardExprs(depth);
RefSet combinedRefs = new RefSet<>();
for (List cases = t.cases; cases.nonEmpty(); cases = 
cases.tail) {
scan(cases.head);
combinedRefs.addAll(refs.removeExprs(depth));
}
refs.addAll(combinedRefs);
});
}

@Override
public void visitCase(JCCase tree) {
scan(tree.stats);  // no need to scan labels
}

After scanning a switch expression case, the `yield`'ed value will be on the 
top of the stack.

Then `visitCase` does NOT remove it, so that it can be picked up and removed by 
`visitSwitchExpression()`.

But this can be cleaned up a little bit, by having `visitSwitchExpression()` 
not delegate to `visitCase()` but rather iterate through the case statements 
itself directly. Then we can remove the `CASE` as you suggest.

Also I realized we need to handle `yield`'ed values like `return` values, i.e., 
collect and remember them until the entire case is complete.

I'll fix.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 18:37:06 GMT, Archie L. Cobbs  wrote:

>> I guess what I'm thinking about:
>> 
>> class Foo {
>> private Foo() {
>> m(this);
>> }
>> 
>> public void m() { ... } // overridable
>> 
>> static Foo makeFoo() { return new Foo(); }
>> }
>
>> I guess what I'm thinking about:
> 
> No leak is possible in that example.
> * `new Foo()` creates an instance of `Foo` (not a subclass of `Foo`) 
> therefore `m()` is not overridden
> * Any subclass of `Foo` that may exist in the outside world cannot use the 
> `Foo()` constructor that leaks because it's private

but what if `m` is a static method in a separate compilation unit? Should it be 
able to observe a partially initialized Foo?

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 18:18:38 GMT, Maurizio Cimadamore  
wrote:

>> I can't seem to be able to run tests - I get failures in the build:
>> 
>> 
>> * For target support_test_micro_tools-classes__the.BUILD_INDIFY_batch:
>
> This patch:
> 
> 
> diff --git a/make/test/BuildMicrobenchmark.gmk 
> b/make/test/BuildMicrobenchmark.gmk
> index 1c89328a388..7c3f0293edc 100644
> --- a/make/test/BuildMicrobenchmark.gmk
> +++ b/make/test/BuildMicrobenchmark.gmk
> @@ -76,7 +76,7 @@ $(eval $(call SetupJavaCompilation, BUILD_INDIFY, \
>  TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \
>  SRC := $(TOPDIR)/test/jdk/java/lang/invoke, \
>  INCLUDE_FILES := indify/Indify.java, \
> -DISABLED_WARNINGS := rawtypes serial options, \
> +DISABLED_WARNINGS := this-escape rawtypes serial options, \
>  BIN := $(MICROBENCHMARK_TOOLS_CLASSES), \
>  JAVAC_FLAGS := -XDstringConcat=inline -Xprefer:newer, \
>  ))
> @@ -91,7 +91,7 @@ $(eval $(call SetupJavaCompilation, 
> BUILD_JDK_MICROBENCHMARK, \
>  TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \
>  SMALL_JAVA := false, \
>  CLASSPATH := $(MICROBENCHMARK_CLASSPATH), \
> -DISABLED_WARNINGS := processing rawtypes cast serial preview, \
> +DISABLED_WARNINGS := this-escape processing rawtypes cast serial 
> preview, \
>  SRC := $(MICROBENCHMARK_SRC), \
>  BIN := $(MICROBENCHMARK_CLASSES), \
>  JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \
> diff --git 
> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  
> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
> index 9d35c2fbc0a..4e2b1e558e7 100644
> --- 
> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
> +++ 
> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
> @@ -897,6 +897,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>  // Check for implicit 'this' reference
>  final Type.ClassType currentClassType = 
> (Type.ClassType)this.methodClass.sym.type;
>  final Type methodOwnerType = sym.owner.type;
> +//if (currentClassType.tsym.isSubClass(sym.owner, types)) {
>  if (this.isSubtype(currentClassType, methodOwnerType)) {
>  if (this.refs.contains(ThisRef.direct()))
>  this.refs.add(ExprRef.direct(this.depth));
> @@ -906,6 +907,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
>  }
>  
>  // Check for implicit outer 'this' reference
> +// if 
> (currentClassType.tsym.isEnclosedBy((ClassSymbol)sym.owner)) {
>  if (this.types.hasOuterClass(currentClassType, methodOwnerType)) 
> {
>  if (this.refs.contains(OuterRef.direct()))
>  this.refs.add(ExprRef.direct(this.depth));
> 
> 
> Fixes the build failure.

This patch passes all tests:


diff --git 
a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
 
b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
index 9d35c2fbc0a..e755c54d0c8 100644
--- 
a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
+++ 
b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
@@ -451,7 +451,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
 }
 
 // If the expression type is incompatible with 'this', discard it
-if (type != null && !this.isSubtype(this.targetClass.sym.type, 
type))
+if (type != null && !this.targetClass.sym.isSubClass(type.tsym, 
types))
 this.refs.remove(ExprRef.direct(this.depth));
 }
 }
@@ -672,7 +672,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
 if (explicitOuterThis != null) {
 this.scan(explicitOuterThis);
 this.refs.removeExprs(this.depth, direct -> outerRefs.add(new 
OuterRef(direct)));
-} else if (this.types.hasOuterClass(type, this.methodClass.type)) {
+} else if (type.tsym.isEnclosedBy(this.methodClass.sym)) {
 if (this.refs.contains(ThisRef.direct()))
 outerRefs.add(OuterRef.direct());
 if (this.refs.contains(ThisRef.indirect()))
@@ -801,9 +801,8 @@ class ThisEscapeAnalyzer extends TreeScanner {
 // Explicit outer 'this' reference?
 final Type selectedType = this.types.erasure(tree.selected.type);
 if (selectedType.hasTag(CLASS)) {
-final Type.ClassType selectedClassType = 
(Type.ClassType)selectedType;
 if (tree.name == this.names._this &&
-this.types.hasOuterClass(currentClassType, selectedClassType)) 
{
+
currentClassType.tsym.isEnclosedBy((ClassSymbol)selectedType.tsym)) {
 if (this.refs.contains(OuterRef.direct()))
 this.refs.add(ExprRef.direct(this.depth));
 if (this.refs.cont

Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 16:40:33 GMT, Maurizio Cimadamore  
wrote:

> I guess what I'm thinking about:

No leak is possible in that example.
* `new Foo()` creates an instance of `Foo` (not a subclass of `Foo`) therefore 
`m()` is not overridden
* Any subclass of `Foo` that may exist in the outside world cannot use the 
`Foo()` constructor that leaks because it's private

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 18:11:01 GMT, Maurizio Cimadamore  
wrote:

>> Same comment as previous: I don't quite know what I'm doing and I'm loathe 
>> to break what is already working. Do you have a suggested patch?
>
> I can't seem to be able to run tests - I get failures in the build:
> 
> 
> * For target support_test_micro_tools-classes__the.BUILD_INDIFY_batch:

This patch:


diff --git a/make/test/BuildMicrobenchmark.gmk 
b/make/test/BuildMicrobenchmark.gmk
index 1c89328a388..7c3f0293edc 100644
--- a/make/test/BuildMicrobenchmark.gmk
+++ b/make/test/BuildMicrobenchmark.gmk
@@ -76,7 +76,7 @@ $(eval $(call SetupJavaCompilation, BUILD_INDIFY, \
 TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \
 SRC := $(TOPDIR)/test/jdk/java/lang/invoke, \
 INCLUDE_FILES := indify/Indify.java, \
-DISABLED_WARNINGS := rawtypes serial options, \
+DISABLED_WARNINGS := this-escape rawtypes serial options, \
 BIN := $(MICROBENCHMARK_TOOLS_CLASSES), \
 JAVAC_FLAGS := -XDstringConcat=inline -Xprefer:newer, \
 ))
@@ -91,7 +91,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, 
\
 TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \
 SMALL_JAVA := false, \
 CLASSPATH := $(MICROBENCHMARK_CLASSPATH), \
-DISABLED_WARNINGS := processing rawtypes cast serial preview, \
+DISABLED_WARNINGS := this-escape processing rawtypes cast serial preview, \
 SRC := $(MICROBENCHMARK_SRC), \
 BIN := $(MICROBENCHMARK_CLASSES), \
 JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \
diff --git 
a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
 
b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
index 9d35c2fbc0a..4e2b1e558e7 100644
--- 
a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
+++ 
b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
@@ -897,6 +897,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
 // Check for implicit 'this' reference
 final Type.ClassType currentClassType = 
(Type.ClassType)this.methodClass.sym.type;
 final Type methodOwnerType = sym.owner.type;
+//if (currentClassType.tsym.isSubClass(sym.owner, types)) {
 if (this.isSubtype(currentClassType, methodOwnerType)) {
 if (this.refs.contains(ThisRef.direct()))
 this.refs.add(ExprRef.direct(this.depth));
@@ -906,6 +907,7 @@ class ThisEscapeAnalyzer extends TreeScanner {
 }
 
 // Check for implicit outer 'this' reference
+// if (currentClassType.tsym.isEnclosedBy((ClassSymbol)sym.owner)) 
{
 if (this.types.hasOuterClass(currentClassType, methodOwnerType)) {
 if (this.refs.contains(OuterRef.direct()))
 this.refs.add(ExprRef.direct(this.depth));


Fixes the build failure.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 12:28:12 GMT, Maurizio Cimadamore  
wrote:

> This might also be related with the fact that we deal with return values in 
> different ways than with e.g. values returned from a nested scope (where we 
> just pop, and then copy all pending expression to the outer depth).

Yes, a method return value that represents a reference is an `ExprRef` just 
before the `return` is actually executed, but then it turns into a `ReturnRef`.

A `ReturnRef` is really just a yellow sticky note that reminds us "Hey whenever 
you finish 'executing' this method, remember that there is a reference in its 
return value".

> This might also be related with the fact that we deal with return values in 
> different ways than with e.g. values returned from a nested scope (where we 
> just pop, and then copy all pending expression to the outer depth).

Exactly.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 17:48:37 GMT, Archie L. Cobbs  wrote:

>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  line 909:
>> 
>>> 907: 
>>> 908: // Check for implicit outer 'this' reference
>>> 909: if (this.types.hasOuterClass(currentClassType, 
>>> methodOwnerType)) {
>> 
>> Similarly here - look for `Symbol.isEnclosedBy`
>
> Same comment as previous: I don't quite know what I'm doing and I'm loathe to 
> break what is already working. Do you have a suggested patch?

I can't seem to be able to run tests - I get failures in the build:


* For target support_test_micro_tools-classes__the.BUILD_INDIFY_batch:

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 17:39:05 GMT, Vicente Romero  wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 200:
> 
>> 198: //
>> 199: 
>> 200: public void analyzeTree(Env env) {
> 
> nit: this method could be removed in favor of the overloaded version below

Thanks - will fix.

> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 204:
> 
>> 202: }
>> 203: 
>> 204: public void analyzeTree(Env env, JCTree tree) {
> 
> nit: `env` parameter doesn't seem to be used could be dropped I think

Thanks - will fix.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 12:26:27 GMT, Maurizio Cimadamore  
wrote:

> Do we really need a set for this?

There are surely other ways to model things. But I got myself really confused 
trying to build more complicated models.

What I ended up with is this simple model that works:
* There is a set of `Ref` subclasses that model the various types of 'this' 
references possible: `OuterRef`, `ExprRef`, etc.
* There is a singleton `this.refs`, which just is a `Set`, containing the 
'this' references that are alive at the current moment
* As we "execute" code, all we need to do is update the `this.refs` based on 
what the current bit of code does

E.g

When a variable is assigned a value that has a reference, we add a `VarRef` for 
that variable.

When we leave a scope, we remove any `Ref`'s that are no longer in scope.

Before executing a method, we add `Ref`'s for the method receiver and parameter.

When we return from a non-void method, we convert any `ReturnRef` into a 
`ExprRef`.

Etc.

THAT I can understand.

I don't see converting the above into a bitmap or whatever as worth the 
additional complexity. We're not programming in perl here.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 12:17:32 GMT, Maurizio Cimadamore  
wrote:

> There is a concept of push/popScope and then there's a separate concept of 
> call stack (which is just a list of diagnostic position up to the point). I 
> wonder if this could be better modeled by using a single class e.g. 
> Scope/Frame which has a diagnostic position, plus other useful things.

I think that would be more confusing, because they're really two different 
animals.

Scopes only have meaning within a single method. They simply serve to bracket 
the lifetimes of `VarRef` references, following Java curly brace scope. When 
you "invoke" a method, you put aside the current stack of scopes and start over 
with an empty scope stack. They don't bridge between methods.

Call stacks are just a list of method+code position therein, and they exist 
outside of any single method. They have nothing to do with Java scopes defined 
by curly braces.

> Perhaps it might even be helpful to have a ref set on each scope, so that you 
> don't have to attach a "depth" to each ref - the depth of the ref would be 
> determined by the "scope" in which it appears.

That's another way to skin the same cat... in fact I had a design like that 
before but then realized it was a lot simpler to just carry around one `RefSet`.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 12:15:17 GMT, Maurizio Cimadamore  
wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 175:
> 
>> 173: private DiagnosticPosition[] pendingWarning;
>> 174: 
>> 175: // These fields are scoped to the CONSTRUCTOR OR INVOKED METHOD BEING 
>> ANALYZED
> 
> Watch out for "all caps"

Will fix.

> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 900:
> 
>> 898: final Type.ClassType currentClassType = 
>> (Type.ClassType)this.methodClass.sym.type;
>> 899: final Type methodOwnerType = sym.owner.type;
>> 900: if (this.isSubtype(currentClassType, methodOwnerType)) {
> 
> I believe what you need here is not subtyping but subclassing - see 
> `Symbol.isSubclass`

Hmm, I tried using `Symbol.isSubclass()` but it caused test failures. Obviously 
I don't quite know what I'm doing and I'm loathe to break what is already 
working. Do you have a suggested patch?

> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 909:
> 
>> 907: 
>> 908: // Check for implicit outer 'this' reference
>> 909: if (this.types.hasOuterClass(currentClassType, 
>> methodOwnerType)) {
> 
> Similarly here - look for `Symbol.isEnclosedBy`

Same comment as previous: I don't quite know what I'm doing and I'm loathe to 
break what is already working. Do you have a suggested patch?

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 11:09:35 GMT, Maurizio Cimadamore  
wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 444:
> 
>> 442: if (referenceExpressionNode) {
>> 443: 
>> 444: // We treat instance methods as having a "value" equal to 
>> their instance
> 
> The comment is slightly misleading - e.g. I'd suggest clarifying "having a 
> "value" whose type is the same as that of the class in which the method is 
> defined"

Agreed - will fix.

> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 454:
> 
>> 452: 
>> 453: // If the expression type is incompatible with 'this', 
>> discard it
>> 454: if (type != null && 
>> !this.isSubtype(this.targetClass.sym.type, type))
> 
> Instead of adding the direct reference, and then having to check if the 
> reference needs to be removed, would it be possible not to add the reference 
> in the first place if the types mismatch?

No because (for example) what if you cast?

The thing you're casting might be compatible, but after the cast it might 
become incompatible.

> This will then determine how to interpret this in the context of that method 
> analysis - e.g. when we see a JCIdent for this, we create a direct/indirect 
> ExprRef based on what the receiver kind was. Correct?

Yes, exactly.

When we "invoke" a method, we "preload" the set of current references that the 
method is going to have when it starts. That "preload" step includes any 
references from (a) the method receiver (non-static methods only) and (b) 
method parameters.

To the extent the method receiver has a direct/indirect reference, that turns 
into a direct/indirect `ThisRef` during method execution. You can see this 
happen in the very next lines after what you quoted. But of course the method 
invocation (the "apply") could have been applied to any arbitrary expression as 
the receiver.

> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 817:
> 
>> 815: // Methods - the "value" of a non-static method is a reference 
>> to its instance
>> 816: final Symbol sym = tree.sym;
>> 817: if (sym.kind == MTH) {
> 
> This is perhaps where filtering based on the declaring class could make sense 
> (to avoid having to filter later) ? Perhaps this could also be centralized - 
> e.g. whenever you create an ExprRef you also pass the type for it, and if the 
> type matches that for the current class you create it and add to the list, 
> otherwise you skip it.

Yes but see previous comment regarding casting.

> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 875:
> 
>> 873: // Reference to this?
>> 874: if (tree.name == names._this || tree.name == names._super) {
>> 875: if (this.refs.contains(ThisRef.direct()))
> 
> This idiom occurs quite a lot. If I'm correct, this basically amounts at 
> asking as to whether the receiver of the method we're currently evaluating is 
> direct or not (which is an invariant, given a method body - e.g. for a given 
> method this "fact" should stay the same). If that's the case, perhaps 
> capturing this in a flag could be better - then you could have just have a 
> single method e.g. `XYZRef.create(boolean direct)`, and remove the branching 
> (here and elsewhere).

The code you quoted has nothing specifically to do with method invocations. 
This code is simply handing the evaluation of the expressions `this` and 
`super`. For example, `this` could just be a parameter we're passing to another 
method.

When `this` or `super` expressions are evaluated, the thing left on top of the 
stack is a direct/indirect reference (i.e., an `ExprRef`) exactly when the 
there is a direct/indirect reference of type `ThisRef` in the current reference 
set.

In cases where `this` is then "invoked", e.g., `this()`, the `ExprRef` (top of 
Java stack) becomes the method receiver, and when the method is "invoked" it 
turns back into a `ThisRef` (see earlier question).

Regarding the optimization you mention, in light of the above I'm not sure it 
would still work.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 17:13:55 GMT, Archie L. Cobbs  wrote:

>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  line 411:
>> 
>>> 409: final boolean referenceExpressionNode;
>>> 410: switch (tree.getTag()) {
>>> 411: case CASE:
>> 
>> surprised to see `CASE` here - as that's not an expression
>
> I put it there because of switch expressions and `yeild`... ?

Well, yield can... yield a value - `case` doesn't. So I'm confused. Also 
because the variable is called `referenceExpressionNode` and `CASE` is not an 
expression. Can `CASE` leave anything on the stack? YIELD does, but CASE?

>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  line 454:
>> 
>>> 452: 
>>> 453: // If the expression type is incompatible with 'this', 
>>> discard it
>>> 454: if (type != null && 
>>> !this.isSubtype(this.targetClass.sym.type, type))
>> 
>> Instead of adding the direct reference, and then having to check if the 
>> reference needs to be removed, would it be possible not to add the reference 
>> in the first place if the types mismatch?
>
> No because (for example) what if you cast?
> 
> The thing you're casting might be compatible, but after the cast it might 
> become incompatible.

Uhm. Turns out I probably did not understand the filter correctly, and now I'm 
more dubious about what it actually does. Say you have this hierarchy:


interface A { }
class B {
 B() {
  A a = (A)this;
  ...
  }
 }
 class C extends B implements A { }
 ```

Pathological case, I know. But the filtering will end up dropping the 
expression Ref on the floor, right? (because B and A are unrelated).

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Vicente Romero
On Wed, 11 Jan 2023 03:30:03 GMT, Archie L. Cobbs  wrote:

>> This PR adds a new lint warning category `this-escape`.
>> 
>> It also adds `@SuppressWarnings` annotations as needed to the JDK itself to 
>> allow the JDK to continue to compile with `-Xlint:all`.
>> 
>> A 'this' escape warning is generated for a constructor `A()` in a class `A` 
>> when the compiler detects that the following situation is _in theory 
>> possible:_
>> * Some subclass `B extends A` exists, and `B` is defined in a separate 
>> source file (i.e., compilation unit)
>> * Some constructor `B()` of `B` invokes `A()` as its superclass constructor
>> * During the execution of `A()`, some non-static method of `B.foo()` could 
>> get invoked, perhaps indirectly
>> 
>> In the above scenario, `B.foo()` would execute before `A()` has returned and 
>> before `B()` has performed any initialization. To the extent `B.foo()` 
>> accesses any fields of `B` - all of which are still uninitialized - it is 
>> likely to function incorrectly.
>> 
>> Note, when determining if a 'this' escape is possible, the compiler makes no 
>> assumptions about code outside of the current compilation unit. It doesn't 
>> look outside of the current source file to see what might actually happen 
>> when a method is invoked. It does follow method and constructors within the 
>> current compilation unit, and applies a simplified 
>> union-of-all-possible-branches data flow analysis to see where 'this' could 
>> end up.
>> 
>> From my review, virtually all of the warnings generated in the various JDK 
>> modules are valid warnings in the sense that a 'this' escape, as defined 
>> above, is really and truly possible. However, that doesn't imply that any 
>> bugs were found within the JDK - only that the possibility of a certain type 
>> of bug exists if certain superclass constructors are used by someone, 
>> somewhere, someday.
>> 
>> For several "popular" classes, this PR also adds `@implNote`'s to the 
>> offending constructors so that subclass implementors are made aware of the 
>> threat. For one example, `TreeMap(Map)` invokes `putAll()` and `put()`.
>> 
>> More details and a couple of motivating examples are given in an included 
>> [doc 
>> file](https://github.com/archiecobbs/jdk/blob/ThisEscape/src/java.base/share/classes/java/lang/doc-files/ThisEscape.html)
>>  that these `@implNote`'s link to. See also the recent thread on `amber-dev` 
>> for some background.
>> 
>> Ideally, over time the owners of the various modules would review their 
>> `@SuppressWarnings("this-escape")` annotations and determine which other 
>> constructors also warranted such an `@implNote`.
>> 
>> Because of all the`@SuppressWarnings` annotations, this PR touches a bunch 
>> of different JDK modules. My apologies for that. Adding these annotations 
>> was determined to be the more conservative approach, as compared to just 
>> excepting `this-escape` from various module builds globally.
>> 
>> **Patch Navigation Guide**
>> 
>> * Non-trivial compiler changes:
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties`
>> 
>> * Javadoc additions of `@implNote`:
>> 
>> * `src/java.base/share/classes/java/io/PipedReader.java`
>> * `src/java.base/share/classes/java/io/PipedWriter.java`
>> * `src/java.base/share/classes/java/lang/Throwable.java`
>> * `src/java.base/share/classes/java/util/ArrayDeque.java`
>> * `src/java.base/share/classes/java/util/EnumMap.java`
>> * `src/java.base/share/classes/java/util/HashSet.java`
>> * `src/java.base/share/classes/java/util/Hashtable.java`
>> * `src/java.base/share/classes/java/util/LinkedList.java`
>> * `src/java.base/share/classes/java/util/TreeMap.java`
>> * `src/java.base/share/classes/java/util/TreeSet.java`
>> 
>> * New unit tests
>> * `test/langtools/tools/javac/warnings/ThisEscape/*.java`
>> 
>> * **Everything else** is just adding `@SuppressWarnings("this-escape")`
>
> Archie L. Cobbs has updated the pull request incrementally with two 
> additional commits since the last revision:
> 
>  - Use the more appropriate Type comparison method Types.isSameType().
>  - Add some more comments to clarify how the analysis works.

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java 
line 200:

> 198: //
> 199: 
> 200: public void analyzeTree(Env env) {

nit: this method could be removed in favor of the overloaded version below

src/jdk.compiler/share/clas

Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 10:56:53 GMT, Maurizio Cimadamore  
wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 411:
> 
>> 409: final boolean referenceExpressionNode;
>> 410: switch (tree.getTag()) {
>> 411: case CASE:
> 
> surprised to see `CASE` here - as that's not an expression

I put it there because of switch expressions and `yeild`... ?

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 10:48:49 GMT, Maurizio Cimadamore  
wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 227:
> 
>> 225: final boolean privateOuterPrev = this.privateOuter;
>> 226: final Lint lintPrev = this.lint;
>> 227: this.lint = this.lint.augment(tree.sym);
> 
> general stylistic comment - I see here and everywhere in this class 
> `this.xyz` - this is not the norm in the rest of the javac codebase, and it 
> would be better to replace it with just `xyz`

OK. I will reluctantly remove...

``
> Why on earth wouldn't you want to make it clear which one of N outer classes 
> a field comes from, and that it's a field, and not a variable declared 
> somewhere off screen??
> 
> IMHO omitting 'this' qualifiers is effectively accepting the grave offense of 
> code obfuscation in exchange for a tiny smidgen of brevity.
>
> It's definitely made it harder for me to read and understand the compiler, 
> with all the levels of nesting it has.
> 
``

I readily admit I'm probably in the minority on this and anyway it's a bikeshed 
thing so there's no point in debating it.

I will go with the flow :) though I feel a little sorry for the next person who 
has to read this code.

> From a documentation perspective it might carry a bit of value

Yes, that's the purpose - the `final` is for the human viewer, not the 
compiler. Just trying to be helpful to the next person.

But you're right it's inconsistent so I'll remove.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Thu, 12 Jan 2023 16:20:12 GMT, Archie L. Cobbs  wrote:

>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  line 218:
>> 
>>> 216: new TreeScanner() {
>>> 217: 
>>> 218: private Lint lint = ThisEscapeAnalyzer.this.lint;
>> 
>> On a first look I'm not sure about the granularity of suppression here. I 
>> believe that suppressing at the class level, or at the constructor level is 
>> enough. Allowing to further annotate var declarations and non-constructor 
>> methods, while doable, might actually be counter productive - in the sense 
>> that the annotation does not occur in the constructor (where you'd want to 
>> see it) but in some other method. I think the fact that a constructor is 
>> escaping (willingly) should be a visible thing. Something to consider.
>
> Two things...
> 
> We need to support annotations on field declarations because their 
> initializers are effectively mini-constructors. But we don't need it on local 
> variables, parameters, etc. - too confusing.
> 
> For annotations on methods, yes it's debatable. It can be handy if you have 
> e.g. an `init()` method that all of your constructors invoke. However, I 
> agree it's also confusing, so I will remove.
> 
> But we should keep the notion that if a constructor invokes `this()`, and the 
> invoked constructor has annotations suppressed, then we skip over the 
> constructor invocation.
> 
> I will make these updates.

Yep - I guess there's a general theme of "where do we want the warnings to be 
reported". My general feeling is that reporting a warning on the constructor 
might be enough - but I see that you try to generate a warning in the exact 
spot where the leakage happens - which I'm not sure if it's ok, or too clever.

>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  line 270:
>> 
>>> 268: final boolean analyzable = 
>>> this.currentClassIsExternallyExtendable() &&
>>> 269: TreeInfo.isConstructor(tree) &&
>>> 270: !tree.sym.isPrivate() &&
>> 
>> Why aren't private constructors analyzed? If we have a class with a private 
>> constructor and public static factory invoking said constructor, and the 
>> constructor makes `this` escape, isn't that an issue we should detect?
>
>> If we have a class with a private constructor and public static factory 
>> invoking said constructor, and the constructor makes this escape, isn't that 
>> an issue we should detect?
> 
> A static factory method will not create a subclassed instance, so there's no 
> 'this' escape problem.
> 
> But I admit I completely missed factory methods as a potential thing to worry 
> about.
> 
> Is it possible for a leak to be missed due to the use of a factory method?
> 
> Hmm. I can't immediately think of how, but if you can come up with an example 
> please share.

I guess what I'm thinking about:

class Foo {
private Foo() {
m(this);
}

public void m() { ... } // overridable

static Foo makeFoo() { return new Foo(); }
}

>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>>  line 294:
>> 
>>> 292:   !(this.currentClass.sym.isSealed() && 
>>> this.currentClass.permitting.isEmpty()) &&
>>> 293:   !(this.currentClass.sym.owner.kind == MTH) &&
>>> 294:   !this.privateOuter;
>> 
>> Here, note that is the owner of the current class symbol is a method, that 
>> covers anonymous classes too, which is a part of `privateOuter`. So the only 
>> think we need to check here is whether "currentClass" is private, which is a 
>> simple predicate. No need to carry `privateOuter` I believe
>
> Unless you explicitly declare a nested class `private`, it won't have the 
> `ACC_PRIVATE` flag, even though it is "implicitly private" because it has a 
> `private` enclosing class.
> 
> Example:
> 
> $ cat PrivateOuter.java 
> public class PrivateOuter {
> private static class Inner1 {
> static class Inner2 {
> }
> }
> }
> $ javap -v PrivateOuter$Inner1$Inner2
> Classfile 
> /Users/archie/proj/jdk/flex-test/classes/PrivateOuter$Inner1$Inner2.class
>   Last modified Jan 12, 2023; size 408 bytes
>   SHA-256 checksum 
> 51ba6d39a5e66df2a078761d6424acbea7a8e32b8451f6ca7d2af49889673b2c
>   Compiled from "PrivateOuter.java"
> class PrivateOuter$Inner1$Inner2
>   minor version: 0
>   major version: 63
>   flags: (0x0020) ACC_SUPER
>   this_class: #7  // PrivateOuter$Inner1$Inner2
>   super_class: #2 // java/lang/Object
>   ...
> 
> 
> So we have to keep track of this "implicit privateness" manually, which is 
> what the `privateOuter` flag is for.

D'oh - missed the `|=` - so this keeps updating...

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 10:32:19 GMT, Maurizio Cimadamore  
wrote:

> If we have a class with a private constructor and public static factory 
> invoking said constructor, and the constructor makes this escape, isn't that 
> an issue we should detect?

A static factory method will not create a subclassed instance, so there's no 
'this' escape problem.

But I admit I completely missed factory methods as a potential thing to worry 
about.

Is it possible for a leak to be missed due to the use of a factory method?

Hmm. I can't immediately think of how, but if you can come up with an example 
please share.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 10:25:27 GMT, Maurizio Cimadamore  
wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 218:
> 
>> 216: new TreeScanner() {
>> 217: 
>> 218: private Lint lint = ThisEscapeAnalyzer.this.lint;
> 
> On a first look I'm not sure about the granularity of suppression here. I 
> believe that suppressing at the class level, or at the constructor level is 
> enough. Allowing to further annotate var declarations and non-constructor 
> methods, while doable, might actually be counter productive - in the sense 
> that the annotation does not occur in the constructor (where you'd want to 
> see it) but in some other method. I think the fact that a constructor is 
> escaping (willingly) should be a visible thing. Something to consider.

Two things...

We need to support annotations on field declarations because their initializers 
are effectively mini-constructors. But we don't need it on local variables, 
parameters, etc. - too confusing.

For annotations on methods, yes it's debatable. It can be handy if you have 
e.g. an `init()` method that all of your constructors invoke. However, I agree 
it's also confusing, so I will remove.

But we should keep the notion that if a constructor invokes `this()`, and the 
invoked constructor has annotations suppressed, then we skip over the 
constructor invocation.

I will make these updates.

> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 304:
> 
>> 302: 
>> 303: // We are looking for analyzable constructors only
>> 304: final Symbol sym = entry.getKey();
> 
> This seems unused. And, if so, perhaps we only need a `Set`, not 
> a map.

Thanks, you're right, the map keys are not used here. I'll clean up the loop.

But we still need a map, not a set, because the map is used elsewhere.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 10:18:27 GMT, Maurizio Cimadamore  
wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 294:
> 
>> 292:   !(this.currentClass.sym.isSealed() && 
>> this.currentClass.permitting.isEmpty()) &&
>> 293:   !(this.currentClass.sym.owner.kind == MTH) &&
>> 294:   !this.privateOuter;
> 
> Here, note that is the owner of the current class symbol is a method, that 
> covers anonymous classes too, which is a part of `privateOuter`. So the only 
> think we need to check here is whether "currentClass" is private, which is a 
> simple predicate. No need to carry `privateOuter` I believe

Unless you explicitly declare a nested class `private`, it won't have the 
`ACC_PRIVATE` flag, even though it is "implicitly private" because it has a 
`private` enclosing class.

Example:

$ cat PrivateOuter.java 
public class PrivateOuter {
private static class Inner1 {
static class Inner2 {
}
}
}
$ javap -v PrivateOuter$Inner1$Inner2
Classfile 
/Users/archie/proj/jdk/flex-test/classes/PrivateOuter$Inner1$Inner2.class
  Last modified Jan 12, 2023; size 408 bytes
  SHA-256 checksum 
51ba6d39a5e66df2a078761d6424acbea7a8e32b8451f6ca7d2af49889673b2c
  Compiled from "PrivateOuter.java"
class PrivateOuter$Inner1$Inner2
  minor version: 0
  major version: 63
  flags: (0x0020) ACC_SUPER
  this_class: #7  // PrivateOuter$Inner1$Inner2
  super_class: #2 // java/lang/Object
  ...


So we have to keep track of this "implicit privateness" manually, which is what 
the `privateOuter` flag is for.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Archie L . Cobbs
On Thu, 12 Jan 2023 13:01:44 GMT, Maurizio Cimadamore  
wrote:

>> Archie L. Cobbs has updated the pull request incrementally with two 
>> additional commits since the last revision:
>> 
>>  - Use the more appropriate Type comparison method Types.isSameType().
>>  - Add some more comments to clarify how the analysis works.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java
>  line 334:
> 
>> 332: // of the stack trace of B. For example, if constructor Foo(int 
>> x) has a leak, and constructor
>> 333: // Foo() invokes this(0), then emitting a warning for Foo() 
>> would be redundant.
>> 334: final BiPredicate 
>> extendsAsPrefix = (warning1, warning2) -> {
> 
> Another strategy would be to give a single warning per leaking constructor.

We already impose a limit of at most one warning per constructor.

But for this limit what is technically meant by "per constructor" is "At most 
one warning per constructor, assuming that constructor is the one being 
analyzed (i.e., the one invoked by the subclass and not via `this()`)."

So that limitation doesn't stop a constructor from generating the same warning 
multiple times due to it being invoked indirectly by other constructors via 
`this()`. Those duplicate warnings are what the code above eliminates.

So this code only generates one warning, and it's reported for the second 
constructor:

public class DupWarn1 {

public DupWarn1() {
this(0);
}

public DupWarn1(int x) {
this.mightLeak();
}

public void mightLeak() {
}
}

DupWarn1.java:8: warning: [this-escape] possible 'this' escape before subclass 
is fully initialized
this.mightLeak();
  ^

This is appropriate. The leak is really in the second constructor; the first 
constructor has nothing to do with it. Reporting a leak for both constructors 
would be redundant.

An interesting side question: Is it possible to come up with an example where 
constructor A has a 'this' leak, and some other constructor B invokes `this()` 
to delegate to A, but when B delegates to A the leak occurs differently, or not 
at all?

Because of the "flood" analysis, and the fact that you can't pass 'this' 
references to `this()` or `super()` (these are static contexts), I don't think 
it's possible.

So in fact the deduplication will always apply whenever `this()` is involved.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Wed, 11 Jan 2023 03:30:03 GMT, Archie L. Cobbs  wrote:

>> This PR adds a new lint warning category `this-escape`.
>> 
>> It also adds `@SuppressWarnings` annotations as needed to the JDK itself to 
>> allow the JDK to continue to compile with `-Xlint:all`.
>> 
>> A 'this' escape warning is generated for a constructor `A()` in a class `A` 
>> when the compiler detects that the following situation is _in theory 
>> possible:_
>> * Some subclass `B extends A` exists, and `B` is defined in a separate 
>> source file (i.e., compilation unit)
>> * Some constructor `B()` of `B` invokes `A()` as its superclass constructor
>> * During the execution of `A()`, some non-static method of `B.foo()` could 
>> get invoked, perhaps indirectly
>> 
>> In the above scenario, `B.foo()` would execute before `A()` has returned and 
>> before `B()` has performed any initialization. To the extent `B.foo()` 
>> accesses any fields of `B` - all of which are still uninitialized - it is 
>> likely to function incorrectly.
>> 
>> Note, when determining if a 'this' escape is possible, the compiler makes no 
>> assumptions about code outside of the current compilation unit. It doesn't 
>> look outside of the current source file to see what might actually happen 
>> when a method is invoked. It does follow method and constructors within the 
>> current compilation unit, and applies a simplified 
>> union-of-all-possible-branches data flow analysis to see where 'this' could 
>> end up.
>> 
>> From my review, virtually all of the warnings generated in the various JDK 
>> modules are valid warnings in the sense that a 'this' escape, as defined 
>> above, is really and truly possible. However, that doesn't imply that any 
>> bugs were found within the JDK - only that the possibility of a certain type 
>> of bug exists if certain superclass constructors are used by someone, 
>> somewhere, someday.
>> 
>> For several "popular" classes, this PR also adds `@implNote`'s to the 
>> offending constructors so that subclass implementors are made aware of the 
>> threat. For one example, `TreeMap(Map)` invokes `putAll()` and `put()`.
>> 
>> More details and a couple of motivating examples are given in an included 
>> [doc 
>> file](https://github.com/archiecobbs/jdk/blob/ThisEscape/src/java.base/share/classes/java/lang/doc-files/ThisEscape.html)
>>  that these `@implNote`'s link to. See also the recent thread on `amber-dev` 
>> for some background.
>> 
>> Ideally, over time the owners of the various modules would review their 
>> `@SuppressWarnings("this-escape")` annotations and determine which other 
>> constructors also warranted such an `@implNote`.
>> 
>> Because of all the`@SuppressWarnings` annotations, this PR touches a bunch 
>> of different JDK modules. My apologies for that. Adding these annotations 
>> was determined to be the more conservative approach, as compared to just 
>> excepting `this-escape` from various module builds globally.
>> 
>> **Patch Navigation Guide**
>> 
>> * Non-trivial compiler changes:
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties`
>> 
>> * Javadoc additions of `@implNote`:
>> 
>> * `src/java.base/share/classes/java/io/PipedReader.java`
>> * `src/java.base/share/classes/java/io/PipedWriter.java`
>> * `src/java.base/share/classes/java/lang/Throwable.java`
>> * `src/java.base/share/classes/java/util/ArrayDeque.java`
>> * `src/java.base/share/classes/java/util/EnumMap.java`
>> * `src/java.base/share/classes/java/util/HashSet.java`
>> * `src/java.base/share/classes/java/util/Hashtable.java`
>> * `src/java.base/share/classes/java/util/LinkedList.java`
>> * `src/java.base/share/classes/java/util/TreeMap.java`
>> * `src/java.base/share/classes/java/util/TreeSet.java`
>> 
>> * New unit tests
>> * `test/langtools/tools/javac/warnings/ThisEscape/*.java`
>> 
>> * **Everything else** is just adding `@SuppressWarnings("this-escape")`
>
> Archie L. Cobbs has updated the pull request incrementally with two 
> additional commits since the last revision:
> 
>  - Use the more appropriate Type comparison method Types.isSameType().
>  - Add some more comments to clarify how the analysis works.

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java 
line 334:

> 332: // of the stack trace of B. For example, if constructor Foo(int 
> x) has a leak, and constructor
> 333: // Foo() invokes this(0), then emitting 

Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Maurizio Cimadamore
On Wed, 11 Jan 2023 03:30:03 GMT, Archie L. Cobbs  wrote:

>> This PR adds a new lint warning category `this-escape`.
>> 
>> It also adds `@SuppressWarnings` annotations as needed to the JDK itself to 
>> allow the JDK to continue to compile with `-Xlint:all`.
>> 
>> A 'this' escape warning is generated for a constructor `A()` in a class `A` 
>> when the compiler detects that the following situation is _in theory 
>> possible:_
>> * Some subclass `B extends A` exists, and `B` is defined in a separate 
>> source file (i.e., compilation unit)
>> * Some constructor `B()` of `B` invokes `A()` as its superclass constructor
>> * During the execution of `A()`, some non-static method of `B.foo()` could 
>> get invoked, perhaps indirectly
>> 
>> In the above scenario, `B.foo()` would execute before `A()` has returned and 
>> before `B()` has performed any initialization. To the extent `B.foo()` 
>> accesses any fields of `B` - all of which are still uninitialized - it is 
>> likely to function incorrectly.
>> 
>> Note, when determining if a 'this' escape is possible, the compiler makes no 
>> assumptions about code outside of the current compilation unit. It doesn't 
>> look outside of the current source file to see what might actually happen 
>> when a method is invoked. It does follow method and constructors within the 
>> current compilation unit, and applies a simplified 
>> union-of-all-possible-branches data flow analysis to see where 'this' could 
>> end up.
>> 
>> From my review, virtually all of the warnings generated in the various JDK 
>> modules are valid warnings in the sense that a 'this' escape, as defined 
>> above, is really and truly possible. However, that doesn't imply that any 
>> bugs were found within the JDK - only that the possibility of a certain type 
>> of bug exists if certain superclass constructors are used by someone, 
>> somewhere, someday.
>> 
>> For several "popular" classes, this PR also adds `@implNote`'s to the 
>> offending constructors so that subclass implementors are made aware of the 
>> threat. For one example, `TreeMap(Map)` invokes `putAll()` and `put()`.
>> 
>> More details and a couple of motivating examples are given in an included 
>> [doc 
>> file](https://github.com/archiecobbs/jdk/blob/ThisEscape/src/java.base/share/classes/java/lang/doc-files/ThisEscape.html)
>>  that these `@implNote`'s link to. See also the recent thread on `amber-dev` 
>> for some background.
>> 
>> Ideally, over time the owners of the various modules would review their 
>> `@SuppressWarnings("this-escape")` annotations and determine which other 
>> constructors also warranted such an `@implNote`.
>> 
>> Because of all the`@SuppressWarnings` annotations, this PR touches a bunch 
>> of different JDK modules. My apologies for that. Adding these annotations 
>> was determined to be the more conservative approach, as compared to just 
>> excepting `this-escape` from various module builds globally.
>> 
>> **Patch Navigation Guide**
>> 
>> * Non-trivial compiler changes:
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties`
>> 
>> * Javadoc additions of `@implNote`:
>> 
>> * `src/java.base/share/classes/java/io/PipedReader.java`
>> * `src/java.base/share/classes/java/io/PipedWriter.java`
>> * `src/java.base/share/classes/java/lang/Throwable.java`
>> * `src/java.base/share/classes/java/util/ArrayDeque.java`
>> * `src/java.base/share/classes/java/util/EnumMap.java`
>> * `src/java.base/share/classes/java/util/HashSet.java`
>> * `src/java.base/share/classes/java/util/Hashtable.java`
>> * `src/java.base/share/classes/java/util/LinkedList.java`
>> * `src/java.base/share/classes/java/util/TreeMap.java`
>> * `src/java.base/share/classes/java/util/TreeSet.java`
>> 
>> * New unit tests
>> * `test/langtools/tools/javac/warnings/ThisEscape/*.java`
>> 
>> * **Everything else** is just adding `@SuppressWarnings("this-escape")`
>
> Archie L. Cobbs has updated the pull request incrementally with two 
> additional commits since the last revision:
> 
>  - Use the more appropriate Type comparison method Types.isSameType().
>  - Add some more comments to clarify how the analysis works.

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java 
line 163:

> 161:  *  invoked from the target constructor; if empty, we're still in 
> the constructor.
> 162:  */
> 163: private final ArrayDeque callStack = new 
> A

Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-12 Thread Magnus Ihse Bursie
On Wed, 11 Jan 2023 03:30:03 GMT, Archie L. Cobbs  wrote:

>> This PR adds a new lint warning category `this-escape`.
>> 
>> It also adds `@SuppressWarnings` annotations as needed to the JDK itself to 
>> allow the JDK to continue to compile with `-Xlint:all`.
>> 
>> A 'this' escape warning is generated for a constructor `A()` in a class `A` 
>> when the compiler detects that the following situation is _in theory 
>> possible:_
>> * Some subclass `B extends A` exists, and `B` is defined in a separate 
>> source file (i.e., compilation unit)
>> * Some constructor `B()` of `B` invokes `A()` as its superclass constructor
>> * During the execution of `A()`, some non-static method of `B.foo()` could 
>> get invoked, perhaps indirectly
>> 
>> In the above scenario, `B.foo()` would execute before `A()` has returned and 
>> before `B()` has performed any initialization. To the extent `B.foo()` 
>> accesses any fields of `B` - all of which are still uninitialized - it is 
>> likely to function incorrectly.
>> 
>> Note, when determining if a 'this' escape is possible, the compiler makes no 
>> assumptions about code outside of the current compilation unit. It doesn't 
>> look outside of the current source file to see what might actually happen 
>> when a method is invoked. It does follow method and constructors within the 
>> current compilation unit, and applies a simplified 
>> union-of-all-possible-branches data flow analysis to see where 'this' could 
>> end up.
>> 
>> From my review, virtually all of the warnings generated in the various JDK 
>> modules are valid warnings in the sense that a 'this' escape, as defined 
>> above, is really and truly possible. However, that doesn't imply that any 
>> bugs were found within the JDK - only that the possibility of a certain type 
>> of bug exists if certain superclass constructors are used by someone, 
>> somewhere, someday.
>> 
>> For several "popular" classes, this PR also adds `@implNote`'s to the 
>> offending constructors so that subclass implementors are made aware of the 
>> threat. For one example, `TreeMap(Map)` invokes `putAll()` and `put()`.
>> 
>> More details and a couple of motivating examples are given in an included 
>> [doc 
>> file](https://github.com/archiecobbs/jdk/blob/ThisEscape/src/java.base/share/classes/java/lang/doc-files/ThisEscape.html)
>>  that these `@implNote`'s link to. See also the recent thread on `amber-dev` 
>> for some background.
>> 
>> Ideally, over time the owners of the various modules would review their 
>> `@SuppressWarnings("this-escape")` annotations and determine which other 
>> constructors also warranted such an `@implNote`.
>> 
>> Because of all the`@SuppressWarnings` annotations, this PR touches a bunch 
>> of different JDK modules. My apologies for that. Adding these annotations 
>> was determined to be the more conservative approach, as compared to just 
>> excepting `this-escape` from various module builds globally.
>> 
>> **Patch Navigation Guide**
>> 
>> * Non-trivial compiler changes:
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties`
>> 
>> * Javadoc additions of `@implNote`:
>> 
>> * `src/java.base/share/classes/java/io/PipedReader.java`
>> * `src/java.base/share/classes/java/io/PipedWriter.java`
>> * `src/java.base/share/classes/java/lang/Throwable.java`
>> * `src/java.base/share/classes/java/util/ArrayDeque.java`
>> * `src/java.base/share/classes/java/util/EnumMap.java`
>> * `src/java.base/share/classes/java/util/HashSet.java`
>> * `src/java.base/share/classes/java/util/Hashtable.java`
>> * `src/java.base/share/classes/java/util/LinkedList.java`
>> * `src/java.base/share/classes/java/util/TreeMap.java`
>> * `src/java.base/share/classes/java/util/TreeSet.java`
>> 
>> * New unit tests
>> * `test/langtools/tools/javac/warnings/ThisEscape/*.java`
>> 
>> * **Everything else** is just adding `@SuppressWarnings("this-escape")`
>
> Archie L. Cobbs has updated the pull request incrementally with two 
> additional commits since the last revision:
> 
>  - Use the more appropriate Type comparison method Types.isSameType().
>  - Add some more comments to clarify how the analysis works.

FWIW, the make changes look good. I have nothing to say about the actual lint 
code itself.

-

Marked as reviewed by ihse (Reviewer).

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-11 Thread Joe Darcy
On Wed, 11 Jan 2023 03:30:03 GMT, Archie L. Cobbs  wrote:

>> This PR adds a new lint warning category `this-escape`.
>> 
>> It also adds `@SuppressWarnings` annotations as needed to the JDK itself to 
>> allow the JDK to continue to compile with `-Xlint:all`.
>> 
>> A 'this' escape warning is generated for a constructor `A()` in a class `A` 
>> when the compiler detects that the following situation is _in theory 
>> possible:_
>> * Some subclass `B extends A` exists, and `B` is defined in a separate 
>> source file (i.e., compilation unit)
>> * Some constructor `B()` of `B` invokes `A()` as its superclass constructor
>> * During the execution of `A()`, some non-static method of `B.foo()` could 
>> get invoked, perhaps indirectly
>> 
>> In the above scenario, `B.foo()` would execute before `A()` has returned and 
>> before `B()` has performed any initialization. To the extent `B.foo()` 
>> accesses any fields of `B` - all of which are still uninitialized - it is 
>> likely to function incorrectly.
>> 
>> Note, when determining if a 'this' escape is possible, the compiler makes no 
>> assumptions about code outside of the current compilation unit. It doesn't 
>> look outside of the current source file to see what might actually happen 
>> when a method is invoked. It does follow method and constructors within the 
>> current compilation unit, and applies a simplified 
>> union-of-all-possible-branches data flow analysis to see where 'this' could 
>> end up.
>> 
>> From my review, virtually all of the warnings generated in the various JDK 
>> modules are valid warnings in the sense that a 'this' escape, as defined 
>> above, is really and truly possible. However, that doesn't imply that any 
>> bugs were found within the JDK - only that the possibility of a certain type 
>> of bug exists if certain superclass constructors are used by someone, 
>> somewhere, someday.
>> 
>> For several "popular" classes, this PR also adds `@implNote`'s to the 
>> offending constructors so that subclass implementors are made aware of the 
>> threat. For one example, `TreeMap(Map)` invokes `putAll()` and `put()`.
>> 
>> More details and a couple of motivating examples are given in an included 
>> [doc 
>> file](https://github.com/archiecobbs/jdk/blob/ThisEscape/src/java.base/share/classes/java/lang/doc-files/ThisEscape.html)
>>  that these `@implNote`'s link to. See also the recent thread on `amber-dev` 
>> for some background.
>> 
>> Ideally, over time the owners of the various modules would review their 
>> `@SuppressWarnings("this-escape")` annotations and determine which other 
>> constructors also warranted such an `@implNote`.
>> 
>> Because of all the`@SuppressWarnings` annotations, this PR touches a bunch 
>> of different JDK modules. My apologies for that. Adding these annotations 
>> was determined to be the more conservative approach, as compared to just 
>> excepting `this-escape` from various module builds globally.
>> 
>> **Patch Navigation Guide**
>> 
>> * Non-trivial compiler changes:
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties`
>> 
>> * Javadoc additions of `@implNote`:
>> 
>> * `src/java.base/share/classes/java/io/PipedReader.java`
>> * `src/java.base/share/classes/java/io/PipedWriter.java`
>> * `src/java.base/share/classes/java/lang/Throwable.java`
>> * `src/java.base/share/classes/java/util/ArrayDeque.java`
>> * `src/java.base/share/classes/java/util/EnumMap.java`
>> * `src/java.base/share/classes/java/util/HashSet.java`
>> * `src/java.base/share/classes/java/util/Hashtable.java`
>> * `src/java.base/share/classes/java/util/LinkedList.java`
>> * `src/java.base/share/classes/java/util/TreeMap.java`
>> * `src/java.base/share/classes/java/util/TreeSet.java`
>> 
>> * New unit tests
>> * `test/langtools/tools/javac/warnings/ThisEscape/*.java`
>> 
>> * **Everything else** is just adding `@SuppressWarnings("this-escape")`
>
> Archie L. Cobbs has updated the pull request incrementally with two 
> additional commits since the last revision:
> 
>  - Use the more appropriate Type comparison method Types.isSameType().
>  - Add some more comments to clarify how the analysis works.

Per the discussion in "Effective Java" on calling overridable methods in a 
constructor, future refinements of the check under review here could be 
extended to examine the bodies of clone and readObject methods.

-

PR: https://git.openjdk.org/jdk

Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-11 Thread Archie L . Cobbs
On Wed, 11 Jan 2023 15:59:29 GMT, Maurizio Cimadamore  
wrote:

> What I'm interested in though is what incremental improvement is brought by 
> the more complex analysis you have in this PR?

It's a good question. Here are some thoughts...

One meta-goal is that this analysis be conservative. In other words, if your 
code does NOT generate any warnings, then you should feel confident that there 
is a high probability that there are no leaks.

This is analogous to how you can be confident that you won't get any 
`ClassCastExceptions` at runtime if your code doesn't generate any `unchecked` 
warnings at compile time.

I think this point is generally agreed upon.

If so, then we would want any simpler analysis to err on the side of generating 
more false positives rather than more false negatives.

Assuming that, then the question comes down to a trade-off between code 
complexity vs. rate of false positives.

>From my casual looking over the JDK, the current algorithm generates very few 
>false positives - almost all of the warnings represent actual leaks (I'd be 
>interested in any false positives you do see).
 
(Of course, an irony is that most of these leaks have no real-world effect. For 
example package-private classes in the JDK (a) have already been debugged long 
ago, so any intra-package bugs due to 'this' escapes have already been fixed; 
and (b) are unlikely to be subclassed by anyone. And the ones that have a 
real-world effect (e.g., `HashSet(Collection)`) can't be fixed because of 
backward compatibility concerns. So this warning is most useful when writing 
new code.)

So the current code is clearly "complex enough" already. FWIW that's ~975 lines 
of code excluding blank lines and comments.

Now to answer your question about a theoretical simpler analysis:

> let's say that we don't care about tracking where `this` ends up going 
> exactly (e.g. if it's aliased by another variable) - and perhaps also let's 
> say we don't care about inspecting the method to which `this` is leaked too 
> closely - e.g. we treat any early escape of this as a possible issue.

I'm not sure I completely understand the semantics. But are you saying that if 
a constructor invokes a private or static method, then this simpler analysis 
would always declare a leak?

If that's the case then I think there are a lot of new false positives, because 
this is common in constructors.

I would then worry that if we dilute the warnings with a bunch of new false 
positives people are just going to get discouraged and turn the warning off 
completely.

-

PR: https://git.openjdk.org/jdk/pull/11874


Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-11 Thread Maurizio Cimadamore
On Wed, 11 Jan 2023 03:30:03 GMT, Archie L. Cobbs  wrote:

>> This PR adds a new lint warning category `this-escape`.
>> 
>> It also adds `@SuppressWarnings` annotations as needed to the JDK itself to 
>> allow the JDK to continue to compile with `-Xlint:all`.
>> 
>> A 'this' escape warning is generated for a constructor `A()` in a class `A` 
>> when the compiler detects that the following situation is _in theory 
>> possible:_
>> * Some subclass `B extends A` exists, and `B` is defined in a separate 
>> source file (i.e., compilation unit)
>> * Some constructor `B()` of `B` invokes `A()` as its superclass constructor
>> * During the execution of `A()`, some non-static method of `B.foo()` could 
>> get invoked, perhaps indirectly
>> 
>> In the above scenario, `B.foo()` would execute before `A()` has returned and 
>> before `B()` has performed any initialization. To the extent `B.foo()` 
>> accesses any fields of `B` - all of which are still uninitialized - it is 
>> likely to function incorrectly.
>> 
>> Note, when determining if a 'this' escape is possible, the compiler makes no 
>> assumptions about code outside of the current compilation unit. It doesn't 
>> look outside of the current source file to see what might actually happen 
>> when a method is invoked. It does follow method and constructors within the 
>> current compilation unit, and applies a simplified 
>> union-of-all-possible-branches data flow analysis to see where 'this' could 
>> end up.
>> 
>> From my review, virtually all of the warnings generated in the various JDK 
>> modules are valid warnings in the sense that a 'this' escape, as defined 
>> above, is really and truly possible. However, that doesn't imply that any 
>> bugs were found within the JDK - only that the possibility of a certain type 
>> of bug exists if certain superclass constructors are used by someone, 
>> somewhere, someday.
>> 
>> For several "popular" classes, this PR also adds `@implNote`'s to the 
>> offending constructors so that subclass implementors are made aware of the 
>> threat. For one example, `TreeMap(Map)` invokes `putAll()` and `put()`.
>> 
>> More details and a couple of motivating examples are given in an included 
>> [doc 
>> file](https://github.com/archiecobbs/jdk/blob/ThisEscape/src/java.base/share/classes/java/lang/doc-files/ThisEscape.html)
>>  that these `@implNote`'s link to. See also the recent thread on `amber-dev` 
>> for some background.
>> 
>> Ideally, over time the owners of the various modules would review their 
>> `@SuppressWarnings("this-escape")` annotations and determine which other 
>> constructors also warranted such an `@implNote`.
>> 
>> Because of all the`@SuppressWarnings` annotations, this PR touches a bunch 
>> of different JDK modules. My apologies for that. Adding these annotations 
>> was determined to be the more conservative approach, as compared to just 
>> excepting `this-escape` from various module builds globally.
>> 
>> **Patch Navigation Guide**
>> 
>> * Non-trivial compiler changes:
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java`
>> * `src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties`
>> * 
>> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties`
>> 
>> * Javadoc additions of `@implNote`:
>> 
>> * `src/java.base/share/classes/java/io/PipedReader.java`
>> * `src/java.base/share/classes/java/io/PipedWriter.java`
>> * `src/java.base/share/classes/java/lang/Throwable.java`
>> * `src/java.base/share/classes/java/util/ArrayDeque.java`
>> * `src/java.base/share/classes/java/util/EnumMap.java`
>> * `src/java.base/share/classes/java/util/HashSet.java`
>> * `src/java.base/share/classes/java/util/Hashtable.java`
>> * `src/java.base/share/classes/java/util/LinkedList.java`
>> * `src/java.base/share/classes/java/util/TreeMap.java`
>> * `src/java.base/share/classes/java/util/TreeSet.java`
>> 
>> * New unit tests
>> * `test/langtools/tools/javac/warnings/ThisEscape/*.java`
>> 
>> * **Everything else** is just adding `@SuppressWarnings("this-escape")`
>
> Archie L. Cobbs has updated the pull request incrementally with two 
> additional commits since the last revision:
> 
>  - Use the more appropriate Type comparison method Types.isSameType().
>  - Add some more comments to clarify how the analysis works.

More questions. Let's say that we only tracked escaping of direct references to 
`this`. Do you have any sense of how many "less" warnings would be reported in 
real code (e.g. JDK) ? What I mean by this - let's say that we don't care about 
tracking _where_ `

Re: RFR: 8015831: Add lint check for calling overridable methods from a constructor [v7]

2023-01-10 Thread Archie L . Cobbs
> This PR adds a new lint warning category `this-escape`.
> 
> It also adds `@SuppressWarnings` annotations as needed to the JDK itself to 
> allow the JDK to continue to compile with `-Xlint:all`.
> 
> A 'this' escape warning is generated for a constructor `A()` in a class `A` 
> when the compiler detects that the following situation is _in theory 
> possible:_
> * Some subclass `B extends A` exists, and `B` is defined in a separate source 
> file (i.e., compilation unit)
> * Some constructor `B()` of `B` invokes `A()` as its superclass constructor
> * During the execution of `A()`, some non-static method of `B.foo()` could 
> get invoked, perhaps indirectly
> 
> In the above scenario, `B.foo()` would execute before `A()` has returned and 
> before `B()` has performed any initialization. To the extent `B.foo()` 
> accesses any fields of `B` - all of which are still uninitialized - it is 
> likely to function incorrectly.
> 
> Note, when determining if a 'this' escape is possible, the compiler makes no 
> assumptions about code outside of the current compilation unit. It doesn't 
> look outside of the current source file to see what might actually happen 
> when a method is invoked. It does follow method and constructors within the 
> current compilation unit, and applies a simplified 
> union-of-all-possible-branches data flow analysis to see where 'this' could 
> end up.
> 
> From my review, virtually all of the warnings generated in the various JDK 
> modules are valid warnings in the sense that a 'this' escape, as defined 
> above, is really and truly possible. However, that doesn't imply that any 
> bugs were found within the JDK - only that the possibility of a certain type 
> of bug exists if certain superclass constructors are used by someone, 
> somewhere, someday.
> 
> For several "popular" classes, this PR also adds `@implNote`'s to the 
> offending constructors so that subclass implementors are made aware of the 
> threat. For one example, `TreeMap(Map)` invokes `putAll()` and `put()`.
> 
> More details and a couple of motivating examples are given in an included 
> [doc 
> file](https://github.com/archiecobbs/jdk/blob/ThisEscape/src/java.base/share/classes/java/lang/doc-files/ThisEscape.html)
>  that these `@implNote`'s link to. See also the recent thread on `amber-dev` 
> for some background.
> 
> Ideally, over time the owners of the various modules would review their 
> `@SuppressWarnings("this-escape")` annotations and determine which other 
> constructors also warranted such an `@implNote`.
> 
> Because of all the`@SuppressWarnings` annotations, this PR touches a bunch of 
> different JDK modules. My apologies for that. Adding these annotations was 
> determined to be the more conservative approach, as compared to just 
> excepting `this-escape` from various module builds globally.
> 
> **Patch Navigation Guide**
> 
> * Non-trivial compiler changes:
> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java`
> * `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java`
> * `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java`
> * `src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java`
> * 
> `src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java`
> * 
> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties`
> * 
> `src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties`
> 
> * Javadoc additions of `@implNote`:
> 
> * `src/java.base/share/classes/java/io/PipedReader.java`
> * `src/java.base/share/classes/java/io/PipedWriter.java`
> * `src/java.base/share/classes/java/lang/Throwable.java`
> * `src/java.base/share/classes/java/util/ArrayDeque.java`
> * `src/java.base/share/classes/java/util/EnumMap.java`
> * `src/java.base/share/classes/java/util/HashSet.java`
> * `src/java.base/share/classes/java/util/Hashtable.java`
> * `src/java.base/share/classes/java/util/LinkedList.java`
> * `src/java.base/share/classes/java/util/TreeMap.java`
> * `src/java.base/share/classes/java/util/TreeSet.java`
> 
> * New unit tests
> * `test/langtools/tools/javac/warnings/ThisEscape/*.java`
> 
> * **Everything else** is just adding `@SuppressWarnings("this-escape")`

Archie L. Cobbs has updated the pull request incrementally with two additional 
commits since the last revision:

 - Use the more appropriate Type comparison method Types.isSameType().
 - Add some more comments to clarify how the analysis works.

-

Changes:
  - all: https://git.openjdk.org/jdk/pull/11874/files
  - new: https://git.openjdk.org/jdk/pull/11874/files/d70d12f4..6e96a7d7

Webrevs:
 - full: https://webrevs.openjdk.org/?repo=jdk&pr=11874&range=06
 - incr: https://webrevs.openjdk.org/?repo=jdk&pr=11874&range=05-06

  Stats: 31 lines in 2 files changed: 16 ins; 10 del; 5 mod
  Patch: https://git.openjdk.org/jdk/pull/11874.diff
  Fetc