Skip to content

Commit d8bfd02

Browse files
authored
Fix vsnprint leading zero and %X formatting (#34)
Related To Issue: 33 What's Wrong: * The custom core::vsnprint parser did not handle leading zero flags or width limits. As a result, formatting strings like "%02X" failed, resulting in malformed output (such as "?2X:?2X:..." on boot for the Ethernet MAC address). * The custom parser only supported lowercase hexadecimal ("%x") and always prepended "0x", which prevented printing uppercase raw hex values without prefixes. How Was it Fixed (if not obvious): * Enhanced print_number and print_signed_number in vsnprint.cpp to support width, space/zero padding, uppercase hex, and correct signs. * Updated vsnprint format parser to parse leading zero flags and widths prior to length modifiers. * Added support for "%X" specifier (uppercase hex without prepending "0x"). * Adjusted width calculations for "%x" and "%b" to account for their prepended prefixes. * Added Catch2 unit tests in catch2-vsnprint.cpp to verify formatting. * AI generated the code modifications and unit tests, and a human reviewed the changes, built target presets, and made this commit. What side effects does this have (could be none): * None. Existing calls to %x and other formatting specifiers preserve their exact original behavior. Which builds did you run to make sure they build? [x] arm-none-eabi-gcc Cortex M4 [x] arm-none-eabi-gcc Cortex M7 [x] (Apple) Native Clang [ ] (Apple) Homebrew GCC [x] (Apple) Homebrew LLVM How Do We Know and Can Show It's Fixed: * Verified by 100% passing results of the new vsnprint Catch2 tests in the test-core-none-all host suite. Which Unittest Series did you Check? [x] (Apple) Native Clang [ ] (Apple) Homebrew GCC [x] (Apple) Homebrew LLVM Did this affect any on-target builds? If so which were tested? [ ] STM32F407VE board [ ] STM32H753ZI board
1 parent e335d1b commit d8bfd02

4 files changed

Lines changed: 197 additions & 99 deletions

File tree

applications/nucleo-demo/source/Demo.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ void Demo::OnEntry(DemoState state) {
6767
}
6868

6969
DemoState Demo::OnCycle(DemoState state) {
70-
jarnax::print("Demo::OnCycle: %u\r\n", static_cast<std::uint8_t>(state));
70+
// jarnax::print("Demo::OnCycle: %u\r\n", static_cast<std::uint8_t>(state));
7171
if (state == DemoState::StartUp) {
7272
state = DemoState::KeyLoop;
7373
} else if (state == DemoState::KeyLoop) {

modules/core/source/vsnprint.cpp

Lines changed: 117 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ static U clamp_to_range(T value, U min, U max) {
3838
/// @param base The base to parse the number in
3939
/// @return The index at the exclusive end of the number
4040
template <typename T>
41-
static unsigned long print_number(unsigned long start, char buffer[], T num, unsigned int base) {
41+
static unsigned long print_number(unsigned long start, char buffer[], T num, unsigned int base, unsigned int width = 0, bool leading_zero = false, bool uppercase = false, bool is_negative = false) {
4242
// the digits cover base 2, base 8 (don't use) base 10, base 12, base 16 and base 64
4343
static constexpr char const *const digits =
4444
"0123456789abcdef"
@@ -49,72 +49,72 @@ static unsigned long print_number(unsigned long start, char buffer[], T num, uns
4949
unsigned long offset = 0;
5050
char backwards_digits[digit_places_limit]; // hold the value temporarily
5151

52-
if (base == base2) {
53-
if (num < 0b10) {
54-
buffer[start] = digits[num];
55-
return start + 1;
56-
} else {
52+
if (num == 0) {
53+
backwards_digits[offset++] = '0';
54+
} else {
55+
if (base == base2) {
5756
while (num != 0) {
58-
backwards_digits[offset] = digits[num & 1];
59-
offset++;
57+
backwards_digits[offset++] = digits[num & 1];
6058
num >>= 1;
6159
}
62-
}
63-
}
64-
if (base == base8) {
65-
if (num < 8) {
66-
buffer[start] = digits[num];
67-
return start + 1;
68-
} else {
60+
} else if (base == base8) {
6961
while (num != 0) {
70-
backwards_digits[offset] = digits[num & 7];
71-
offset++;
62+
backwards_digits[offset++] = digits[num & 7];
7263
num >>= 3;
7364
}
74-
}
75-
}
76-
if (base == base10) {
77-
if (num < 10) {
78-
buffer[start] = digits[num];
79-
return start + 1;
80-
} else {
65+
} else if (base == base10) {
8166
while (num != 0) {
82-
backwards_digits[offset] = digits[num % 10];
83-
offset++;
67+
backwards_digits[offset++] = digits[num % 10];
8468
num /= 10;
8569
}
86-
}
87-
}
88-
if (base == base16) {
89-
if (num < 0x10) {
90-
buffer[start] = digits[num];
91-
return start + 1;
92-
} else {
70+
} else if (base == base16) {
9371
while (num != 0) {
94-
backwards_digits[offset] = digits[num & 0xF];
95-
offset++;
72+
char d = digits[num & 0xF];
73+
if (uppercase && d >= 'a' && d <= 'z') {
74+
d = d - 'a' + 'A';
75+
}
76+
backwards_digits[offset++] = d;
9677
num >>= 4;
9778
}
98-
}
99-
}
100-
if (base == base64) {
101-
if (num < 0x40) {
102-
buffer[start] = digits[num];
103-
return start + 1;
104-
} else {
79+
} else if (base == base64) {
10580
while (num != 0) {
106-
backwards_digits[offset] = digits[num & 0x3F];
107-
offset++;
81+
backwards_digits[offset++] = digits[num & 0x3F];
10882
num >>= 6;
10983
}
11084
}
11185
}
112-
if (offset > 0) {
113-
for (unsigned int i = 0; i < offset; i++) {
114-
buffer[start + i] = backwards_digits[offset - i - 1];
86+
87+
// Determine how many characters we will print before padding
88+
unsigned int total_chars = static_cast<unsigned int>(offset) + (is_negative ? 1U : 0U);
89+
unsigned long current = start;
90+
91+
// Space padding (leading_zero = false) goes before the sign
92+
if (!leading_zero && width > total_chars) {
93+
unsigned int padding = width - total_chars;
94+
for (unsigned int i = 0; i < padding; i++) {
95+
buffer[current++] = ' ';
96+
}
97+
}
98+
99+
// Print the sign if negative
100+
if (is_negative) {
101+
buffer[current++] = '-';
102+
}
103+
104+
// Zero padding (leading_zero = true) goes after the sign
105+
if (leading_zero && width > total_chars) {
106+
unsigned int padding = width - total_chars;
107+
for (unsigned int i = 0; i < padding; i++) {
108+
buffer[current++] = '0';
115109
}
116110
}
117-
return start + offset;
111+
112+
// Print the digits in correct order (reverse of backwards_digits)
113+
for (unsigned int i = 0; i < offset; i++) {
114+
buffer[current++] = backwards_digits[offset - i - 1];
115+
}
116+
117+
return current;
118118
}
119119

120120
/// @brief Parses the number and prints it in the specified base in the buffer
@@ -125,12 +125,13 @@ static unsigned long print_number(unsigned long start, char buffer[], T num, uns
125125
/// @param base The base to parse the number in
126126
/// @return The index at the exclusive end of the number
127127
template <typename T>
128-
static unsigned long print_signed_number(unsigned long start, char buffer[], T num, unsigned int base) {
128+
static unsigned long print_signed_number(unsigned long start, char buffer[], T num, unsigned int base, unsigned int width = 0, bool leading_zero = false) {
129129
if (num < 0) {
130-
buffer[start] = '-';
131-
return print_number(start + 1, buffer, -num, base);
130+
// Safe absolute value conversion using uint64_t to avoid signed integer overflow on INT_MIN
131+
uint64_t abs_val = static_cast<uint64_t>(-static_cast<int64_t>(num));
132+
return print_number(start, buffer, abs_val, base, width, leading_zero, false, true);
132133
} else {
133-
return print_number(start, buffer, num, base);
134+
return print_number(start, buffer, static_cast<uint64_t>(num), base, width, leading_zero, false, false);
134135
}
135136
}
136137

@@ -145,6 +146,25 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va
145146
if (*format == '%') {
146147
format++;
147148

149+
// Handle leading zero modifier
150+
bool leading_zero = false;
151+
if (*format == '0') {
152+
leading_zero = true;
153+
format++;
154+
}
155+
156+
// Handle width modifier
157+
unsigned int width = 0;
158+
if (is_digit(*format)) {
159+
while (is_digit(*format)) {
160+
width = width * 10U + static_cast<unsigned int>(*format - '0');
161+
format++;
162+
}
163+
if (width > digit_places_limit) {
164+
width = digit_places_limit;
165+
}
166+
}
167+
148168
// Handle %l modifiers
149169
bool longlong_modifier = false;
150170
bool long_modifier = false;
@@ -177,27 +197,6 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va
177197
format++;
178198
}
179199

180-
// Handle leading zero modifier
181-
// bool leading_zero = false;
182-
// if (*format == '0') {
183-
// leading_zero = true;
184-
// format++;
185-
// }
186-
// bool specific_digits = false;
187-
// if (is_digit(*format)) {
188-
// specific_digits = true;
189-
// unsigned int digits = 0;
190-
// while (is_digit(*format)) {
191-
// digits = digits * 10 + (*format - '0');
192-
// format++;
193-
// }
194-
// if (digits > digit_places_limit) {
195-
// digits = digit_places_limit;
196-
// }
197-
// }
198-
// (void)leading_zero; // @TODO unused
199-
// (void)specific_digits; // @TODO unused
200-
201200
switch (*format) {
202201
case 's': {
203202
const char *str = va_arg(args, const char *);
@@ -212,78 +211,99 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va
212211
if (longlong_modifier) {
213212
int64_t num = va_arg(args, int64_t);
214213
num = clamp_to_range(num, std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max());
215-
index = print_signed_number(index, buffer, num, base10);
214+
index = print_signed_number(index, buffer, num, base10, width, leading_zero);
216215
} else if (long_modifier) {
217216
int32_t num = va_arg(args, int32_t);
218217
num = clamp_to_range(num, std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max());
219-
index = print_signed_number(index, buffer, num, base10);
218+
index = print_signed_number(index, buffer, num, base10, width, leading_zero);
220219
} else if (half_modifier) {
221220
int value = static_cast<int16_t>(va_arg(args, int));
222221
int16_t num = clamp_to_range(value, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
223-
index = print_signed_number(index, buffer, num, base10);
222+
index = print_signed_number(index, buffer, num, base10, width, leading_zero);
224223
} else if (halfhalf_modifier) {
225224
int value = va_arg(args, int);
226225
int8_t num = clamp_to_range(value, std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
227-
index = print_signed_number(index, buffer, num, base10);
228-
// } else if (size_modifier) {
229-
// ssize_t value = va_arg(args, ssize_t);
230-
// num = clamp_to_range(value, std::numeric_limits<ssize_t>::min(), std::numeric_limits<ssize_t>::max());
231-
// index = print_signed_number(index, buffer, num, base10);
226+
index = print_signed_number(index, buffer, num, base10, width, leading_zero);
232227
} else {
233228
auto num = va_arg(args, int);
234-
index = print_signed_number(index, buffer, num, base10);
229+
index = print_signed_number(index, buffer, num, base10, width, leading_zero);
235230
}
236231
break;
237232
}
238233
case 'u': {
239234
if (longlong_modifier) {
240235
uint64_t num = va_arg(args, uint64_t);
241236
num = clamp_to_range(num, std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
242-
index = print_number(index, buffer, num, base10);
237+
index = print_number(index, buffer, num, base10, width, leading_zero);
243238
} else if (long_modifier) {
244239
uint32_t num = va_arg(args, uint32_t);
245240
num = clamp_to_range(num, std::numeric_limits<uint32_t>::min(), std::numeric_limits<uint32_t>::max());
246-
index = print_number(index, buffer, num, base10);
241+
index = print_number(index, buffer, num, base10, width, leading_zero);
247242
} else if (half_modifier) {
248243
unsigned int value = va_arg(args, unsigned int);
249244
uint16_t num = clamp_to_range(value, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max());
250-
index = print_number(index, buffer, num, base10);
245+
index = print_number(index, buffer, num, base10, width, leading_zero);
251246
} else if (halfhalf_modifier) {
252247
unsigned int value = va_arg(args, unsigned int);
253248
uint8_t num = clamp_to_range(value, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max());
254-
index = print_number(index, buffer, num, base10);
249+
index = print_number(index, buffer, num, base10, width, leading_zero);
255250
} else if (size_modifier) {
256251
size_t num = va_arg(args, size_t);
257-
index = print_number(index, buffer, num, base10);
252+
index = print_number(index, buffer, num, base10, width, leading_zero);
258253
} else {
259254
unsigned int num = va_arg(args, unsigned int);
260-
index = print_number(index, buffer, num, base10);
255+
index = print_number(index, buffer, num, base10, width, leading_zero);
261256
}
262257
break;
263258
}
264259
case 'x': {
265260
buffer[index++] = '0';
266261
buffer[index++] = 'x';
262+
unsigned int sub_width = (width > 2) ? (width - 2) : 0;
267263
if (longlong_modifier) {
268264
uint64_t num = va_arg(args, uint64_t);
269-
index = print_number(index, buffer, num, base16);
265+
index = print_number(index, buffer, num, base16, sub_width, leading_zero, false);
270266
} else if (long_modifier) {
271-
uint32_t num = va_arg(args, uint32_t); // 32 bits
272-
index = print_number(index, buffer, num, base16);
267+
uint32_t num = va_arg(args, uint32_t);
268+
index = print_number(index, buffer, num, base16, sub_width, leading_zero, false);
273269
} else if (half_modifier) {
274270
unsigned int value = va_arg(args, unsigned int);
275271
uint16_t num = clamp_to_range(value, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max());
276-
index = print_number(index, buffer, num, base16);
272+
index = print_number(index, buffer, num, base16, sub_width, leading_zero, false);
277273
} else if (halfhalf_modifier) {
278274
unsigned int value = va_arg(args, unsigned int);
279275
uint8_t num = clamp_to_range(value, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max());
280-
index = print_number(index, buffer, num, base16);
276+
index = print_number(index, buffer, num, base16, sub_width, leading_zero, false);
281277
} else if (size_modifier) {
282278
size_t num = va_arg(args, size_t);
283-
index = print_number(index, buffer, num, base16);
279+
index = print_number(index, buffer, num, base16, sub_width, leading_zero, false);
284280
} else {
285281
uint32_t num = va_arg(args, unsigned int);
286-
index = print_number(index, buffer, num, base16);
282+
index = print_number(index, buffer, num, base16, sub_width, leading_zero, false);
283+
}
284+
break;
285+
}
286+
case 'X': {
287+
if (longlong_modifier) {
288+
uint64_t num = va_arg(args, uint64_t);
289+
index = print_number(index, buffer, num, base16, width, leading_zero, true);
290+
} else if (long_modifier) {
291+
uint32_t num = va_arg(args, uint32_t);
292+
index = print_number(index, buffer, num, base16, width, leading_zero, true);
293+
} else if (half_modifier) {
294+
unsigned int value = va_arg(args, unsigned int);
295+
uint16_t num = clamp_to_range(value, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max());
296+
index = print_number(index, buffer, num, base16, width, leading_zero, true);
297+
} else if (halfhalf_modifier) {
298+
unsigned int value = va_arg(args, unsigned int);
299+
uint8_t num = clamp_to_range(value, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max());
300+
index = print_number(index, buffer, num, base16, width, leading_zero, true);
301+
} else if (size_modifier) {
302+
size_t num = va_arg(args, size_t);
303+
index = print_number(index, buffer, num, base16, width, leading_zero, true);
304+
} else {
305+
uint32_t num = va_arg(args, unsigned int);
306+
index = print_number(index, buffer, num, base16, width, leading_zero, true);
287307
}
288308
break;
289309
}
@@ -321,7 +341,8 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va
321341
}
322342
buffer[index++] = '0';
323343
buffer[index++] = 'b';
324-
index = print_number(index, buffer, num, base2);
344+
unsigned int sub_width = (width > 2) ? (width - 2) : 0;
345+
index = print_number(index, buffer, num, base2, sub_width, leading_zero, false);
325346
break;
326347
}
327348
default:
@@ -336,8 +357,6 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va
336357
format++;
337358
}
338359

339-
// FIXME determine if we've run over.
340-
341360
// null terminate the string
342361
if (index >= buffer_size) {
343362
index = buffer_size - 1;

modules/core/tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ host_unit_test(NAME core
2323
${CMAKE_CURRENT_SOURCE_DIR}/catch2-tokenizer.cpp
2424
# ${CMAKE_CURRENT_SOURCE_DIR}/catch2-units.cpp # requires information from the Configuration and Architecture
2525
${CMAKE_CURRENT_SOURCE_DIR}/catch2-variants.cpp
26+
${CMAKE_CURRENT_SOURCE_DIR}/catch2-vsnprint.cpp
2627
${CMAKE_CURRENT_SOURCE_DIR}/source/conversions.cpp
2728
${_GENERATED_CORE_TESTS}
2829
LIBRARIES

0 commit comments

Comments
 (0)