Skip to content

Conversation

@liach
Copy link
Member

@liach liach commented Jun 24, 2025

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

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8361300: Document exceptions for Unsafe offset methods (Enhancement - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25945/head:pull/25945
$ git checkout pull/25945

Update a local copy of the PR:
$ git checkout pull/25945
$ git pull https://git.openjdk.org/jdk.git pull/25945/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 25945

View PR using the GUI difftool:
$ git pr show -t 25945

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25945.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jun 24, 2025

👋 Welcome back liach! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jun 24, 2025

@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:

8361300: Document exceptions for Unsafe offset methods

Reviewed-by: jrose, vyazici

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 master branch:

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 master branch, type /integrate in a new comment.

@openjdk
Copy link

openjdk bot commented Jun 24, 2025

@liach The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the core-libs core-libs-dev@openjdk.org label Jun 24, 2025
Comment on lines +1069 to +1070
* @throws NullPointerException if the field is {@code null}
* @throws IllegalArgumentException if the field is static

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");
}

Copy link
Member Author

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...

Copy link
Member Author

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.

Copy link
Contributor

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.

@ExE-Boss
Copy link

ExE-Boss commented Jun 26, 2025

I noticed that the native implementation of Unsafe​::array⁠(BaseOffset​/IndexScale) refers to the non‑existent class java.lang.InvalidClassException instead of java.lang.IllegalArgumentException (which results in java.lang.NoClassDefFoundError being thrown):

static void getBaseAndScale(int& base, int& scale, jclass clazz, TRAPS) {
assert(clazz != nullptr, "clazz must not be null");
oop mirror = JNIHandles::resolve_non_null(clazz);
Klass* k = java_lang_Class::as_Klass(mirror);
if (k == nullptr || !k->is_array_klass()) {
THROW(vmSymbols::java_lang_InvalidClassException());

@liach
Copy link
Member Author

liach commented Jul 2, 2025

For arrayBaseOffset/IndexScale, I don't think I will change it here: they are methods that are exposed via sun.misc.Unsafe.

@liach liach changed the title Document IAE for Unsafe offset methods 8361300: Document exceptions for Unsafe offset methods Jul 2, 2025
@liach liach marked this pull request as ready for review July 2, 2025 19:21
@openjdk openjdk bot added the rfr Pull request is ready for review label Jul 2, 2025
@mlbridge
Copy link

mlbridge bot commented Jul 2, 2025

? caller : tclass;
this.tclass = tclass;
this.offset = U.objectFieldOffset(field);
this.offset = U.objectFieldOffset(field); // throws IAE for static
Copy link
Contributor

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.

@liach
Copy link
Member Author

liach commented Jul 3, 2025

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.

@liach
Copy link
Member Author

liach commented Jul 3, 2025

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)
Copy link

@ExE-Boss ExE-Boss Jul 6, 2025

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).

Copy link
Member Author

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))
Copy link
Contributor

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?

Copy link
Member Author

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


if (Modifier.isStatic(modifiers))
throw new IllegalArgumentException("Must not be a static field");

Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

@liach
Copy link
Member Author

liach commented Jul 8, 2025

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();
Copy link
Contributor

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.

Copy link
Member Author

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
Copy link
Contributor

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"));
Copy link
Contributor

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.

Copy link
Contributor

@rose00 rose00 left a 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.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jul 31, 2025
@liach
Copy link
Member Author

liach commented Aug 1, 2025

@vy Can you help review this patch too? For context, objectFieldOffset(Class, String) should only find trusted fields and is a bootstrap optimization, while the Field-accepting version is the generic API.

Copy link
Contributor

@vy vy left a 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:

  • Unsafe doc improvements
  • U::objectFieldOffset(Class,String) throws descriptive IE
  • U::objectFieldOffset1 is renamed to more descriptive knownObjectFieldOffset0
  • Atomic*FieldUpdater checks 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() {
Copy link
Contributor

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:

Suggested change
void fastObjectFieldOffset() {
void knownObjectFieldOffset() {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Aug 1, 2025
@openjdk openjdk bot added the ready Pull request is ready to be integrated label Aug 1, 2025
@liach
Copy link
Member Author

liach commented Aug 8, 2025

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

@openjdk
Copy link

openjdk bot commented Aug 8, 2025

Going to push as commit cd50d78.
Since your change was applied there have been 94 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Aug 8, 2025
@openjdk openjdk bot closed this Aug 8, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Aug 8, 2025
@openjdk
Copy link

openjdk bot commented Aug 8, 2025

@liach Pushed as commit cd50d78.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core-libs core-libs-dev@openjdk.org integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

8 participants