-
-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathasserts.cpp
More file actions
245 lines (202 loc) · 10.5 KB
/
Copy pathasserts.cpp
File metadata and controls
245 lines (202 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
serialize
Copyright © 2016 - 2026, Más Bandwidth LLC.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
The write side debug assertions, tested by observing them fire.
A serialize macro that narrows the caller's value before asserting on it cannot report
the input the assertion exists to diagnose: the assertion sees a value already truncated
to the narrower width, and 2^32 + 5 truncates to 5, which is in range by construction.
Every macro that narrows now validates the caller's original expression first, and this
file is where that ordering is proved.
serialize_assert is defined here, before serialize.h, which the header honors. A
recording definition makes "the assertion fired" an ordinary observable result rather
than a process death: it runs identically on all three platforms in the matrix and in
both build configurations, where an aborting assertion would need a death test whose
shape differs on each of them, and it also names WHICH assertion fired.
InitializePadded's too-small destination is a memory claim, not a decoded-value one.
The unit test can only exercise the refusal under NDEBUG, because a live assert aborts
first. Recording here lets both halves run in every configuration, including the
sanitizer job.
*/
#include <stdio.h>
#include <stdint.h>
static int assert_fires = 0;
static const char * assert_condition = "";
#define serialize_assert( condition ) \
do \
{ \
if ( !( condition ) ) \
{ \
/* the FIRST assertion to fire is the diagnostic one: a case that gets past */ \
/* it goes on to trip the downstream assertions the narrowing used to hide */ \
if ( assert_fires == 0 ) \
{ \
assert_condition = #condition; \
} \
assert_fires++; \
} \
} \
while ( 0 )
#include "serialize.h"
static int failures = 0;
static void check( bool condition, const char * what )
{
printf( " %-62s %s\n", what, condition ? "pass" : "FAILED" );
if ( !condition )
{
failures++;
}
}
// the assertion counter is global to the translation unit, so each case starts from zero
static void reset()
{
assert_fires = 0;
assert_condition = "";
}
static void test_value_in_int_relative_domain()
{
printf( " serialize::value_in_int_relative_domain\n" );
check( serialize::value_in_int_relative_domain( 0 ), "zero is in the domain" );
check( serialize::value_in_int_relative_domain( 2147483647 ), "the top of the domain is in it" );
check( !serialize::value_in_int_relative_domain( -1 ), "a negative value is out of the domain" );
check( !serialize::value_in_int_relative_domain( int64_t( 2147483648LL ) ), "2^31 is out of the domain" );
// the point of the helper: a wide value whose low 32 bits are in the domain
check( !serialize::value_in_int_relative_domain( int64_t( 4294967301LL ) ), "2^32 + 5 is out of the domain, not 5" );
check( !serialize::value_in_int_relative_domain( uint64_t( 1 ) << 63 ), "2^63 is out of the domain" );
check( !serialize::value_in_int_relative_domain( uint32_t( 4294967295u ) ), "an unsigned value above the domain is out of it" );
}
static void test_write_int_relative_asserts_on_the_callers_value()
{
printf( " write_int_relative\n" );
uint8_t buffer[64];
// a wide current whose low 32 bits are in the domain and above previous. Narrowed to int
// first, this is 5, which is a legal current for a previous of 4 -- and that is what the
// helper's assertion used to be handed.
{
reset();
serialize::WriteStream stream( buffer, (int64_t) sizeof( buffer ) );
const int64_t current = 4294967301LL; // 2^32 + 5
const int previous = 4;
write_int_relative( stream, previous, current );
check( assert_fires > 0, "a wide out of domain current trips the assertion" );
printf( " assertion: %s\n", assert_condition );
}
// the negative control: the same call one step inside the domain must not assert
{
reset();
serialize::WriteStream stream( buffer, (int64_t) sizeof( buffer ) );
const int64_t current = 5;
const int previous = 4;
write_int_relative( stream, previous, current );
check( assert_fires == 0, "an in domain current does not trip it" );
}
// a current at the top of the domain is legal, and 2^31 is one step outside it
{
reset();
serialize::WriteStream stream( buffer, (int64_t) sizeof( buffer ) );
const int64_t current = 2147483647LL;
const int previous = 2147483646;
write_int_relative( stream, previous, current );
check( assert_fires == 0, "a current at the top of the domain does not trip it" );
}
{
reset();
serialize::WriteStream stream( buffer, (int64_t) sizeof( buffer ) );
const int64_t current = 2147483648LL;
const int previous = 2147483646;
write_int_relative( stream, previous, current );
check( assert_fires > 0, "a current one step above the domain trips it" );
printf( " assertion: %s\n", assert_condition );
}
}
#if defined( SERIALIZE_HAS_COMPILE_TIME_SURFACE )
template <typename Stream> bool serialize_compile_time_bounded( Stream & stream, int64_t & value )
{
serialize_int_compile_time( stream, value, 0, 1000 );
return true;
}
static void test_serialize_int_compile_time_asserts_on_the_callers_value()
{
printf( " serialize_int_compile_time\n" );
uint8_t buffer[64];
// 2^32 narrows to 0, which is in [0,1000]: the assertion inside SerializeIntConst is
// handed a value the narrowing already made legal
{
reset();
serialize::WriteStream stream( buffer, (int64_t) sizeof( buffer ) );
int64_t value = 4294967296LL;
serialize_compile_time_bounded( stream, value );
check( assert_fires > 0, "a wide out of range value trips the assertion" );
printf( " assertion: %s\n", assert_condition );
}
{
reset();
serialize::WriteStream stream( buffer, (int64_t) sizeof( buffer ) );
int64_t value = 500;
serialize_compile_time_bounded( stream, value );
check( assert_fires == 0, "an in range value does not trip it" );
}
}
#endif // #if defined( SERIALIZE_HAS_COMPILE_TIME_SURFACE )
static void test_initialize_padded_refuses_a_short_destination()
{
printf( " ReadStream::InitializePadded\n" );
uint8_t payload[11];
memset( payload, 0xAB, sizeof( payload ) );
{
reset();
uint8_t dest[16];
memset( dest, 0xFF, sizeof( dest ) );
serialize::ReadStream stream;
bool ok = stream.InitializePadded( dest, (int64_t) sizeof( dest ), payload, (int64_t) sizeof( payload ) );
check( assert_fires > 0, "a destination that cannot hold payload+8 trips the assertion" );
check( ok == false, "and refuses in every build" );
check( dest[0] == (uint8_t) 0xFF, "copying nothing" );
uint32_t after = 0xFFFFFFFFu;
check( stream.SerializeBits( after, 8 ) == false, "the stream is latched" );
check( after == 0xFFFFFFFFu, "a refused read leaves the caller's value" );
printf( " assertion: %s\n", assert_condition );
}
{
reset();
uint8_t dest[19];
memset( dest, 0xFF, sizeof( dest ) );
serialize::ReadStream stream;
bool ok = stream.InitializePadded( dest, (int64_t) sizeof( dest ), payload, (int64_t) sizeof( payload ) );
check( assert_fires == 0, "exactly payload+8 does not trip it" );
check( ok == true, "and is accepted" );
check( dest[0] == (uint8_t) 0xAB, "the payload is copied" );
check( dest[11] == 0, "the slack is zeroed" );
}
}
int main()
{
printf( "\nwrite side assertions\n\n" );
test_value_in_int_relative_domain();
test_write_int_relative_asserts_on_the_callers_value();
#if defined( SERIALIZE_HAS_COMPILE_TIME_SURFACE )
test_serialize_int_compile_time_asserts_on_the_callers_value();
#endif
test_initialize_padded_refuses_a_short_destination();
if ( failures > 0 )
{
printf( "\n*** %d ASSERTION TESTS FAILED ***\n\n", failures );
return 1;
}
printf( "\n*** ALL ASSERTION TESTS PASS ***\n\n" );
return 0;
}