cyclonedds/src/core/ddsc/tests/loan.c

322 lines
12 KiB
C
Raw Normal View History

/*
* Copyright(c) 2006 to 2020 ADLINK Technology Limited and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
#include <stdio.h>
#include "dds/dds.h"
#include "test_common.h"
static dds_entity_t participant, topic, reader, writer, read_condition, read_condition_unread;
static void create_entities (void)
{
char topicname[100];
struct dds_qos *qos;
create_unique_topic_name ("ddsc_return_loan_test", topicname, sizeof topicname);
participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL);
CU_ASSERT_FATAL (participant > 0);
qos = dds_create_qos ();
dds_qset_reliability (qos, DDS_RELIABILITY_RELIABLE, 0);
dds_qset_history (qos, DDS_HISTORY_KEEP_ALL, 1);
topic = dds_create_topic (participant, &RoundTripModule_DataType_desc, topicname, qos, NULL);
CU_ASSERT_FATAL (topic > 0);
dds_delete_qos (qos);
writer = dds_create_writer (participant, topic, NULL, NULL);
CU_ASSERT_FATAL (writer > 0);
reader = dds_create_reader (participant, topic, NULL, NULL);
CU_ASSERT_FATAL (reader > 0);
read_condition = dds_create_readcondition (reader, DDS_ANY_STATE);
CU_ASSERT_FATAL (read_condition > 0);
read_condition_unread = dds_create_readcondition (reader, DDS_ANY_INSTANCE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_READ_SAMPLE_STATE);
CU_ASSERT_FATAL (read_condition > 0);
}
static void delete_entities (void)
{
dds_return_t result;
result = dds_delete (participant);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
}
CU_Test (ddsc_loan, bad_params, .init = create_entities, .fini = delete_entities)
{
dds_return_t result;
/* buf = NULL */
result = dds_return_loan (reader, NULL, -1);
CU_ASSERT (result == DDS_RETCODE_BAD_PARAMETER);
result = dds_return_loan (reader, NULL, 0);
CU_ASSERT (result == DDS_RETCODE_BAD_PARAMETER);
result = dds_return_loan (reader, NULL, 1);
CU_ASSERT (result == DDS_RETCODE_BAD_PARAMETER);
/* buf[0] = NULL, size > 0 */
void *buf = NULL;
result = dds_return_loan (reader, &buf, 1);
CU_ASSERT (result == DDS_RETCODE_BAD_PARAMETER);
/* buf[0] != NULL, size <= 0 */
char dummy = 0;
buf = &dummy;
result = dds_return_loan (reader, &buf, 0);
CU_ASSERT (result == DDS_RETCODE_BAD_PARAMETER);
result = dds_return_loan (reader, &buf, -1);
CU_ASSERT (result == DDS_RETCODE_BAD_PARAMETER);
/* not a reader or condition (checking only the ones we have at hand) */
result = dds_return_loan (participant, &buf, 1);
CU_ASSERT (result == DDS_RETCODE_ILLEGAL_OPERATION);
result = dds_return_loan (topic, &buf, 1);
CU_ASSERT (result == DDS_RETCODE_ILLEGAL_OPERATION);
}
CU_Test (ddsc_loan, success, .init = create_entities, .fini = delete_entities)
{
const RoundTripModule_DataType s = {
.payload = {
._length = 1,
._buffer = (uint8_t[]) { 'a' }
}
};
const unsigned char zeros[3 * sizeof (s)] = { 0 };
dds_return_t result;
for (size_t i = 0; i < 3; i++)
{
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
}
/* rely on things like address sanitizer, valgrind for detecting double frees and leaks */
int32_t n;
void *ptrs[3] = { NULL };
void *ptr0copy, *ptr1copy;
dds_sample_info_t si[3];
/* read 1, return: this should cause memory to be allocated for 1 sample only */
n = dds_read (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] != NULL && ptrs[1] == NULL);
ptr0copy = ptrs[0];
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
/* return resets buf[0] (so that it picks up the loan the next time) and zeros the data */
CU_ASSERT_FATAL (ptrs[0] == NULL);
CU_ASSERT_FATAL (memcmp (ptr0copy, zeros, sizeof (s)) == 0);
/* read 3, return: should work fine, causes realloc */
n = dds_read (reader, ptrs, si, 3, 3);
CU_ASSERT_FATAL (n == 3);
CU_ASSERT_FATAL (ptrs[0] != NULL && ptrs[1] != NULL && ptrs[2] != NULL);
ptr0copy = ptrs[0];
ptr1copy = ptrs[1];
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
CU_ASSERT_FATAL (ptrs[0] == NULL);
CU_ASSERT_FATAL (memcmp (ptr0copy, zeros, 3 * sizeof (s)) == 0);
/* read 1 using loan, expecting to get the same address (no realloc needed), defer return.
Expect ptrs[1] to remain unchanged, although that probably is really an implementation
detail rather than something one might want to rely on */
n = dds_read (read_condition, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] == ptr0copy && ptrs[1] == ptr1copy);
/* read 3, letting read allocate */
int32_t n2;
void *ptrs2[3] = { NULL };
n2 = dds_read (read_condition, ptrs2, si, 3, 3);
CU_ASSERT_FATAL (n2 == 3);
CU_ASSERT_FATAL (ptrs2[0] != NULL && ptrs2[1] != NULL && ptrs2[2] != NULL);
CU_ASSERT_FATAL (ptrs2[0] != ptrs[0]);
/* contents of first sample should be the same; the point of comparing them
is that valgrind/address sanitizer will get angry with us if one of them
has been freed; can't use memcmp because the sequence buffers should be
at different addresses */
{
const struct RoundTripModule_DataType *a = ptrs[0];
const struct RoundTripModule_DataType *b = ptrs2[0];
CU_ASSERT_FATAL (a->payload._length == b->payload._length);
CU_ASSERT_FATAL (a->payload._buffer != b->payload._buffer);
CU_ASSERT_FATAL (a->payload._buffer[0] == b->payload._buffer[0]);
}
/* return loan -- to be freed when we delete the reader */
result = dds_return_loan (read_condition, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
CU_ASSERT_FATAL (ptrs[0] == NULL);
/* use "dds_return_loan" to free the second result immediately, there's no
easy way to check this happens short of using a custom sertopic */
ptr0copy = ptrs2[0];
result = dds_return_loan (read_condition, ptrs2, n2);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
CU_ASSERT_FATAL (ptrs2[0] == NULL);
//This should be a use-after-free
//CU_ASSERT_FATAL (memcmp (ptr0copy, zeros, sizeof (s)) == 0);
}
CU_Test (ddsc_loan, take_cleanup, .init = create_entities, .fini = delete_entities)
{
const RoundTripModule_DataType s = {
.payload = {
._length = 1,
._buffer = (uint8_t[]) { 'a' }
}
};
dds_return_t result;
/* rely on things like address sanitizer, valgrind for detecting double frees and leaks */
int32_t n;
void *ptrs[3] = { NULL };
void *ptr0copy;
dds_sample_info_t si[3];
/* take 1 from an empty reader: this should cause memory to be allocated for
1 sample only, be stored as the loan, but not become visisble to the
application */
n = dds_take (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 0);
CU_ASSERT_FATAL (ptrs[0] == NULL && ptrs[1] == NULL);
/* take 1 that's present: allocates a loan */
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_take (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] != NULL && ptrs[1] == NULL);
ptr0copy = ptrs[0];
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
/* if it really got handled as a loan, the same address must come out again
(rely on address sanitizer allocating at a different address each time) */
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_take (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] == ptr0copy && ptrs[1] == NULL);
ptr0copy = ptrs[0];
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
/* take that fails (for lack of data in this case) must reuse the loan, but
hand it back and restore the null pointer */
n = dds_take (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 0);
CU_ASSERT_FATAL (ptrs[0] == NULL && ptrs[1] == NULL);
/* take that succeeds again must therefore still be using the same address */
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_take (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] == ptr0copy && ptrs[1] == NULL);
/* take that fails (with the loan still out) must allocate new memory and
free it */
int32_t n2;
void *ptrs2[3] = { NULL };
n2 = dds_take (reader, ptrs2, si, 1, 1);
CU_ASSERT_FATAL (n2 == 0);
CU_ASSERT_FATAL (ptrs2[0] == NULL && ptrs2[1] == NULL);
/* return the loan and the next take should reuse the memory */
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_take (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] == ptr0copy && ptrs[1] == NULL);
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
}
CU_Test (ddsc_loan, read_cleanup, .init = create_entities, .fini = delete_entities)
{
const RoundTripModule_DataType s = {
.payload = {
._length = 1,
._buffer = (uint8_t[]) { 'a' }
}
};
dds_return_t result;
/* rely on things like address sanitizer, valgrind for detecting double frees and leaks */
int32_t n;
void *ptrs[3] = { NULL };
void *ptr0copy;
dds_sample_info_t si[3];
/* read 1 from an empty reader: this should cause memory to be allocated for
1 sample only, be stored as the loan, but not become visisble to the
application */
n = dds_read (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 0);
CU_ASSERT_FATAL (ptrs[0] == NULL && ptrs[1] == NULL);
/* read 1 that's present: allocates a loan */
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_take (reader, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] != NULL && ptrs[1] == NULL);
ptr0copy = ptrs[0];
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
/* if it really got handled as a loan, the same address must come out again
(rely on address sanitizer allocating at a different address each time) */
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_read (read_condition_unread, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] == ptr0copy && ptrs[1] == NULL);
ptr0copy = ptrs[0];
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
/* take that fails (for lack of data in this case) must reuse the loan, but
hand it back and restore the null pointer */
n = dds_read (read_condition_unread, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 0);
CU_ASSERT_FATAL (ptrs[0] == NULL && ptrs[1] == NULL);
/* take that succeeds again must therefore still be using the same address */
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_read (read_condition_unread, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] == ptr0copy && ptrs[1] == NULL);
/* take that fails (with the loan still out) must allocate new memory and
free it */
int32_t n2;
void *ptrs2[3] = { NULL };
n2 = dds_read (read_condition_unread, ptrs2, si, 1, 1);
CU_ASSERT_FATAL (n2 == 0);
CU_ASSERT_FATAL (ptrs2[0] == NULL && ptrs2[1] == NULL);
/* return the loan and the next take should reuse the memory */
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
result = dds_write (writer, &s);
CU_ASSERT_FATAL (result == 0);
n = dds_read (read_condition_unread, ptrs, si, 1, 1);
CU_ASSERT_FATAL (n == 1);
CU_ASSERT_FATAL (ptrs[0] == ptr0copy && ptrs[1] == NULL);
result = dds_return_loan (reader, ptrs, n);
CU_ASSERT_FATAL (result == DDS_RETCODE_OK);
}