Skip to content

Commit

Permalink
[Java] Make UnsafeApi#arrayBaseOffset forwards compatible with JDK …
Browse files Browse the repository at this point in the history
…25+ which changed the return type to `long` whereas we keep it as `int`.
  • Loading branch information
vyazelenko committed Feb 25, 2025
1 parent 3729965 commit 21d5603
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import net.bytebuddy.implementation.bytecode.constant.NullConstant;
import net.bytebuddy.implementation.bytecode.constant.TextConstant;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
Expand All @@ -46,6 +49,7 @@

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import static net.bytebuddy.matcher.ElementMatchers.*;
Expand Down Expand Up @@ -135,6 +139,84 @@ enum GetUnsafeImplementation implements Implementation
}
}

enum ArrayBasedOffsetByteCode implements ByteCodeAppender
{
INSTANCE;

@Override
public @NotNull Size apply(
final @NotNull MethodVisitor methodVisitor,
final @NotNull Implementation.Context implementationContext,
final @NotNull MethodDescription instrumentedMethod)
{
final TypeDescription.ForLoadedType classType = new TypeDescription.ForLoadedType(Class.class);
final TypeDescription.ForLoadedType methodType = new TypeDescription.ForLoadedType(Method.class);
final TypeDescription.ForLoadedType numberType = new TypeDescription.ForLoadedType(Number.class);

final MethodDescription.InDefinedShape classForName = classType.getDeclaredMethods()
.filter(hasSignature(new MethodDescription.SignatureToken(
"forName",
new TypeDescription.ForLoadedType(Class.class),
new TypeDescription.ForLoadedType(String.class))))
.getOnly();
final MethodDescription.InDefinedShape classGetMethod = classType.getDeclaredMethods()
.filter(named("getMethod"))
.getOnly();
final MethodDescription.InDefinedShape methodInvoke = methodType.getDeclaredMethods()
.filter(named("invoke"))
.getOnly();
final MethodDescription.InDefinedShape numberIntValue =
numberType.getDeclaredMethods().filter(named("intValue")).getOnly();
final StackManipulation.Size operandStackSize = new StackManipulation.Compound(
new TextConstant(UNSAFE_CLASS.getName()),
MethodInvocation.invoke(classForName),
new TextConstant("arrayBaseOffset"),
ArrayFactory.forType(TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Class.class))
.withValues(List.of(ClassConstant.of(classType))),
MethodInvocation.invoke(classGetMethod),
MethodVariableAccess.REFERENCE.storeAt(1),

MethodVariableAccess.REFERENCE.loadFrom(1),
FieldAccess.forField(new FieldDescription.Latent(
implementationContext.getInstrumentedType(),
"UNSAFE",
ModifierContributor.Resolver.of(Ownership.STATIC, Visibility.PRIVATE, FieldManifestation.FINAL)
.resolve(),
TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(UNSAFE_CLASS),
List.of())).read(),
ArrayFactory.forType(TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class))
.withValues(List.of(MethodVariableAccess.REFERENCE.loadFrom(0))),
MethodInvocation.invoke(methodInvoke),
MethodVariableAccess.REFERENCE.storeAt(2),

MethodVariableAccess.REFERENCE.loadFrom(2),
TypeCasting.to(numberType),
MethodInvocation.invoke(numberIntValue),
MethodReturn.INTEGER
).apply(methodVisitor, implementationContext);

return new Size(operandStackSize.getMaximalSize(), 3);
}
}


