Skip to content

Commit 3dfd463

Browse files
eisenhauerclaude
andcommitted
Add C API functions for safe string attribute retrieval
New functions that support a two-call pattern to retrieve string attribute data of unknown length (first call with NULL buffer to get length, then allocate and call again to get data): - adios2_attribute_string_data() - for single-value string attributes - adios2_attribute_string_data_array() - for string array attributes Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 0af0e9f commit 3dfd463

File tree

4 files changed

+227
-5
lines changed

4 files changed

+227
-5
lines changed

bindings/C/adios2/c/adios2_c_attribute.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,105 @@ adios2_error adios2_attribute_data(void *data, size_t *size, const adios2_attrib
192192
}
193193
}
194194

195+
adios2_error adios2_attribute_string_data(const adios2_attribute *attribute, char *data,
196+
size_t *length)
197+
{
198+
try
199+
{
200+
adios2::helper::CheckForNullptr(attribute,
201+
"for attribute, in call to adios2_attribute_string_data");
202+
adios2::helper::CheckForNullptr(
203+
length, "for size_t *length, in call to adios2_attribute_string_data");
204+
205+
const adios2::core::AttributeBase *attributeBase =
206+
reinterpret_cast<const adios2::core::AttributeBase *>(attribute);
207+
208+
if (attributeBase->m_Type != adios2::helper::GetDataType<std::string>())
209+
{
210+
throw std::invalid_argument("ERROR: attribute " + attributeBase->m_Name +
211+
" is not of string type, in call to "
212+
"adios2_attribute_string_data\n");
213+
}
214+
215+
if (!attributeBase->m_IsSingleValue)
216+
{
217+
throw std::invalid_argument("ERROR: attribute " + attributeBase->m_Name +
218+
" is not a single value, use "
219+
"adios2_attribute_string_data_array instead, in call to "
220+
"adios2_attribute_string_data\n");
221+
}
222+
223+
const adios2::core::Attribute<std::string> *attributeCpp =
224+
dynamic_cast<const adios2::core::Attribute<std::string> *>(attributeBase);
225+
226+
*length = attributeCpp->m_DataSingleValue.size();
227+
228+
if (data != nullptr)
229+
{
230+
attributeCpp->m_DataSingleValue.copy(data, *length);
231+
}
232+
233+
return adios2_error_none;
234+
}
235+
catch (...)
236+
{
237+
return static_cast<adios2_error>(
238+
adios2::helper::ExceptionToError("adios2_attribute_string_data"));
239+
}
240+
}
241+
242+
adios2_error adios2_attribute_string_data_array(const adios2_attribute *attribute, char **data,
243+
size_t *lengths)
244+
{
245+
try
246+
{
247+
adios2::helper::CheckForNullptr(
248+
attribute, "for attribute, in call to adios2_attribute_string_data_array");
249+
adios2::helper::CheckForNullptr(
250+
lengths, "for size_t *lengths, in call to adios2_attribute_string_data_array");
251+
252+
const adios2::core::AttributeBase *attributeBase =
253+
reinterpret_cast<const adios2::core::AttributeBase *>(attribute);
254+
255+
if (attributeBase->m_Type != adios2::helper::GetDataType<std::string>())
256+
{
257+
throw std::invalid_argument("ERROR: attribute " + attributeBase->m_Name +
258+
" is not of string type, in call to "
259+
"adios2_attribute_string_data_array\n");
260+
}
261+
262+
if (attributeBase->m_IsSingleValue)
263+
{
264+
throw std::invalid_argument("ERROR: attribute " + attributeBase->m_Name +
265+
" is a single value, use "
266+
"adios2_attribute_string_data instead, in call to "
267+
"adios2_attribute_string_data_array\n");
268+
}
269+
270+
const adios2::core::Attribute<std::string> *attributeCpp =
271+
dynamic_cast<const adios2::core::Attribute<std::string> *>(attributeBase);
272+
273+
const size_t count = attributeCpp->m_Elements;
274+
275+
for (size_t i = 0; i < count; ++i)
276+
{
277+
lengths[i] = attributeCpp->m_DataArray[i].size();
278+
279+
if (data != nullptr)
280+
{
281+
attributeCpp->m_DataArray[i].copy(data[i], lengths[i]);
282+
}
283+
}
284+
285+
return adios2_error_none;
286+
}
287+
catch (...)
288+
{
289+
return static_cast<adios2_error>(
290+
adios2::helper::ExceptionToError("adios2_attribute_string_data_array"));
291+
}
292+
}
293+
195294
#ifdef __cplusplus
196295
} // end extern C
197296
#endif

