Skip to content

Commit

Permalink
8291065: Creating a VarHandle for a static field triggers class initi…
Browse files Browse the repository at this point in the history
…alization

Reviewed-by: mchung, psandoz
  • Loading branch information
liach authored and Mandy Chung committed Jul 18, 2023
1 parent a53345a commit 201e3bc
Show file tree
Hide file tree
Showing 10 changed files with 744 additions and 160 deletions.
Expand Up @@ -68,15 +68,16 @@ MethodType accessModeTypeUncached(AccessType at) {
}

@Override
@ForceInline
VarHandle asDirect() {
return directTarget;
}

@Override
public VarHandle withInvokeExactBehavior() {
return hasInvokeExactBehavior()
? this
: new IndirectVarHandle(target, value, coordinates, handleFactory, vform, true);
? this
: new IndirectVarHandle(target, value, coordinates, handleFactory, vform, true);
}

@Override
Expand All @@ -86,6 +87,7 @@ public VarHandle withInvokeBehavior() {
: new IndirectVarHandle(target, value, coordinates, handleFactory, vform, false);
}

@Override
@ForceInline
boolean checkAccessModeThenIsDirect(VarHandle.AccessDescriptor ad) {
super.checkAccessModeThenIsDirect(ad);
Expand All @@ -103,9 +105,4 @@ MethodHandle getMethodHandleUncached(int mode) {
MethodHandle targetHandle = target.getMethodHandle(mode); // might throw UOE of access mode is not supported, which is ok
return handleFactory.apply(AccessMode.values()[mode], targetHandle);
}

@Override
public MethodHandle toMethodHandle(AccessMode accessMode) {
return getMethodHandle(accessMode.ordinal()).bindTo(directTarget);
}
}
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/

package java.lang.invoke;

import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;

import java.util.Optional;

import static java.lang.invoke.MethodHandleStatics.UNSAFE;
import static java.lang.invoke.MethodHandleStatics.uncaughtException;

/**
* A lazy initializing var handle. It lazily initializes the referenced class before
* any invocation of the target var handle to prevent reading uninitialized static
* field values.
*/
final class LazyInitializingVarHandle extends VarHandle {

// Implementation notes:
// We put a barrier on both target() (for VH form impl direct invocation)
// and on getMethodHandle() (for indirect VH invocation, toMethodHandle)
private final VarHandle target;
private final Class<?> refc;
private @Stable boolean initialized;

LazyInitializingVarHandle(VarHandle target, Class<?> refc) {
super(target.vform, target.exact);
this.target = target;
this.refc = refc;
}

@Override
MethodType accessModeTypeUncached(AccessType at) {
return target.accessModeType(at.ordinal());
}

@Override
@ForceInline
VarHandle asDirect() {
return target;
}

@Override
@ForceInline
VarHandle target() {
ensureInitialized();
return target;
}

@Override
public VarHandle withInvokeExactBehavior() {
if (!initialized && hasInvokeExactBehavior())
return this;
var exactTarget = target.withInvokeExactBehavior();
return initialized ? exactTarget : new LazyInitializingVarHandle(exactTarget, refc);
}

@Override
public VarHandle withInvokeBehavior() {
if (!initialized && !hasInvokeExactBehavior())
return this;
var nonExactTarget = target.withInvokeBehavior();
return initialized ? nonExactTarget : new LazyInitializingVarHandle(nonExactTarget, refc);
}

@Override
public Optional<VarHandleDesc> describeConstable() {
return target.describeConstable();
}

@Override
public MethodHandle getMethodHandleUncached(int accessMode) {
var mh = target.getMethodHandle(accessMode);
if (this.initialized)
return mh;

return MethodHandles.collectArguments(mh, 0, ensureInitializedMh()).bindTo(this);
}

@ForceInline
private void ensureInitialized() {
if (this.initialized)
return;

initialize();
}

private void initialize() {
UNSAFE.ensureClassInitialized(refc);
this.initialized = true;

this.methodHandleTable = target.methodHandleTable;
}

private static @Stable MethodHandle MH_ensureInitialized;

private static MethodHandle ensureInitializedMh() {
var mh = MH_ensureInitialized;
if (mh != null)
return mh;

try {
return MH_ensureInitialized = MethodHandles.lookup().findVirtual(
LazyInitializingVarHandle.class,
"ensureInitialized",
MethodType.methodType(void.class));
} catch (Throwable ex) {
throw uncaughtException(ex);
}
}
}
Expand Up @@ -4201,7 +4201,7 @@ private VarHandle getFieldVarHandleCommon(byte getRefKind, byte putRefKind,
}
refc = lookupClass();
}
return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(),
return VarHandles.makeFieldHandle(getField, refc,
this.allowedModes == TRUSTED && !getField.isTrustedFinalField());
}
/** Check access and get the requested constructor. */
Expand Down
50 changes: 39 additions & 11 deletions src/java.base/share/classes/java/lang/invoke/VarHandle.java
Expand Up @@ -472,7 +472,8 @@
* @since 9
*/
public abstract sealed class VarHandle implements Constable
permits IndirectVarHandle, VarHandleSegmentViewBase,
permits IndirectVarHandle, LazyInitializingVarHandle,
VarHandleSegmentViewBase,
VarHandleByteArrayAsChars.ByteArrayViewVarHandle,
VarHandleByteArrayAsDoubles.ByteArrayViewVarHandle,
VarHandleByteArrayAsFloats.ByteArrayViewVarHandle,
Expand Down Expand Up @@ -518,10 +519,23 @@ public abstract sealed class VarHandle implements Constable
this.exact = exact;
}

