-
Notifications
You must be signed in to change notification settings - Fork 482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JDK-8304959: Public API in javafx.css.Match should not return private API class PseudoClassState #1070
Conversation
👋 Welcome back jhendrikx! A progress list of the required criteria for merging this PR into |
Webrevs
|
This was clearly a mistake back when the public API was first created in JDK 9. /reviewers 2 |
@kevinrushforth |
@kevinrushforth has indicated that a compatibility and specification (CSR) request is needed for this pull request. @hjohn please create a CSR request for issue JDK-8304959 with the correct fix version. This pull request cannot be integrated until the CSR request is approved. |
@kevinrushforth I've created the CSR for this. Can I move it to proposed? Also, I've been looking into https://bugs.openjdk.org/browse/JDK-8199216 which sees excessive use of This change would need to go in first though to make this possible. |
I took a quick look, and it is fine to move to Proposed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did leave one question that you might want to address before proposing the CSR.
@@ -77,7 +76,7 @@ public Selector getSelector() { | |||
* Gets the pseudo class state. | |||
* @return the pseudo class state | |||
*/ | |||
public PseudoClassState getPseudoClasses() { | |||
public Set<PseudoClass> getPseudoClasses() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an ObservableSet
? Changing the type to the Set
superclass will mean that applications would need to do an instanceof
check to know whether it was observable or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that Match
is supposed to be immutable, given the non-public constructor. Match itself will never change the set (and nothing else will) so making it observable seems unnecessary.
However, this was not correctly spec'd, and you can change a Match
now, and since it doesn't make a copy, you'd be changing the pseudo classes of whatever selector created it. Note that SimpleSelector
does make a copy, and goes to great pains to not expose anything mutable, but exposes it accidentally via Match
.
In other words, simpleSelector.createMatch().getPseudoClasses().clear()
would break the Selectors encapsulation.
I think it's best to close that loophole. If you agree, I can document this method that it returns an immutable set, which is also what I assumed would be the case in my other PR where I made many of these immutable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CSS API baffles me a bit. It doesn't seem consistent.
Just now I took a look at the class SimpleSelector
and CompoundSelector
. These are public, yet cannot be constructed by users. They're also not returned anywhere (the closest is Selector#createSelector
which returns the interface).
Essentially this means that SimpleSelector
and CompoundSelector
should probably be package private. Yet, I guess they were made public because SelectorPartitioning
is doing instanceof checks and is casting to these classes. But anybody can do that now, and that means that for example SimpleSelector#getStyleClassSet
is exposed, which returns a mutable set...
Reading between the lines though it seems that SimpleSelector
and CompoundSelector
were intended to be fully immutable (which makes sense as they represent a style sheet). Any changes would not be picked up as nothing is observing these properties.
I think these loopholes should be closed.
There are two options IMHO:
- Move
SimpleSelector
andCompoundSelector
to the com hierarchy. They can't be publically constructed, and are never returned. The only way to reach them is by casting. - If it's too late for that, then close all loopholes and ensure that these two classes are fully immutable. From what I can see now, only
getStyleClassSet
and the mentioned method inMatch
need closing.CompoundSelector
is already immutable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a closer look early next week.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that Match is supposed to be immutable, given the non-public constructor. Match itself will never change the set (and nothing else will) so making it observable seems unnecessary.
Agreed.
In other words, simpleSelector.createMatch().getPseudoClasses().clear() would break the Selectors encapsulation.
I think it's best to close that loophole. If you agree, I can document this method that it returns an immutable set, which is also what I assumed would be the case in my other PR where I made many of these immutable.
Yes, this seems like the best solution to me.
Essentially this means that SimpleSelector and CompoundSelector should probably be package private.
I agree that these two classes should not have been made public when the javafx.css package was created as public API back in JDK 9.
The usual process for removing API is to deprecate it for removal in one release and then remove it in a future release, but in this case, since those classes cannot be constructed, and are never returned by any public API, any use of them would be relying on an implementation detail (via an instanceof
and a cast, to no good purpose).
Since there is no useful way an application could be using these classes, I recommend option 1, as long as those two classes can be cleanly moved to com.sun.javafx.css. Otherwise, we could go with option 2 along with deprecating those two classes for removal.
The Specification section of the CSR would simply propose to remove those two classes. You could describe what is happening (moving them to a non-exported package) in the Solution section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevinrushforth It looks like it will have to be option 2; the reason is that both Selector
and Match
are abstract classes (with a package private constructor). The CompoundSelector
and SimpleSelector
extend this abstract class (as there is some small overlap). If Selector
and Match
had been interfaces, it would have allowed this.
I couldn't find anything about binary compatibility when changing an abstract class with a package private constructor to an interface, so I tested it and I got an IncompatibleClassChangeError
when calling Selector Selector.createSelector(...)
method. Apparently, the compiled method call does encode the difference between an interface and a class:
invokestatic #7 // Method Selector.createSelector:(I)LSelector;
vs:
invokestatic #7 // InterfaceMethod Selector.createSelector:(I)LSelector;
At the most, I could make Can't do that either, they're referred in private API.CompoundSelector
and SimpleSelector
package private, or nested classes in Selector
; they would at least be hidden then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. In that case, option 2 it is. And since there doesn't seem to be a good way to remove these classes from the public API, it makes no sense to deprecate them for removal.
We could deprecate them with a note that they are used by the CSS implementation, and not intended to be used by applications, but since applications aren't usefully using them today, we could defer that to a later time (or not do it at all).
Updated documentation, fixed warnings and removed some unnecessary code
I think this is the best that can be done for now. There are still some mutable aspects to Ideally, However, it is still possible to improve the situation a bit (in another PR perhaps), as the private code only requires a few additional methods to be made public API on
These three methods are implemented by |
I had to pull in some of the fixes I did in Just wrapping a |
@kevinrushforth about the CSR -- as I couldn't move the classes to non-public hierarchy, I think the CSR is currently correct. I also removed a public method in There were a few javadoc only changes that could be seen as clarification (whether something can be If we want to move forward later to perhaps try and remove |
@hjohn This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration! |
@kevinrushforth do you think we can move forward with this? |
I'll take a look at this next week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes look good with a few inline comments.
@@ -77,7 +76,7 @@ public Selector getSelector() { | |||
* Gets the pseudo class state. | |||
* @return the pseudo class state | |||
*/ | |||
public PseudoClassState getPseudoClasses() { | |||
public Set<PseudoClass> getPseudoClasses() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. In that case, option 2 it is. And since there doesn't seem to be a good way to remove these classes from the public API, it makes no sense to deprecate them for removal.
We could deprecate them with a note that they are used by the CSS implementation, and not intended to be used by applications, but since applications aren't usefully using them today, we could defer that to a later time (or not do it at all).
modules/javafx.graphics/src/main/java/javafx/css/SimpleSelector.java
Outdated
Show resolved
Hide resolved
modules/javafx.graphics/src/main/java/javafx/css/SimpleSelector.java
Outdated
Show resolved
Hide resolved
modules/javafx.graphics/src/main/java/javafx/css/SimpleSelector.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
two sticky points:
- verify somehow that nulls cannot be passed to *All() methods
- consider adding a component type to BitSet
|
||
if (obj == null || this.getClass() != obj.getClass()) { | ||
return false; | ||
if (obj instanceof BitSet<?> bitSet) { // fast path if other is a BitSet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we may need to add a 'component type' to BitSet, to make sure than BitSet never intersects with BitSet.
(Didn't we do it already, may be in some other PR?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did, in #1076. However, I would like to not pull in all the BitSet
fixes into this PR as this has nothing to do with BitSet
and its many problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's an internal class, and both PseudoClassState and StyleClassSet are final, checking for class == class is probably sufficient.
Do you think we should add a warning to BitSet, it is not a problem since no-one will ever extend it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also solved in #1076:
if (obj instanceof BitSet<?> bitSet && getElementType().equals(bitSet.getElementType()))
But since that requires pulling in even more changes (getElementType
) I've left it there.
Relying on subclasses being final though seems like a bad idea for an abstract class, unless we seal it and use permits (or, just remove BitSet
completely... it's purpose will be minimal if #1076 is accepted).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's my point. I suppose we could use sealed class here, since javafx requires minimum java17?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this needs to be changed as part of this PR?
Note that the ==
solution requires:
- sealing BitSet permitting only PseudoClassState and StyleClassSet
- adding a warning that if you ever unseal it, that you must re-evaluate the
equals
implementation - adding a warning to PseudoClassState / StyleClassSet to re-evaluate
BitSet#equals
if you removefinal
The instanceof
solution allows arbitary subclasses, like any other Set implementation does (AbstractSet#equals
for example use instanceof Set
) and requires no further changes.
What I'm missing is the reasoning why you would want to change this. I realize that the other solution can also work, but that's not enough IMHO. For me the two options are practically equivalent, neither being truly better than the other, so then it is a matter of preference, and I slightly favor using instanceof
here to avoid potential problems down the road.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this needs to be changed as part of this PR?
I'll answer this part of your question: No.
It seems out of scope for this particular bug fix, which is aimed at fixing an API and impl problem with Match
, and should limit the changes to other classes to those needed to accomplish this.
It's great to point out existing weaknesses in, say, BitSet
, but they should be addressed separately, after evaluating the need.
@@ -131,7 +131,10 @@ static CssStyleHelper createStyleHelper(final Node node) { | |||
node.styleHelper.cacheContainer.fontSizeCache.clear(); | |||
} | |||
node.styleHelper.cacheContainer.forceSlowpath = true; | |||
node.styleHelper.triggerStates.addAll(triggerStates[0]); | |||
|
|||
if (triggerStates[0] != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that a null check was added.
Are we sure we added it to every possible place?
(it's not trivial to verify that with Eclipse without massive changes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The underlying collection BitSet
is only used by classes dealing with styles and pseudo class states. I checked the classes involved, and only this one needed changing. The check was added because BitSet
erroneously allowed adding null
before (which resulted in no change), but that breaks the Set
contract.
In any case, adding a null
collection is an error, we should never be lenient when it comes to null
s.
Consider this example:
|
I'm aware that this is broken now, and still is broken after this PR. It's fixed in #1076 and has this test case even. It's worse than that tough, even
Do you want me to pull in more changes from #1076 unrelated to fixing the public API problem in Match? |
My concern is that replacing == with .equals() in BitSet.equals() may lead to issues in internal CSS code. It is highly unlikely that application would extend BitSet, or put StyleClassSet in the same hash table due to both being in com.sun; but the internal code might (and I don't want to comb the CSS code looking for possible occurrences). If we replace line 532 with |
Not merely unlikely, it is not possible, so this need not be considered.
That's a good suggestion. Given that we want the minimal changes to BitSet for this particular PR, this seems a reasonable, and safe choice. |
Alright, I see your point. The current version will always return I'll fix it. |
I will still fix it so it isn't worse than it was before, but I just found another reason why you shouldn't use
I will leave that for #1076 (where this test is coming from). I'm only adding a test to verify the fix I'm adding here, and was seeing what I can use before writing new ones. The above test passes though because it will fall back to |
80ff999
to
da8ac03
Compare
@hjohn Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. See OpenJDK Developers’ Guide for more information. |
Sorry for the force push, that wasn't what I intended; wanted to undo changes GitHub did on the remote by merging in master as I also merged it in locally (but I added a commit just before). Unfortunately, the PR already picked up the master merge from GitHub... I'll know that for next time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tested with the MonkeyTester on macOS Ventura 13.3.1(a), including dynamic re-loading of the stylesheets. see no ill effects.
I took a closer look at the compatibility issues Joe raised in the CSR, and I can confirm that yes, this change will break binary compatibility for any application that calls the existing method. It would also can break source compatibility for the subset of those applications that use the return value as an So it's the binary compatibility we need to consider. The question is: is the If not, then the change seems OK. If it is, then we might need to consider adding a replacement method and deprecating the existing method for removal. The implementation of the replacement method would be exactly what your PR currently has for the existing method, and the existing method, would create a |
I've done some Googling with To call this method, one would to do something like this at a minimum:
I did find a few references to There's not a lot of reason to call |
This is what I would have expected as well. I'll do some additional searching as well, but, as of now, I think we can proceed down the current path and live with the incompatibility. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've completed my review, and everything looks good. I found one missing doc change in the CSR (the change in SimpleSelector) that should be added, and then I'll formally review the CSR.
@hjohn This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 2 new commits pushed to the
Please see this link for an up-to-date comparison between the source branch of this pull request and the ➡️ To integrate this PR with the above commit message to the |
@hjohn In case you missed seeing it, the CSR was approved, so Skara marked this PR as |
/integrate |
Going to push as commit 3fa02ee.
Your commit was automatically rebased without conflicts. |
The class
PseudoClassState
is private API, but was exposed erroneously in the CSS API. Instead,Set<PseudoClass>
should have been used. This PR corrects this.Progress
Issues
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1070/head:pull/1070
$ git checkout pull/1070
Update a local copy of the PR:
$ git checkout pull/1070
$ git pull https://git.openjdk.org/jfx.git pull/1070/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 1070
View PR using the GUI difftool:
$ git pr show -t 1070
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1070.diff
Webrev
Link to Webrev Comment