bindings/C/adios2/c/adios2_c_attribute.h

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,74 @@ adios2_error adios2_attribute_size(size_t *size, const adios2_attribute *attribu
7878
*/
7979
adios2_error adios2_attribute_data(void *data, size_t *size, const adios2_attribute *attribute);
8080

81+
/**
82+
* Retrieve a single-value string attribute's data.
83+
*
84+
* This function supports a two-call pattern to safely retrieve string
85+
* attributes of unknown length:
86+
* 1. Call with data=NULL to get the string length
87+
* 2. Allocate a buffer of at least (length + 1) bytes
88+
* 3. Call again with the buffer to retrieve the string data
89+
*
90+
* The returned length does NOT include a null terminator. The caller
91+
* is responsible for null-terminating the string if needed.
92+
*
93+
* @param attribute handler for a single-value string attribute
94+
* @param data pre-allocated buffer for string data, or NULL to only query length
95+
* @param length output: the string length in bytes (excludes null terminator)
96+
* @return adios2_error 0: success, see enum adios2_error for errors
97+
*
98+
* Example usage:
99+
* @code
100+
* size_t length;
101+
* adios2_attribute_string_data(attr, NULL, &length); // get length
102+
* char *str = (char *)malloc(length + 1);
103+
* adios2_attribute_string_data(attr, str, &length); // get data
104+
* str[length] = '\0'; // null terminate
105+
* @endcode
106+
*/
107+
adios2_error adios2_attribute_string_data(const adios2_attribute *attribute, char *data,
108+
size_t *length);
109+
110+
/**
111+
* Retrieve string data from a string array attribute.
112+
*
113+
* This function supports a two-call pattern to safely retrieve string
114+
* arrays of unknown lengths:
115+
* 1. Call adios2_attribute_size() to get the number of strings
116+
* 2. Allocate a lengths array of that size
117+
* 3. Call with data=NULL to get the length of each string
118+
* 4. Allocate each string buffer based on the lengths
119+
* 5. Call again with the allocated buffers to retrieve the string data
120+
*
121+
* The returned lengths do NOT include null terminators. The caller
122+
* is responsible for null-terminating each string if needed.
123+
*
124+
* @param attribute handler for a string array attribute
125+
* @param data array of pre-allocated buffers (char**), or NULL to only query lengths
126+
* @param lengths output: array of string lengths in bytes (excludes null terminators).
127+
* Must be pre-allocated with size from adios2_attribute_size().
128+
* @return adios2_error 0: success, see enum adios2_error for errors
129+
*
130+
* Example usage:
131+
* @code
132+
* size_t count;
133+
* adios2_attribute_size(&count, attr); // get array size
134+
* size_t *lengths = (size_t *)malloc(count * sizeof(size_t));
135+
* adios2_attribute_string_data_array(attr, NULL, lengths); // get lengths
136+
* char **strings = (char **)malloc(count * sizeof(char*));
137+
* for (size_t i = 0; i < count; i++) {
138+
* strings[i] = (char *)malloc(lengths[i] + 1);
139+
* }
140+
* adios2_attribute_string_data_array(attr, strings, lengths); // get data
141+
* for (size_t i = 0; i < count; i++) {
142+
* strings[i][lengths[i]] = '\0'; // null terminate
143+
* }
144+
* @endcode
145+
*/
146+
adios2_error adios2_attribute_string_data_array(const adios2_attribute *attribute, char **data,
147+
size_t *lengths);
148+
81149
#ifdef __cplusplus
82150
} // end extern C
83151
#endif

docs/user_guide/source/introduction/whatsnew.rst

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ What's new in master?
55
C API Improvements
66
------------------
77