enum ArrayBasedOffsetImplementation implements Implementation
{
INSTANCE;

@Override
public @NotNull InstrumentedType prepare(final @NotNull InstrumentedType instrumentedType)
{
return instrumentedType;
}

@Override
public @NotNull ByteCodeAppender appender(final @NotNull Target implementationTarget)
{
return ArrayBasedOffsetByteCode.INSTANCE;
}
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -170,11 +252,20 @@ enum GetUnsafeImplementation implements Implementation
for (final MethodDescription.InDefinedShape method : staticMethods)
{
// Redefine existing method
newBuilder = newBuilder
.method(named(method.getName()))
.intercept(method.isStatic() ? MethodDelegation.to(unsafeType) :
MethodDelegation.withDefaultConfiguration().filter(named(method.getName()))
.toField(unsafeFieldName));
if (method.getName().equals("arrayBaseOffset"))
{
newBuilder = newBuilder
.method(named("arrayBaseOffset"))
.intercept(ArrayBasedOffsetImplementation.INSTANCE);
}
else
{
newBuilder = newBuilder
.method(named(method.getName()))
.intercept(method.isStatic() ? MethodDelegation.to(unsafeType) :
MethodDelegation.withDefaultConfiguration().filter(named(method.getName()))
.toField(unsafeFieldName));
}
}

return newBuilder;
Expand Down
104 changes: 55 additions & 49 deletions buildSrc/src/main/java/org/agrona/build/UnsafeApiSourceGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.Stream;

/**
* This task generates {@code UnsafeApi.java} source file.
Expand Down Expand Up @@ -136,69 +136,75 @@ private UnsafeApi()
final StringBuilder buffer = new StringBuilder();
final String lineSeparator = System.lineSeparator();

final Method[] methods = unsafeClass.getMethods();
Arrays.sort(methods, Comparator.comparing(Method::getName).thenComparingInt(Method::getParameterCount));
final Method[] methods = Stream.of(unsafeClass.getMethods())
.filter(method -> method.getDeclaringClass() == unsafeClass &&
!method.getName().endsWith("0") &&
!method.getName().equals("getUnsafe") &&
null == method.getAnnotation(Deprecated.class))
.sorted(Comparator.comparing(Method::getName).thenComparingInt(Method::getParameterCount))
.toArray(Method[]::new);

for (final Method method : methods)
{
if (method.getDeclaringClass() == unsafeClass &&
!method.getName().endsWith("0") &&
!method.getName().equals("getUnsafe") &&
null == method.getAnnotation(Deprecated.class))
{
final Class<?>[] parameterTypes = method.getParameterTypes();
final Parameter[] parameters = method.getParameters();
final Class<?>[] parameterTypes = method.getParameterTypes();
final Parameter[] parameters = method.getParameters();

buffer.append(lineSeparator).append(" /**");
buffer.append(lineSeparator).append(" * See {@code ").append(unsafeClass.getName())
.append("#").append(method.getName());
if (parameterTypes.length > 0)
buffer.append(lineSeparator).append(" /**");
buffer.append(lineSeparator).append(" * See {@code ").append(unsafeClass.getName())
.append("#").append(method.getName());
if (parameterTypes.length > 0)
{
buffer.append('(');
for (int i = 0; i < parameters.length; i++)
{
buffer.append('(');
for (int i = 0; i < parameters.length; i++)
if (i != 0)
{
if (i != 0)
{
buffer.append(", ");
}
buffer.append(parameterTypes[i].getTypeName());
buffer.append(", ");
}
buffer.append(')');
}
buffer.append("}.").append(lineSeparator);
for (final Parameter parameter : parameters)
{
buffer.append(" * @param ").append(parameter.getName()).append(' ')
.append(parameter.getName()).append(lineSeparator);
buffer.append(parameterTypes[i].getTypeName());
}
buffer.append(')');
}
buffer.append("}.");
for (final Parameter parameter : parameters)
{
buffer.append(lineSeparator).append(" * @param ").append(parameter.getName()).append(' ')
.append(parameter.getName());
}

if (method.getReturnType() != void.class)
{
buffer.append(" * @return value").append(lineSeparator);
}
buffer.append(" */").append(lineSeparator);
if (method.getReturnType() != void.class)
{
buffer.append(lineSeparator).append(" * @return value");
}
buffer.append(lineSeparator).append(" */");

buffer.append(" public static ")
.append(TYPE_NAME.get(method.getReturnType())).append(' ')
.append(method.getName()).append("(");
buffer.append(lineSeparator).append(" public static ");
if (method.getName().equals("arrayBaseOffset"))
{
buffer.append(TYPE_NAME.get(int.class)); // JDK 25 changed to long
}
else
{
buffer.append(TYPE_NAME.get(method.getReturnType()));
}
buffer.append(' ').append(method.getName()).append("(");

for (int i = 0; i < parameters.length; i++)
for (int i = 0; i < parameters.length; i++)
{
if (i > 0)
{
if (i > 0)
{
buffer.append(',');
}

buffer.append(lineSeparator).append(" final ")
.append(TYPE_NAME.get(parameterTypes[i])).append(' ')
.append(parameters[i].getName());
buffer.append(',');
}

buffer.append(')').append(lineSeparator).append(" {").append(lineSeparator)
.append(" throw new UnsupportedOperationException(\"'")
.append(method.getName()).append("' not implemented\");")
.append(lineSeparator).append(" }").append(lineSeparator);
buffer.append(lineSeparator).append(" final ")
.append(TYPE_NAME.get(parameterTypes[i])).append(' ')
.append(parameters[i].getName());
}

buffer.append(')').append(lineSeparator).append(" {").append(lineSeparator)
.append(" throw new UnsupportedOperationException(\"'")
.append(method.getName()).append("' not implemented\");")
.append(lineSeparator).append(" }").append(lineSeparator);
}

Files.writeString(
Expand Down

0 comments on commit 21d5603

Please sign in to comment.