blob: 1a886d119ae82fcc80f10da75dd68aeb164def9f [file]
'Recursion-depth limit test for the *generated* Smalltalk struct read/write path.
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.
This drives the recursion-depth limit through the generated struct read/write
code (DeepClient sendEchoA: / recvEcho), not by poking the depth counter in
isolation. The recursive types in test/Recursive.thrift cannot be used: the
Smalltalk generator inline-expands nested struct serialization and recurses
without bound at code-generation time on a recursive type, so a finite chain of
distinct struct types (A1..A65) is used to nest past the limit (64) instead.
To run (Squeak/Pharo, or GNU Smalltalk with SUnit):
thrift --gen st RecursionDepthTest.thrift
file in: thrift.st, the generated RecursionDepthTest.st, then this file.
Run the TProtocolRecursionDepthTest suite.
'!
"A minimal in-memory transport so the round-trip needs no socket: writeByte:
hands us an Array, writeString: a String; we normalise both to bytes and replay
them as a ByteArray (first -> integer for readByte, asString -> String)."
TTransport subclass: #TMemoryBuffer
instanceVariableNames: 'bytes pos'
classVariableNames: ''
poolDictionaries: ''
category: 'Thrift-Tests'!
!TMemoryBuffer class methodsFor: 'instance creation'!
new
^ super new init! !
!TMemoryBuffer methodsFor: 'init'!
init
bytes := OrderedCollection new.
pos := 0! !
!TMemoryBuffer methodsFor: 'transport'!
write: aCollection
aCollection do: [:each | bytes add: each asInteger]! !
!TMemoryBuffer methodsFor: 'transport'!
read: anInteger
| chunk |
chunk := bytes copyFrom: pos + 1 to: pos + anInteger.
pos := pos + anInteger.
^ chunk asByteArray! !
!TMemoryBuffer methodsFor: 'transport'!
flush! !
!TMemoryBuffer methodsFor: 'transport'!
open
^ self! !
!TMemoryBuffer methodsFor: 'transport'!
close! !
!TMemoryBuffer methodsFor: 'transport'!
isOpen
^ true! !
TestCase subclass: #TProtocolRecursionDepthTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Thrift-Tests'!
!TProtocolRecursionDepthTest methodsFor: 'support'!
recursionLimit
"The limit enforced by TProtocol>>incrementRecursionDepth."
^ 64! !
!TProtocolRecursionDepthTest methodsFor: 'support'!
clientOn: aTransport
^ DeepClient new
inProtocol: (TBinaryProtocol new transport: aTransport);
yourself! !
!TProtocolRecursionDepthTest methodsFor: 'support'!
chainOfDepth: depth
"An A1->A2->..->A`depth` chain of distinct structs; deeper links stay nil.
Writing it drives `depth` nested struct writers, i.e. depth levels deep."
| node |
node := nil.
depth to: 1 by: -1 do: [:i | | cur |
cur := (Smalltalk at: ('A', i printString) asSymbol) new.
i = 65 ifTrue: [cur x: 1] ifFalse: [cur f: node].
node := cur].
^ node! !
!TProtocolRecursionDepthTest methodsFor: 'support'!
craftReplyOfDepth: n
"Craft an echo_result REPLY whose success value nests n structs deep, using
raw protocol primitives so the depth counter is bypassed on the way in (a
normal write of an over-limit value would itself be rejected). Reading it
back drives the generated struct reader n (+1 for the result) levels deep."
| transport proto emit |
transport := TMemoryBuffer new.
proto := TBinaryProtocol new transport: transport.
proto writeMessageBegin: (TCallMessage new name: 'echo'; seqid: 0; yourself).
proto writeFieldBegin: (TField new name: 'success'; type: TType struct; id: 0).
emit := nil.
emit := [:i |
i < n ifTrue: [
proto writeFieldBegin: (TField new name: 'f'; type: TType struct; id: 1).
emit value: i + 1].
proto writeFieldStop].
emit value: 1.
proto writeFieldStop.
proto writeMessageEnd.
^ transport! !
!TProtocolRecursionDepthTest methodsFor: 'support'!
craftUnknownFieldReplyOfDepth: n
"Craft an echo_result REPLY whose success value nests n structs deep under a
field id the generated reader does not declare, so every level is handed to
skip: instead of the generated struct reader."
| transport proto emit |
transport := TMemoryBuffer new.
proto := TBinaryProtocol new transport: transport.
proto writeMessageBegin: (TCallMessage new name: 'echo'; seqid: 0; yourself).
proto writeFieldBegin: (TField new name: 'success'; type: TType struct; id: 0).
emit := nil.
emit := [:i |
i < n ifTrue: [
proto writeFieldBegin: (TField new name: 'unknown'; type: TType struct; id: 99).
emit value: i + 1].
proto writeFieldStop].
emit value: 1.
proto writeFieldStop.
proto writeMessageEnd.
^ transport! !
!TProtocolRecursionDepthTest methodsFor: 'tests'!
testWriteAtLimitRoundTrips
"A chain exactly at the limit serializes without error."
self
shouldnt: [(self clientOn: TMemoryBuffer new) sendEchoA: (self chainOfDepth: self recursionLimit)]
raise: TProtocolError! !
!TProtocolRecursionDepthTest methodsFor: 'tests'!
testWriteOverLimitIsRejected
"A chain one level past the limit is rejected with TProtocolError."
self
should: [(self clientOn: TMemoryBuffer new) sendEchoA: (self chainOfDepth: self recursionLimit + 1)]
raise: TProtocolError! !
!TProtocolRecursionDepthTest methodsFor: 'tests'!
testReadWithinLimitRoundTrips
"A shallow payload is read back through the generated reader without error."
self
shouldnt: [(self clientOn: (self craftReplyOfDepth: 3)) recvEcho]
raise: TProtocolError! !
!TProtocolRecursionDepthTest methodsFor: 'tests'!
testReadOverLimitIsRejected
"A payload nested past the limit is rejected with TProtocolError on read."
self
should: [(self clientOn: (self craftReplyOfDepth: self recursionLimit + 6)) recvEcho]
raise: TProtocolError! !
!TProtocolRecursionDepthTest methodsFor: 'tests'!
testSkipWithinLimitRoundTrips
"Nesting a skip: can carry is read back without error."
self
shouldnt: [(self clientOn: (self craftUnknownFieldReplyOfDepth: 3)) recvEcho]
raise: TProtocolError! !
!TProtocolRecursionDepthTest methodsFor: 'tests'!
testSkipOverLimitIsRejected
"Nesting past the limit under a field no reader declares reaches skip:,
which is bounded by the same limit."
self
should: [(self clientOn: (self craftUnknownFieldReplyOfDepth: self recursionLimit + 6)) recvEcho]
raise: TProtocolError! !