-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8361300: Document exceptions for Unsafe offset methods #25945
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
Conversation
|
👋 Welcome back liach! A progress list of the required criteria for merging this PR into |
|
@liach 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 69 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
| * @throws NullPointerException if the field is {@code null} | ||
| * @throws IllegalArgumentException if the field is static |
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.
Maybe these checks could be done in Java like:
// implicit null check:
if ((f.getModifiers() & ACC_STATIC) != 0) {
throw new IllegalArgumentException("Field is static");
}and for the staticField(Offset/Base) methods:
// implicit null check:
if ((f.getModifiers() & ACC_STATIC) == 0) {
throw new IllegalArgumentException("Field is not static");
}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.
Don't think anyone is willing to change code here...
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 said, the only places that depend on this IAE behavior is the AtomicXxxFieldUpdater classes. All other sites pass trusted fields into these methods, of course besides the sun.misc.Unsafe users which definitely need this IAE. Still I personally prefer adding these checks to atomic field updaters instead of here to reduce risky dependencies.
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.
As I commented on the C++ change, I think it's better to put the check in Java rather than C++.
The behavioral results are equivalent, right? It is fine to upgrade the InternalError to IAE; InternalError is by definition a poorly-defined state, and IAE is a less poorly defined way to report it. Changing that path to throw IAE is better, since it aligns with other field-query APIs. Because we control use of Unsafe (in this package) we can make small changes like this and just adjust our own code if something breaks.
Error checks and validations in Java code will be 10x clearer (more maintainable, scrutable to JITs) than logic in the C++ code.
|
I noticed that the native implementation of jdk/src/hotspot/share/prims/unsafe.cpp Lines 587 to 594 in 1ca008f
|
|
For arrayBaseOffset/IndexScale, I don't think I will change it here: they are methods that are exposed via sun.misc.Unsafe. |
Webrevs
|
| ? caller : tclass; | ||
| this.tclass = tclass; | ||
| this.offset = U.objectFieldOffset(field); | ||
| this.offset = U.objectFieldOffset(field); // throws IAE for static |
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.
AtomicIntegerFieldUpdaterImpl already checks the field type and it is volatile. I think it would be better to expand these checks so that static volatile fields are rejected there rather than "accidentally" calling objectFieldOffset with a static field.
|
Thanks for this permission to update the Atomic updaters. I found that there's no coverage in tck when these IAE on static fields are acknowledged recently, so I took the liberty to add them. |
|
I have added test cases to verify the current behaviors of Atomic field updaters and Unsafe memory address calculation methods, including untouched ones for arrays pointed out by @ExE-Boss. All of these are currently exposed through public APIs. |
| void arrayBaseOffset() { | ||
| assertDoesNotThrow(() -> getUnsafe().arrayBaseOffset(int[].class)); | ||
| assertThrows(NullPointerException.class, () -> getUnsafe().arrayBaseOffset(null)); | ||
| // Caused by VM trying to throw java.lang.InvalidClassException (there's one in java.io instead) |
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.
Note that the one in java.io is also a checked exception (extends java.io.ObjectStreamException which extends java.io.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.
Yes, but there has been precedent of some sort of code throwing checked exceptions in a potentially-unchecked context, such as the lookup getter in proxy classes throw IllegalAccessException instead of IllegalAccessError.
Anyways, an java.io exception is definitely not suitable here. When sun.misc.Unsafe memory access methods are gone, we might simply change this to an IAE instead.
| if (!Modifier.isVolatile(modifiers)) | ||
| throw new IllegalArgumentException("Must be volatile type"); | ||
|
|
||
| if (Modifier.isStatic(modifiers)) |
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.
Can we break out the Modifier tests in the three updater classes to a common method?
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 we include that in this patch?
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.
Created https://bugs.openjdk.org/browse/JDK-8364544 to track that.
|
|
||
| if (Modifier.isStatic(modifiers)) | ||
| throw new IllegalArgumentException("Must not be a static field"); | ||
|
|
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.
@viktorklang-ora @DougLea Should we update the Atomic*FieldUpdater to specify the IAE when the factory method is called with a static Field?
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.
@AlanBateman Yeah, I think that makes sense—IIRC it's been that way in practice since forever, but making it clear in documentation seems like the right thing to do.
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.
Tracked in 8360541; I think we can implement in a separate patch. Don't know if this is worth backporting to MRs.
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.
Ah yes, I'd forgotten I created this issue to track clarifying the spec.
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.
Sure, the clarification is long past being useful (since people should be using VarHandles) but why not.
|
I think from our discussions, there is nothing else to change for this patch. Please correct me if my understanding is wrong, and if it isn't, I hope someone can hop in and leave a review. |
| for (JavaFieldStream fs(k); !fs.done(); fs.next()) { | ||
| Symbol *name = fs.name(); | ||
| if (name->equals(utf_name)) { | ||
| offset = fs.offset(); |
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.
While you are here, if you keep this check, rename this particular function find_field_offset to find_nonstatic_field_offset (not the other one that takes the "must be static" flag).
The logic, as written, is difficult to understand because of the behavioral difference between the two overloading.
(I note that "instance field" is also a term of art in our code base, but "nonstatic_field" is more common.)
Alternatively, and probably better, take out this particular check (restoring equal access to static and non-static) and just rely on the checks in Java code. Such checks are almost always better (easier to reason about by humans and JITs) than checks in C++ code.
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 particular method intentionally avoids accessing Java reflective objects to reduce startup overhead. I think a better approach may be returning error code and have Java code report a failure. Thoughts?
| private native long staticFieldOffset0(Field f); | ||
| private native Object staticFieldBase0(Field f); | ||
| private native long objectFieldOffset0(Field f); // throws IAE | ||
| private native long objectFieldOffset1(Class<?> c, String name); // throws InternalError |
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.
Ugh; this IE is part of the problem. You could push an IAE into the C++ code for symmetry, or start pushing more IAE logic into the Java code (my preference, but a bigger change perhaps).
I can tell you that the original intention of choosing InternalError is to mark places where the caller has a responsibility to make InternalError impossible. InternalError is not intended to trigger further processing; it denotes system failure.
| assertDoesNotThrow(() -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "instanceField")); | ||
| assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(null, "instanceField")); | ||
| assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, null)); | ||
| assertThrows(InternalError.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "doesNotExist")); |
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 a malformed test on a malformed API. VirtualMachineError exceptions signal system failure.
rose00
left a comment
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 works for me.
|
@vy Can you help review this patch too? For context, |
vy
left a comment
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 don't possess sufficient experience on Unsafe et al., though given there are no behavioral changes, I presume it should all be fine. I've verified the following:
Unsafedoc improvementsU::objectFieldOffset(Class,String)throws descriptiveIEU::objectFieldOffset1is renamed to more descriptiveknownObjectFieldOffset0Atomic*FieldUpdaterchecks and their TCK counterparts (This could have actually been a separate PR, but I see that the component owners gave consent.)AddressComputationContractTest
I guess you will have a follow-up JBS ticket (along with a PR? 😇) for @minborg's suggestion on consolidating checks in Atomic*FieldUpdater classes. For instance, I see AIFU.AIFUImpl::isAncestor is not even used.
| } | ||
|
|
||
| @Test | ||
| void fastObjectFieldOffset() { |
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.
Nit: You may want to match the corresponding impl. method name, as you did in other test methods:
| void fastObjectFieldOffset() { | |
| void knownObjectFieldOffset() { |
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.
Done.
|
Thanks for the reviews. As a final note, I decide to keep the in C++ unsafe checks for now; the unsafe.cpp seems to indicate there are more contracts that I cannot verify right now, for things like offset conversions. So I will integrate this patch as-is. /integrate |
|
Going to push as commit cd50d78.
Your commit was automatically rebased without conflicts. |
Unsafe throws IAE for misusing static vs instance fields, and it's revealed that AtomicXxxFieldUpdaters are using this mechanism to reject static fields. This is not a good practice, but we can at least document this so we don't accidentally introduce problems.
Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25945/head:pull/25945$ git checkout pull/25945Update a local copy of the PR:
$ git checkout pull/25945$ git pull https://git.openjdk.org/jdk.git pull/25945/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 25945View PR using the GUI difftool:
$ git pr show -t 25945Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25945.diff
Using Webrev
Link to Webrev Comment