Skip to content
Draft
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
Expand Up @@ -153,9 +153,14 @@ public final void writeField(TField field, WriteCallback<Void> callback) throws
}

public final void writeStruct(TStruct struct, WriteCallback<Void> callback) throws TException {
writeStructBegin(struct);
callback.call(null);
writeStructEnd();
incrementRecursionDepth();
try {
writeStructBegin(struct);
callback.call(null);
writeStructEnd();
} finally {
decrementRecursionDepth();
}
}

public final void writeMessage(TMessage message, WriteCallback<Void> callback) throws TException {
Expand Down Expand Up @@ -190,10 +195,15 @@ public final <T> T readMessage(ReadCallback<TMessage, T> callback) throws TExcep
* @throws TException when any sub-operation failed
*/
public final <T> T readStruct(ReadCallback<TStruct, T> callback) throws TException {
TStruct tStruct = readStructBegin();
T t = callback.accept(tStruct);
readStructEnd();
return t;
incrementRecursionDepth();
try {
TStruct tStruct = readStructBegin();
T t = callback.accept(tStruct);
readStructEnd();
return t;
} finally {
decrementRecursionDepth();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.thrift

import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import org.apache.thrift.protocol.TBinaryProtocol
import org.apache.thrift.protocol.TProtocol
import org.apache.thrift.protocol.TProtocolException
import org.apache.thrift.transport.TMemoryInputTransport
import org.junit.jupiter.api.Test

internal class RecursionDepthTest {

private fun makeProtocol(): TProtocol {
val transport = TMemoryInputTransport(ByteArray(0))
return TBinaryProtocol(transport)
}

@Test
fun testReadStructDepthLimitThrows() {
val proto = makeProtocol()
// Drive depth to the limit by direct increments
repeat(64) { proto.incrementRecursionDepth() }
// Next readStruct must throw DEPTH_LIMIT
val ex = assertFailsWith<TProtocolException> { proto.readStruct { _ -> null } }
assertEquals(TProtocolException.DEPTH_LIMIT, ex.type)
// Depth counter must be restored by the finally block
repeat(64) { proto.decrementRecursionDepth() }
}

@Test
fun testWriteStructDepthLimitThrows() {
val proto = makeProtocol()
repeat(64) { proto.incrementRecursionDepth() }
val struct = org.apache.thrift.protocol.TStruct("Test")
val ex = assertFailsWith<TProtocolException> {
proto.writeStruct(struct) {}
}
assertEquals(TProtocolException.DEPTH_LIMIT, ex.type)
repeat(64) { proto.decrementRecursionDepth() }
}

@Test
fun testReadStructDepthLimitMessageNonEmpty() {
val proto = makeProtocol()
repeat(64) { proto.incrementRecursionDepth() }
val ex = assertFailsWith<TProtocolException> { proto.readStruct { _ -> null } }
assertNotNull(ex.message)
repeat(64) { proto.decrementRecursionDepth() }
}

@Test
fun testDepthCounterRestoredAfterLimitOnRead() {
val proto = makeProtocol()
repeat(63) { proto.incrementRecursionDepth() }
// At depth 63, one more increment should work
proto.incrementRecursionDepth()
// Now at 64 — next readStruct must throw and restore
assertFailsWith<TProtocolException> { proto.readStruct { _ -> null } }
// After the throw, depth is still 64 (we manually decremented all ours after)
repeat(64) { proto.decrementRecursionDepth() }
// Depth now 0 — a readStruct should no longer throw
proto.readStruct { _ -> null }
}

@Test
fun testReadStructSucceedsUnderLimit() {
val proto = makeProtocol()
// 63 nested increments — 64th readStruct should still succeed
repeat(63) { proto.incrementRecursionDepth() }
proto.readStruct { _ -> null } // must not throw (depth goes 64 → 63 after)
repeat(63) { proto.decrementRecursionDepth() }
}
}
Loading