-
Notifications
You must be signed in to change notification settings - Fork 5.8k
8354221: (zipfs) Tidy initialization of jdk.nio.zipfs.ZipFileSystem #24555
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
base: master
Are you sure you want to change the base?
Conversation
👋 Welcome back david-beaumont! A progress list of the required criteria for merging this PR into |
❗ This change is not yet ready to be integrated. |
@david-beaumont The following labels will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command. |
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.
Explanatory thoughts and a couple of questions.
@@ -104,7 +103,8 @@ class ZipFileSystem extends FileSystem { | |||
private final Path zfpath; | |||
final ZipCoder zc; | |||
private final ZipPath rootdir; | |||
private boolean readOnly; // readonly file system, false by default | |||
// Start readOnly (safe mode) and maybe reset at end of initialization. | |||
private boolean readOnly = true; |
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.
Seems more sensible to start in read-only mode. There's no sign this was ever an issue, but it feels safer in general.
@@ -129,6 +129,7 @@ class ZipFileSystem extends FileSystem { | |||
final boolean supportPosix; | |||
private final UserPrincipal defaultOwner; | |||
private final GroupPrincipal defaultGroup; | |||
// Unmodifiable set. |
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.
Or I could just rename it to unmodifiableDefaultPosixPermissions
which would let me remove 2 comments (here and where it's returned).
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 don't usually indicate a collection is unmodifiable in the field names - a comment suffices, and in fact, I think we promote using unmodifiable collections as the default for non-mutating data now.
@@ -147,7 +148,7 @@ class ZipFileSystem extends FileSystem { | |||
this.supportPosix = isTrue(env, PROPERTY_POSIX); | |||
this.defaultOwner = supportPosix ? initOwner(zfpath, env) : null; | |||
this.defaultGroup = supportPosix ? initGroup(zfpath, env) : null; | |||
this.defaultPermissions = supportPosix ? initPermissions(env) : null; | |||
this.defaultPermissions = supportPosix ? Collections.unmodifiableSet(initPermissions(env)) : 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.
Wrap it on setting the field rather than in the helper so it's clearly done unconditionally.
@@ -180,7 +178,22 @@ class ZipFileSystem extends FileSystem { | |||
this.provider = provider; | |||
this.zfpath = zfpath; | |||
|
|||
initializeReleaseVersion(env); | |||
// Determining a release version uses 'this' instance to read paths etc. |
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 bulk of this change is moving the (re)setting of these fields down to the end of initialization, and now doing it in the constructor itself. Even though the helper functions are not static (they could be if needed) they no longer mutate the instance.
if (o == null) { | ||
return DEFAULT_PERMISSIONS; | ||
return PosixFilePermissions.fromString("rwxrwxrwx"); |
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.
Inlining the default permission seems fine (used once, matches the comment 3 lines above).
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 can probably use EnumSet.allOf
here - same enum set remark for the hash set usage below. The old field allows sharing the unmodified set instance, but enum set is cheap so creating should be fine.
@@ -1383,33 +1391,33 @@ private void removeFromTree(IndexNode inode) { | |||
* Checks if the Zip File System property "releaseVersion" has been specified. If it has, | |||
* use its value to determine the requested version. If not use the value of the "multi-release" property. | |||
*/ | |||
private void initializeReleaseVersion(Map<String, ?> env) throws IOException { | |||
private Optional<Integer> determineReleaseVersion(Map<String, ?> env) throws IOException { |
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 should probably change the comment above a bit. Thoughts on rewording welcome.
@@ -1435,11 +1443,11 @@ private boolean isMultiReleaseJar() throws IOException { | |||
* Then wrap the map in a function that getEntry can use to override root | |||
* entry lookup for entries that have corresponding versioned entries. | |||
*/ | |||
private void createVersionedLinks(int version) { | |||
private Function<byte[], byte[]> createVersionedLinks(int version) { |
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.
Or return Optional<Function<...>>
perhaps. Thoughts welcome.
Comment above can probably be changed too.
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 would return a UnaryOperator<byte[]>
just because the type name is shorter :)
@@ -3551,7 +3556,8 @@ public GroupPrincipal group() { | |||
|
|||
@Override | |||
public Set<PosixFilePermission> permissions() { | |||
return storedPermissions().orElse(Set.copyOf(defaultPermissions)); | |||
// supportPosix ==> (defaultPermissions != null) and it's unmodifiable. |
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'd be happy to drop the comment if I renamed the field to unmodifiableDefaultPosixPermissions
.
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.
Hmm, PosixFileAttributes requires this returned set to be modifiable. Seems the existing implementation already violates the specification, but we might just adjust the specs to say that the returned set, if modifiable, does not affect the actual permission of the attributes.
@@ -3551,7 +3556,8 @@ public GroupPrincipal group() { | |||
|
|||
@Override | |||
public Set<PosixFilePermission> permissions() { | |||
return storedPermissions().orElse(Set.copyOf(defaultPermissions)); | |||
// supportPosix ==> (defaultPermissions != null) and it's unmodifiable. |
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.
Hmm, PosixFileAttributes requires this returned set to be modifiable. Seems the existing implementation already violates the specification, but we might just adjust the specs to say that the returned set, if modifiable, does not affect the actual permission of the attributes.
@@ -1435,11 +1443,11 @@ private boolean isMultiReleaseJar() throws IOException { | |||
* Then wrap the map in a function that getEntry can use to override root | |||
* entry lookup for entries that have corresponding versioned entries. | |||
*/ | |||
private void createVersionedLinks(int version) { | |||
private Function<byte[], byte[]> createVersionedLinks(int version) { |
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 would return a UnaryOperator<byte[]>
just because the type name is shorter :)
if (o == null) { | ||
return DEFAULT_PERMISSIONS; | ||
return PosixFilePermissions.fromString("rwxrwxrwx"); |
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 can probably use EnumSet.allOf
here - same enum set remark for the hash set usage below. The old field allows sharing the unmodified set instance, but enum set is cheap so creating should be fine.
@@ -129,6 +129,7 @@ class ZipFileSystem extends FileSystem { | |||
final boolean supportPosix; | |||
private final UserPrincipal defaultOwner; | |||
private final GroupPrincipal defaultGroup; | |||
// Unmodifiable set. |
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 don't usually indicate a collection is unmodifiable in the field names - a comment suffices, and in fact, I think we promote using unmodifiable collections as the default for non-mutating data now.
// It requires 'entryLookup' and 'readOnly' to have safe defaults (which | ||
// is why they are the only non-final fields), and it requires that the | ||
// inode map has been initialized. | ||
Optional<Integer> multiReleaseVersion = determineReleaseVersion(env); |
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 call still feels risky - this is probably a "this escape" and can cause troubles if isMultiReleaseJar
uses final fields not yet initialized below.
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.
Thanks for pointing out the mutable permissions set thing. I guess we'll just need to make a copy to return to the user every time (or explicitly continue to violate the spec). EnumSet is super cheap, but I'll change the internal field to be an EnumSet so we don't have any risk of creating a larger data structure and then copying that every time.
As regards the "this escape" comment. How can it escape?
The code called is private and reads from the inode table with the default file mapping function (and this is still better than the existing code because it waits until everything else is set before doing the read).
return storedPermissions().orElse(Set.copyOf(defaultPermissions)); | ||
// supportPosix ==> (defaultPermissions != null). | ||
assert defaultPermissions != null; | ||
return storedPermissions().orElse(defaultPermissions.clone()); |
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.
return storedPermissions().orElse(defaultPermissions.clone()); | |
return storedPermissions().orElseGet(defaultPermissions::clone); |
So we don't clone if stored permissions is present.
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.
doh Excellent spot, thanks!
} | ||
} | ||
|
||
private Set<PosixFilePermission> getFilePermissions( |
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 pulled out the permission getting logic so I can get permissions several times.
I even think if reads a bit nicer than before tbh (less nesting and a clearer mapping in the switch statement).
d3d07bb
to
61389ad
Compare
* Making default permissions always be an enum set and cloning it on return (it must be mutable, contrary to what the existing code does).
* Add tests and fix daft mistake with initPermissions(). * Function -> UnaryOperator (better describes the intent).
61389ad
to
d027149
Compare
DEFAULT_PERMISSIONS
, which was only used once (having a mutable static field seems like something to generally be avoided).defaultPermissions
field non-mutable and avoid copying aSet
every time it's read.Progress
Issue
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/24555/head:pull/24555
$ git checkout pull/24555
Update a local copy of the PR:
$ git checkout pull/24555
$ git pull https://git.openjdk.org/jdk.git pull/24555/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 24555
View PR using the GUI difftool:
$ git pr show -t 24555
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/24555.diff