diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORParser.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORParser.java index f56e8154d..9eb756bab 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORParser.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORParser.java @@ -125,6 +125,29 @@ public int getFirstTag() { protected final static JacksonFeatureSet CBOR_READ_CAPABILITIES = DEFAULT_READ_CAPABILITIES.with(StreamReadCapability.EXACT_FLOATS); + /** + * Whether VarHandles are usable on this runtime; probed once at class load. + * Being {@code static final} lets the branches in {@link #_decode32Bits()}, + * {@link #_decode64Bits()} and {@link #_decodeQuad} 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 `CBORVarHandleUtil`, 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 CBORVarHandleUtil.isAvailable(); + } catch (Throwable t) { + return false; + } + } + /* /********************************************************************** /* Configuration @@ -1566,10 +1589,8 @@ private final int _nextFieldOptimized(PropertyNameMatcher matcher, final int len int inPtr = _inputPtr; // First quadbyte is easy - int q1 = (inBuf[inPtr++] & 0xFF); - q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); - q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); - q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); + int q1 = _decodeQuad(inBuf, inPtr); + inPtr += 4; if (len < 9) { int q2 = (inBuf[inPtr++] & 0xFF); @@ -1588,10 +1609,8 @@ private final int _nextFieldOptimized(PropertyNameMatcher matcher, final int len return matcher.matchByQuad(q1, q2); } - int q2 = (inBuf[inPtr++] & 0xFF); - q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); - q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); - q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); + int q2 = _decodeQuad(inBuf, inPtr); + inPtr += 4; if (len < 13) { int q3 = (inBuf[inPtr++] & 0xFF); @@ -1636,11 +1655,8 @@ private final int _nextFieldFromSymbolsLong(PropertyNameMatcher matcher, final byte[] inBuf = _inputBuffer; do { - int q = (inBuf[inPtr++] & 0xFF); - q = (q << 8) | inBuf[inPtr++] & 0xFF; - q = (q << 8) | inBuf[inPtr++] & 0xFF; - q = (q << 8) | inBuf[inPtr++] & 0xFF; - _quadBuffer[offset++] = q; + _quadBuffer[offset++] = _decodeQuad(inBuf, inPtr); + inPtr += 4; } while ((len -= 4) > 3); // and then leftovers if (len > 0) { @@ -3362,10 +3378,8 @@ private final String _findDecodedFromSymbols(final int len) throws JacksonExcept int inPtr = _inputPtr; // First quadbyte is easy - int q1 = (inBuf[inPtr++] & 0xFF); - q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); - q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); - q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); + int q1 = _decodeQuad(inBuf, inPtr); + inPtr += 4; if (len < 9) { int q2 = _padQuadForNulls(inBuf[inPtr++]); @@ -3384,10 +3398,8 @@ private final String _findDecodedFromSymbols(final int len) throws JacksonExcept return _symbols.findName(q1, q2); } - int q2 = (inBuf[inPtr++] & 0xFF); - q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); - q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); - q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); + int q2 = _decodeQuad(inBuf, inPtr); + inPtr += 4; if (len < 13) { int q3 = _padQuadForNulls(inBuf[inPtr++]); @@ -3434,11 +3446,8 @@ private final String _findDecodedLong(int len, int q1, int q2) throws JacksonExc final byte[] inBuf = _inputBuffer; do { - int q = (inBuf[inPtr++] & 0xFF); - q = (q << 8) | inBuf[inPtr++] & 0xFF; - q = (q << 8) | inBuf[inPtr++] & 0xFF; - q = (q << 8) | inBuf[inPtr++] & 0xFF; - _quadBuffer[offset++] = q; + _quadBuffer[offset++] = _decodeQuad(inBuf, inPtr); + inPtr += 4; } while ((len -= 4) > 3); // and then leftovers if (len > 0) { @@ -3484,6 +3493,23 @@ private final static int _padQuadForNulls(int firstByte) { return (firstByte & 0xFF) | 0xFFFFFF00; } + /** + * Helper method for decoding 4 bytes of an Object property name into the + * "quad" (big-endian {@code int}) form used by {@code ByteQuadsCanonicalizer}. + * Caller MUST have verified that 4 bytes are readable at given offset. + * + * @since 3.3 + */ + private final static int _decodeQuad(byte[] buffer, int offset) { + if (_VARHANDLE_AVAILABLE) { + return CBORVarHandleUtil.getInt(buffer, offset); + } + return ((buffer[offset] & 0xFF) << 24) + | ((buffer[offset+1] & 0xFF) << 16) + | ((buffer[offset+2] & 0xFF) << 8) + | (buffer[offset+3] & 0xFF); + } + /* /********************************************************************** /* Internal methods, skipping @@ -3776,8 +3802,14 @@ private final int _decode32Bits() throws JacksonException { return _slow32(); } final byte[] b = _inputBuffer; - int v = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16) - + ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF); + final int v; + if (_VARHANDLE_AVAILABLE) { + v = CBORVarHandleUtil.getInt(b, ptr); + ptr += 4; + } else { + v = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16) + + ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF); + } _inputPtr = ptr; return v; } @@ -3807,6 +3839,13 @@ private final long _decode64Bits() throws JacksonException { return _slow64(); } final byte[] b = _inputBuffer; + if (_VARHANDLE_AVAILABLE) { + // NOTE: identical to `_long()` of the two 32-bit halves below, since + // that is just a big-endian 8-byte read spelled out + final long l = CBORVarHandleUtil.getLong(b, ptr); + _inputPtr = ptr + 8; + return l; + } int i1 = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16) + ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF); int i2 = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16) diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java index 4cc74b553..ece81b047 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java @@ -51,8 +51,8 @@ static boolean isAvailable() { return LONG_BE != null; } - // Helper methods that write primitives via the class's own VarHandle fields. - // Only called when the corresponding field is non-null, which implies + // Helper methods that read/write primitives via the class's own VarHandle + // fields. Only called when the corresponding field is non-null, which implies // VarHandle is available on this runtime. static void setInt(byte[] array, int offset, int value) { @@ -62,4 +62,24 @@ static void setInt(byte[] array, int offset, int value) { static void setLong(byte[] array, int offset, long value) { LONG_BE.set(array, offset, value); } + + /** + * Reads 4 bytes at given offset as a big-endian {@code int}; caller MUST + * have verified that {@code offset+4} is within bounds of given array. + * + * @since 3.3 + */ + static int getInt(byte[] array, int offset) { + return (int) INT_BE.get(array, offset); + } + + /** + * Reads 8 bytes at given offset as a big-endian {@code long}; caller MUST + * have verified that {@code offset+8} is within bounds of given array. + * + * @since 3.3 + */ + static long getLong(byte[] array, int offset) { + return (long) LONG_BE.get(array, offset); + } }