RuntimeException unsupported() {
return new UnsupportedOperationException();
/**
* Returns the target VarHandle. Subclasses may override this method to implement
* additional logic for example lazily initializing the declaring class of a static field var handle.
*/
@ForceInline
VarHandle target() {
return asDirect();
}

/**
* Returns the direct target VarHandle. Indirect VarHandle subclasses should implement
* this method.
*
* @see #getMethodHandle(int)
* @see #checkAccessModeThenIsDirect(AccessDescriptor)
*/
@ForceInline
VarHandle asDirect() {
return this;
}
Expand Down Expand Up @@ -2062,13 +2076,19 @@ public final MethodType accessModeType(AccessMode accessMode) {

/**
* Validates that the given access descriptors method type matches up with
* the access mode of this VarHandle, then returns if this is a direct
* method handle. These operations were grouped together to slightly
* the access mode of this VarHandle, then returns if this is direct.
* These operations were grouped together to slightly
* improve efficiency during startup/warmup.
*
* A direct VarHandle's VarForm has implementation MemberNames that can
* be linked directly. If a VarHandle is indirect, it must override
* {@link #isAccessModeSupported} and {@link #getMethodHandleUncached}
* which access MemberNames.
*
* @return true if this is a direct VarHandle, false if it's an indirect
* VarHandle.
* @throws WrongMethodTypeException if there's an access type mismatch
* @see #asDirect()
*/
@ForceInline
boolean checkAccessModeThenIsDirect(VarHandle.AccessDescriptor ad) {
Expand Down Expand Up @@ -2144,7 +2164,7 @@ public boolean isAccessModeSupported(AccessMode accessMode) {
public MethodHandle toMethodHandle(AccessMode accessMode) {
if (isAccessModeSupported(accessMode)) {
MethodHandle mh = getMethodHandle(accessMode.ordinal());
return mh.bindTo(this);
return mh.bindTo(asDirect());
}
else {
// Ensure an UnsupportedOperationException is thrown
Expand Down Expand Up @@ -2186,6 +2206,14 @@ final MethodHandle getMethodHandle(int mode) {
return mh;
}

/**
* Computes a method handle that can be passed the {@linkplain #asDirect() direct}
* var handle of this var handle with the given access mode. Pre/postprocessing
* such as argument or return value filtering should be done by the returned
* method handle.
*
* @throws UnsupportedOperationException if the access mode is not supported
*/
MethodHandle getMethodHandleUncached(int mode) {
MethodType mt = accessModeType(AccessMode.values()[mode]).
insertParameterTypes(0, VarHandle.class);
Expand Down Expand Up @@ -2401,13 +2429,13 @@ public ClassDesc varType() {
public VarHandle resolveConstantDesc(MethodHandles.Lookup lookup)
throws ReflectiveOperationException {
return switch (kind) {
case FIELD -> lookup.findVarHandle((Class<?>) declaringClass.resolveConstantDesc(lookup),
case FIELD -> lookup.findVarHandle(declaringClass.resolveConstantDesc(lookup),
constantName(),
(Class<?>) varType.resolveConstantDesc(lookup));
case STATIC_FIELD -> lookup.findStaticVarHandle((Class<?>) declaringClass.resolveConstantDesc(lookup),
varType.resolveConstantDesc(lookup));
case STATIC_FIELD -> lookup.findStaticVarHandle(declaringClass.resolveConstantDesc(lookup),
constantName(),
(Class<?>) varType.resolveConstantDesc(lookup));
case ARRAY -> MethodHandles.arrayElementVarHandle((Class<?>) declaringClass.resolveConstantDesc(lookup));
varType.resolveConstantDesc(lookup));
case ARRAY -> MethodHandles.arrayElementVarHandle(declaringClass.resolveConstantDesc(lookup));
default -> throw new InternalError("Cannot reach here");
};
}
Expand Down

1 comment on commit 201e3bc

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.