Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package tools.jackson.dataformat.avro.deser;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;

/**
* Utility class that provides {@link VarHandle}-based access for reading
* multi-byte primitives out of byte arrays.
*<p>
* NOTE: handles here are LITTLE-endian, unlike the big-endian ones other
* Jackson binary backends need: Avro encodes {@code float} and {@code double}
* as little-endian IEEE-754.
*<p>
* IMPORTANT: this class references {@link VarHandle} in field and method
* signatures, so on a runtime that does not provide {@code java.lang.invoke.VarHandle}
* at all it will fail to <i>link</i>, before any code here gets a chance to run.
* Callers MUST therefore both:
*<ol>
* <li>load this class from within a {@code try}/{@code catch (Throwable)} block,
* so that {@link LinkageError} is caught, and</li>
* <li>keep the byte-shifting fallback in a class that does not reference
* {@link VarHandle}, so the fallback path never resolves this class.</li>
*</ol>
* {@code JacksonAvroParserImpl} does both; see it for the pattern.
*
* @since 3.3
*/
final class AvroVarHandleUtil
{
/**
* VarHandle for reading 4 little-endian bytes as an {@code int}.
* {@code null} if byte-array views are unsupported.
*/
private static final VarHandle INT_LE;

/**
* VarHandle for reading 8 little-endian bytes as a {@code long}.
* {@code null} if byte-array views are unsupported.
*/
private static final VarHandle LONG_LE;

static {
VarHandle intLe = null;
VarHandle longLe = null;
try {
intLe = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
longLe = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
} catch (Throwable t) {
// Byte-array views not supported: caller falls back to byte shifting
}
INT_LE = intLe;
// assigned last: non-null implies the handle above resolved too
LONG_LE = longLe;
}

private AvroVarHandleUtil() { }

/**
* @return {@code true} if the {@code getXxx()} methods may be called; if
* {@code false}, caller MUST use its own byte-shifting fallback
*/
static boolean isAvailable() {
return LONG_LE != null;
}

// Helper methods that read primitives via the class's own VarHandle fields.
// Only called when {@link #isAvailable()} returned true; the handles are
// dereferenced unconditionally and the fallback lives in the caller.

/**
* Reads 4 bytes at given offset as a little-endian {@code int}; caller MUST
* have verified that {@code offset+4} is within bounds of given array.
*/
static int getIntLE(byte[] array, int offset) {
return (int) INT_LE.get(array, offset);
}

/**
* Reads 8 bytes at given offset as a little-endian {@code long}; caller MUST
* have verified that {@code offset+8} is within bounds of given array.
*/
static long getLongLE(byte[] array, int offset) {
return (long) LONG_LE.get(array, offset);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,31 @@ public class JacksonAvroParserImpl extends AvroParserImpl
}
sUtf8UnitLengths = table;
}


/**
* Whether VarHandles are usable on this runtime; probed once at class load.
* Being {@code static final} lets the branches in {@link #decodeFloat()} and
* {@link #decodeDouble()} fold away at JIT time.
*
* @since 3.3
*/
private final static boolean _VARHANDLE_AVAILABLE = _checkVarHandleAvailable();

private static boolean _checkVarHandleAvailable() {
// NOTE: this call is what first loads `AvroVarHandleUtil`, and that class
// names `VarHandle` in its field/method signatures. On a runtime lacking
// `java.lang.invoke.VarHandle` (some Android builds) loading it raises
// `NoClassDefFoundError` -- an Error, not an Exception -- so `Throwable`
// is what has to be caught here. Without this guard the failure would
// propagate out of this class's initializer and make the parser unusable.
try {
return AvroVarHandleUtil.isAvailable();
} catch (Throwable t) {
return false;
}
}


/*
/**********************************************************************
/* Input source config
Expand Down Expand Up @@ -523,8 +547,14 @@ public JsonToken decodeFloat() throws IOException {
}
final byte[] buf = _inputBuffer;
_inputPtr = ptr+4;
int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
| ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
// Avro encodes float as little-endian IEEE-754
final int i;
if (_VARHANDLE_AVAILABLE) {
i = AvroVarHandleUtil.getIntLE(buf, ptr);
} else {
i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
| ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
}
_numberFloat = Float.intBitsToFloat(i);
_numTypesValid = NR_FLOAT;
return JsonToken.VALUE_NUMBER_FLOAT;
Expand All @@ -543,15 +573,22 @@ public JsonToken decodeDouble() throws IOException {
ptr = _inputPtr;
}
final byte[] buf = _inputBuffer;
int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
| ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
ptr += 4;
int i2 = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
| ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);

// Avro encodes double as little-endian IEEE-754; the two 32-bit halves
// below combine to exactly a little-endian 8-byte read
final long l;
if (_VARHANDLE_AVAILABLE) {
l = AvroVarHandleUtil.getLongLE(buf, ptr);
ptr += 4;
} else {
int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
| ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
ptr += 4;
int i2 = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
| ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
l = (((long) i) & 0xffffffffL) | (((long) i2) << 32);
}
_inputPtr = ptr+4;
_numberDouble = Double.longBitsToDouble((((long) i) & 0xffffffffL)
| (((long) i2) << 32));
_numberDouble = Double.longBitsToDouble(l);
_numTypesValid = NR_DOUBLE;
return JsonToken.VALUE_NUMBER_FLOAT;
}
Expand Down