8-
New ``adios2_get_string()`` function for safely retrieving string variables
9-
of unknown length. This function supports a two-call pattern: first call with
10-
a NULL buffer to query the string length, then call again with an allocated
11-
buffer to retrieve the data. This addresses a gap in the C API where string
12-
variable lengths could not be queried before reading.
8+
New functions for safely retrieving string data of unknown length using a
9+
two-call pattern (first call with NULL buffer to query length, then call
10+
again with allocated buffer to retrieve data):
11+
12+
- ``adios2_get_string()`` - for string variables
13+
- ``adios2_attribute_string_data()`` - for single-value string attributes
14+
- ``adios2_attribute_string_data_array()`` - for string array attributes
15+
16+
This addresses a gap in the C API where string lengths could not be queried
17+
before reading.
1318

1419

1520
===================

testing/adios2/bindings/C/TestBPWriteTypes.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,56 @@ TEST_F(ADIOS2_C_API, ADIOS2BPWriteTypes)
264264
EXPECT_EQ(dataVector[3], "fourth");
265265
EXPECT_EQ(elements, 4);
266266

267+
// Test adios2_attribute_string_data with the two-call pattern (single value)
268+
size_t attrStrLength = 0;
269+
adios2_error errAttrStr = adios2_attribute_string_data(attrSingle, NULL, &attrStrLength);
270+
EXPECT_EQ(errAttrStr, adios2_error_none);
271+
EXPECT_EQ(attrStrLength, strlen("Hello Attribute"));
272+
273+
char *attrStrData = new char[attrStrLength + 1];
274+
errAttrStr = adios2_attribute_string_data(attrSingle, attrStrData, &attrStrLength);
275+
EXPECT_EQ(errAttrStr, adios2_error_none);
276+
attrStrData[attrStrLength] = '\0';
277+
EXPECT_EQ(std::string(attrStrData), "Hello Attribute");
278+
delete[] attrStrData;
279+
280+
// Test adios2_attribute_string_data_array with the two-call pattern (array)
281+
size_t attrArrayCount = 0;
282+
adios2_attribute_size(&attrArrayCount, attrArray);
283+
EXPECT_EQ(attrArrayCount, 4u);
284+
285+
size_t *attrArrayLengths = new size_t[attrArrayCount];
286+
adios2_error errAttrArr =
287+
adios2_attribute_string_data_array(attrArray, NULL, attrArrayLengths);
288+
EXPECT_EQ(errAttrArr, adios2_error_none);
289+
EXPECT_EQ(attrArrayLengths[0], strlen("first"));
290+
EXPECT_EQ(attrArrayLengths[1], strlen("second"));
291+
EXPECT_EQ(attrArrayLengths[2], strlen("third"));
292+
EXPECT_EQ(attrArrayLengths[3], strlen("fourth"));
293+
294+
char **attrArrayData = new char *[attrArrayCount];
295+
for (size_t i = 0; i < attrArrayCount; ++i)
296+
{
297+
attrArrayData[i] = new char[attrArrayLengths[i] + 1];
298+
}
299+
errAttrArr = adios2_attribute_string_data_array(attrArray, attrArrayData, attrArrayLengths);
300+
EXPECT_EQ(errAttrArr, adios2_error_none);
301+
for (size_t i = 0; i < attrArrayCount; ++i)
302+
{
303+
attrArrayData[i][attrArrayLengths[i]] = '\0';
304+
}
305+
EXPECT_EQ(std::string(attrArrayData[0]), "first");
306+
EXPECT_EQ(std::string(attrArrayData[1]), "second");
307+
EXPECT_EQ(std::string(attrArrayData[2]), "third");
308+
EXPECT_EQ(std::string(attrArrayData[3]), "fourth");
309+
310+
for (size_t i = 0; i < attrArrayCount; ++i)
311+
{
312+
delete[] attrArrayData[i];
313+
}
314+
delete[] attrArrayData;
315+
delete[] attrArrayLengths;
316+
267317
// compare min and max
268318
auto mmI8 = std::minmax_element(&data_I8[0], &data_I8[data_Nx]);
269319
auto mmI16 = std::minmax_element(&data_I16[0], &data_I16[data_Nx]);

0 commit comments

Comments
 (0)