blob: f84417c6fa811d4446c30ebd67609696ef27b894 [file]
// Copyright 2026 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cfloat>
#include <cinttypes>
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <string>
#include "iree/base/api.h"
#include "iree/testing/gtest.h"
namespace {
// Helper: format with iree_vsnprintf and return as std::string.
// Uses va_list to avoid -Wformat-security on non-literal format strings.
static std::string FormatV(const char* format, ...) {
va_list args1, args2;
va_start(args1, format);
va_copy(args2, args1);
// First call to measure size.
int length = iree_vsnprintf(nullptr, 0, format, args1);
va_end(args1);
EXPECT_GE(length, 0) << "iree_vsnprintf returned error for: " << format;
if (length < 0) {
va_end(args2);
return "<error>";
}
std::string result(length, '\0');
iree_vsnprintf(result.data(), length + 1, format, args2);
va_end(args2);
return result;
}
// Convenience wrapper for test readability.
#define Format(...) FormatV(__VA_ARGS__)
// Helper: format with iree_vsnprintf into a small buffer and verify truncation.
static std::string FormatTruncatedV(size_t buffer_size, const char* format,
...) {
std::string buffer(buffer_size, 'X');
va_list args;
va_start(args, format);
iree_vsnprintf(buffer.data(), buffer_size, format, args);
va_end(args);
// Return only what was written (up to NUL).
return std::string(buffer.c_str());
}
#define FormatTruncated(buffer_size, ...) \
FormatTruncatedV(buffer_size, __VA_ARGS__)
// Helper: format with iree_vfctprintf via callback and return as std::string.
static void format_callback_append(char c, void* user_data) {
static_cast<std::string*>(user_data)->push_back(c);
}
static std::string FormatCallbackV(const char* format, ...) {
std::string result;
va_list args;
va_start(args, format);
int length = iree_vfctprintf(format_callback_append, &result, format, args);
va_end(args);
EXPECT_EQ(length, static_cast<int>(result.size()));
return result;
}
#define FormatCallback(...) FormatCallbackV(__VA_ARGS__)
// Helper: compare our output against libc snprintf for differential testing.
static void ExpectMatchesLibcV(const char* format, ...) {
char libc_buffer[1024] = {};
char iree_buffer[1024] = {};
va_list args1, args2;
va_start(args1, format);
va_copy(args2, args1);
int libc_length = vsnprintf(libc_buffer, sizeof(libc_buffer), format, args1);
va_end(args1);
iree_vsnprintf(iree_buffer, sizeof(iree_buffer), format, args2);
va_end(args2);
EXPECT_STREQ(iree_buffer, libc_buffer)
<< "Format: \"" << format << "\" (libc_length=" << libc_length << ")";
}
#define ExpectMatchesLibc(...) ExpectMatchesLibcV(__VA_ARGS__)
//===----------------------------------------------------------------------===//
// Literal and escape tests
//===----------------------------------------------------------------------===//
TEST(Printf, LiteralText) {
EXPECT_EQ(Format("hello"), "hello");
EXPECT_EQ(Format(""), "");
EXPECT_EQ(Format("a"), "a");
}
TEST(Printf, PercentEscape) {
EXPECT_EQ(Format("%%"), "%");
EXPECT_EQ(Format("100%%"), "100%");
EXPECT_EQ(Format("%%d"), "%d");
}
//===----------------------------------------------------------------------===//
// Integer formatting: %d, %i
//===----------------------------------------------------------------------===//
TEST(Printf, SignedDecimalBasic) {
EXPECT_EQ(Format("%d", 0), "0");
EXPECT_EQ(Format("%d", 1), "1");
EXPECT_EQ(Format("%d", -1), "-1");
EXPECT_EQ(Format("%d", 42), "42");
EXPECT_EQ(Format("%d", -42), "-42");
EXPECT_EQ(Format("%i", 42), "42");
}
TEST(Printf, SignedDecimalBoundaries) {
EXPECT_EQ(Format("%d", INT_MAX), std::to_string(INT_MAX));
EXPECT_EQ(Format("%d", INT_MIN), std::to_string(INT_MIN));
}
TEST(Printf, SignedDecimal64Bit) {
EXPECT_EQ(Format("%lld", (long long)INT64_MAX), std::to_string(INT64_MAX));
EXPECT_EQ(Format("%lld", (long long)INT64_MIN), std::to_string(INT64_MIN));
}
TEST(Printf, SignedDecimalFlags) {
// '+' flag: show sign for positive.
EXPECT_EQ(Format("%+d", 42), "+42");
EXPECT_EQ(Format("%+d", -42), "-42");
EXPECT_EQ(Format("%+d", 0), "+0");
// ' ' flag: space before positive.
EXPECT_EQ(Format("% d", 42), " 42");
EXPECT_EQ(Format("% d", -42), "-42");
// '+' overrides ' '.
EXPECT_EQ(Format("%+ d", 42), "+42");
}
TEST(Printf, SignedDecimalWidth) {
EXPECT_EQ(Format("%5d", 42), " 42");
EXPECT_EQ(Format("%5d", -42), " -42");
EXPECT_EQ(Format("%-5d", 42), "42 ");
EXPECT_EQ(Format("%05d", 42), "00042");
EXPECT_EQ(Format("%05d", -42), "-0042");
}
TEST(Printf, SignedDecimalPrecision) {
EXPECT_EQ(Format("%.5d", 42), "00042");
EXPECT_EQ(Format("%.5d", -42), "-00042");
EXPECT_EQ(Format("%.0d", 0), "");
EXPECT_EQ(Format("%.0d", 1), "1");
// Precision overrides zero-pad flag.
EXPECT_EQ(Format("%08.5d", 42), " 00042");
}
TEST(Printf, SignedDecimalDynamicWidth) {
EXPECT_EQ(Format("%*d", 5, 42), " 42");
EXPECT_EQ(Format("%*d", -5, 42), "42 "); // Negative = left-justify.
}
TEST(Printf, SignedDecimalDynamicPrecision) {
EXPECT_EQ(Format("%.*d", 5, 42), "00042");
EXPECT_EQ(Format("%.*d", -1, 42), "42"); // Negative = omit precision.
}
//===----------------------------------------------------------------------===//
// Integer formatting: %u, %x, %X, %o
//===----------------------------------------------------------------------===//
TEST(Printf, UnsignedDecimal) {
EXPECT_EQ(Format("%u", 0u), "0");
EXPECT_EQ(Format("%u", 42u), "42");
EXPECT_EQ(Format("%u", UINT_MAX), std::to_string(UINT_MAX));
EXPECT_EQ(Format("%llu", (unsigned long long)UINT64_MAX),
std::to_string(UINT64_MAX));
}
TEST(Printf, Hex) {
EXPECT_EQ(Format("%x", 0u), "0");
EXPECT_EQ(Format("%x", 255u), "ff");
EXPECT_EQ(Format("%X", 255u), "FF");
EXPECT_EQ(Format("%08x", 255u), "000000ff");
EXPECT_EQ(Format("%08X", 255u), "000000FF");
EXPECT_EQ(Format("%#x", 255u), "0xff");
EXPECT_EQ(Format("%#X", 255u), "0XFF");
EXPECT_EQ(Format("%#x", 0u), "0"); // No prefix for zero.
}
TEST(Printf, Octal) {
EXPECT_EQ(Format("%o", 0u), "0");
EXPECT_EQ(Format("%o", 8u), "10");
EXPECT_EQ(Format("%#o", 8u), "010");
EXPECT_EQ(Format("%#o", 0u), "0");
// C99 §7.19.6.1p6: "#" with precision 0 and value 0 must still print "0".
EXPECT_EQ(Format("%#.0o", 0u), "0");
EXPECT_EQ(Format("%.0o", 0u),
""); // Without #: empty for precision 0, value 0.
EXPECT_EQ(Format("%#.5o", 8u), "00010");
EXPECT_EQ(Format("%#10.0o", 0u), " 0");
}
//===----------------------------------------------------------------------===//
// Length modifiers
//===----------------------------------------------------------------------===//
TEST(Printf, LengthHH) {
EXPECT_EQ(Format("%hhd", (int)(signed char)-1), "-1");
EXPECT_EQ(Format("%hhu", (unsigned int)(unsigned char)255), "255");
// Truncation: 256 wraps to 0 for unsigned char.
EXPECT_EQ(Format("%hhu", (unsigned int)256), "0");
}
TEST(Printf, LengthH) {
EXPECT_EQ(Format("%hd", (int)(short)-1), "-1");
EXPECT_EQ(Format("%hu", (unsigned int)(unsigned short)65535), "65535");
}
TEST(Printf, LengthL) {
EXPECT_EQ(Format("%ld", (long)42), "42");
EXPECT_EQ(Format("%lu", (unsigned long)42), "42");
}
TEST(Printf, LengthLL) {
EXPECT_EQ(Format("%lld", (long long)42), "42");
EXPECT_EQ(Format("%llu", (unsigned long long)42), "42");
EXPECT_EQ(Format("%llx", (unsigned long long)0xDEADBEEFull), "deadbeef");
}
TEST(Printf, LengthZ) {
EXPECT_EQ(Format("%zu", (size_t)42), "42");
EXPECT_EQ(Format("%zu", (size_t)0), "0");
EXPECT_EQ(Format("%zd", (size_t)42), "42");
}
TEST(Printf, LengthT) {
EXPECT_EQ(Format("%td", (ptrdiff_t)42), "42");
EXPECT_EQ(Format("%td", (ptrdiff_t)-42), "-42");
}
TEST(Printf, LengthJ) {
EXPECT_EQ(Format("%jd", (intmax_t)42), "42");
EXPECT_EQ(Format("%ju", (uintmax_t)42), "42");
}
//===----------------------------------------------------------------------===//
// String formatting: %s
//===----------------------------------------------------------------------===//
TEST(Printf, StringBasic) {
EXPECT_EQ(Format("%s", "hello"), "hello");
EXPECT_EQ(Format("%s", ""), "");
}
TEST(Printf, StringNull) {
EXPECT_EQ(Format("%s", (const char*)nullptr), "(null)");
}
TEST(Printf, StringPrecision) {
EXPECT_EQ(Format("%.3s", "hello"), "hel");
EXPECT_EQ(Format("%.10s", "hello"), "hello");
EXPECT_EQ(Format("%.0s", "hello"), "");
}
TEST(Printf, StringWidth) {
EXPECT_EQ(Format("%10s", "hello"), " hello");
EXPECT_EQ(Format("%-10s", "hello"), "hello ");
}
TEST(Printf, StringWidthAndPrecision) {
EXPECT_EQ(Format("%10.3s", "hello"), " hel");
EXPECT_EQ(Format("%-10.3s", "hello"), "hel ");
}
TEST(Printf, StringDynamicPrecision) {
EXPECT_EQ(Format("%.*s", 3, "hello"), "hel");
EXPECT_EQ(Format("%.*s", 0, "hello"), "");
EXPECT_EQ(Format("%.*s", -1, "hello"), "hello"); // Negative = no limit.
}
TEST(Printf, StringDynamicWidthAndPrecision) {
EXPECT_EQ(Format("%*.*s", 10, 3, "hello"), " hel");
}
// The security-critical test: precision must bound reads, not just output.
TEST(Printf, StringPrecisionBoundsReads) {
// Create a string of exactly 5 characters with no NUL terminator.
// ASAN will catch any over-read.
char buffer[5] = {'h', 'e', 'l', 'l', 'o'};
EXPECT_EQ(Format("%.*s", 5, buffer), "hello");
EXPECT_EQ(Format("%.*s", 3, buffer), "hel");
EXPECT_EQ(Format("%.*s", 0, buffer), "");
}
//===----------------------------------------------------------------------===//
// Character formatting: %c
//===----------------------------------------------------------------------===//
TEST(Printf, CharBasic) {
EXPECT_EQ(Format("%c", 'A'), "A");
EXPECT_EQ(Format("%c", ' '), " ");
EXPECT_EQ(Format("%c", 0), std::string(1, '\0'));
}
TEST(Printf, CharWidth) {
EXPECT_EQ(Format("%5c", 'A'), " A");
EXPECT_EQ(Format("%-5c", 'A'), "A ");
}
//===----------------------------------------------------------------------===//
// Pointer formatting: %p
//===----------------------------------------------------------------------===//
TEST(Printf, PointerNull) { EXPECT_EQ(Format("%p", (void*)nullptr), "0x0"); }
TEST(Printf, PointerNonNull) {
// Use a known address for predictable output.
std::string result = Format("%p", (void*)0xDEAD);
EXPECT_EQ(result, "0xdead");
}
TEST(Printf, PointerWidth) {
EXPECT_EQ(Format("%20p", (void*)0x1), " 0x1");
EXPECT_EQ(Format("%-20p", (void*)0x1), "0x1 ");
}
//===----------------------------------------------------------------------===//
// Floating-point formatting: %f
//===----------------------------------------------------------------------===//
TEST(Printf, FloatFixedBasic) {
EXPECT_EQ(Format("%f", 0.0), "0.000000");
EXPECT_EQ(Format("%f", 1.0), "1.000000");
EXPECT_EQ(Format("%f", -1.0), "-1.000000");
EXPECT_EQ(Format("%f", 3.14159), "3.141590");
}
TEST(Printf, FloatFixedPrecision) {
EXPECT_EQ(Format("%.0f", 3.14), "3");
EXPECT_EQ(Format("%.1f", 3.14), "3.1");
EXPECT_EQ(Format("%.2f", 3.14), "3.14");
EXPECT_EQ(Format("%.3f", 3.14), "3.140");
}
TEST(Printf, FloatFixedRounding) {
EXPECT_EQ(Format("%.0f", 0.5), "0"); // Banker's rounding: 0.5 → 0.
EXPECT_EQ(Format("%.0f", 1.5), "2"); // Banker's rounding: 1.5 → 2.
EXPECT_EQ(Format("%.0f", 2.5), "2"); // Banker's rounding: 2.5 → 2.
EXPECT_EQ(Format("%.1f", 1.25), "1.2"); // 1.25 → 1.2 (round to even).
}
TEST(Printf, FloatFixedWidth) {
EXPECT_EQ(Format("%10.2f", 3.14), " 3.14");
EXPECT_EQ(Format("%-10.2f", 3.14), "3.14 ");
EXPECT_EQ(Format("%010.2f", 3.14), "0000003.14");
}
TEST(Printf, FloatFixedSign) {
EXPECT_EQ(Format("%+f", 3.14), "+3.140000");
EXPECT_EQ(Format("%+f", -3.14), "-3.140000");
EXPECT_EQ(Format("% f", 3.14), " 3.140000");
}
TEST(Printf, FloatFixedHash) {
// '#' forces decimal point even with precision 0.
EXPECT_EQ(Format("%#.0f", 3.0), "3.");
}
//===----------------------------------------------------------------------===//
// Floating-point formatting: %e, %E
//===----------------------------------------------------------------------===//
TEST(Printf, FloatExponentialBasic) {
EXPECT_EQ(Format("%e", 0.0), "0.000000e+00");
EXPECT_EQ(Format("%e", 1.0), "1.000000e+00");
EXPECT_EQ(Format("%e", 100.0), "1.000000e+02");
EXPECT_EQ(Format("%e", 0.001), "1.000000e-03");
}
TEST(Printf, FloatExponentialUppercase) {
EXPECT_EQ(Format("%E", 1.0), "1.000000E+00");
}
TEST(Printf, FloatExponentialPrecision) {
EXPECT_EQ(Format("%.0e", 1.0), "1e+00");
EXPECT_EQ(Format("%.2e", 1.0), "1.00e+00");
}
//===----------------------------------------------------------------------===//
// Floating-point formatting: %g, %G
//===----------------------------------------------------------------------===//
TEST(Printf, FloatGeneralBasic) {
EXPECT_EQ(Format("%g", 0.0), "0");
EXPECT_EQ(Format("%g", 1.0), "1");
EXPECT_EQ(Format("%g", 100.0), "100");
EXPECT_EQ(Format("%g", 100000.0), "100000");
EXPECT_EQ(Format("%g", 1000000.0), "1e+06");
}
TEST(Printf, FloatGeneralSmallValues) {
EXPECT_EQ(Format("%g", 0.0001), "0.0001");
EXPECT_EQ(Format("%g", 0.00001), "1e-05");
}
TEST(Printf, FloatGeneralTrailingZeros) {
// %g strips trailing zeros.
EXPECT_EQ(Format("%g", 1.5), "1.5");
EXPECT_EQ(Format("%g", 1.50), "1.5");
}
TEST(Printf, FloatGeneralHash) {
// '#' flag preserves trailing zeros.
EXPECT_EQ(Format("%#g", 1.0), "1.00000");
}
TEST(Printf, FloatGeneralUppercase) {
EXPECT_EQ(Format("%G", 1000000.0), "1E+06");
}
//===----------------------------------------------------------------------===//
// Floating-point special values
//===----------------------------------------------------------------------===//
TEST(Printf, FloatNaN) {
EXPECT_EQ(Format("%f", NAN), "nan");
EXPECT_EQ(Format("%F", NAN), "NAN");
EXPECT_EQ(Format("%e", NAN), "nan");
EXPECT_EQ(Format("%g", NAN), "nan");
EXPECT_EQ(Format("%10f", NAN), " nan");
}
TEST(Printf, FloatInf) {
EXPECT_EQ(Format("%f", INFINITY), "inf");
EXPECT_EQ(Format("%f", -INFINITY), "-inf");
EXPECT_EQ(Format("%F", INFINITY), "INF");
EXPECT_EQ(Format("%+f", INFINITY), "+inf");
}
TEST(Printf, FloatNegativeZero) {
EXPECT_EQ(Format("%f", -0.0), "-0.000000");
EXPECT_EQ(Format("%e", -0.0), "-0.000000e+00");
EXPECT_EQ(Format("%g", -0.0), "-0");
}
TEST(Printf, FloatSubnormals) {
// Subnormals have biased exponent 0 and require special handling in the
// log10 approximation and scaling paths (scale factors that would overflow
// double range). Test a range of subnormal values.
ExpectMatchesLibc("%e", 5e-324); // Smallest subnormal (DBL_TRUE_MIN).
ExpectMatchesLibc("%e", 1e-320); // Small subnormal.
ExpectMatchesLibc("%e", 1e-310); // Near the normal/subnormal boundary.
ExpectMatchesLibc("%e", 2.2e-308); // Just below DBL_MIN.
ExpectMatchesLibc("%g", 5e-324);
ExpectMatchesLibc("%f", 5e-324);
}
TEST(Printf, FloatExtremeValues) {
// Values near the limits of double range.
ExpectMatchesLibc("%e", 1e300);
ExpectMatchesLibc("%e", 1e308);
ExpectMatchesLibc("%e", DBL_MAX); // ~1.798e+308.
ExpectMatchesLibc("%g", DBL_MAX);
ExpectMatchesLibc("%e", DBL_MIN); // Smallest normal: ~2.225e-308.
ExpectMatchesLibc("%g", DBL_MIN);
}
TEST(Printf, FloatRoundingBoundary) {
// Values that previously triggered rounding bugs at the boundary between
// round-up and round-down, discovered by fuzzing.
ExpectMatchesLibc("%e", 587826050.0); // Division-based normalization.
ExpectMatchesLibc("%e", -587826050.0);
ExpectMatchesLibc("%e", 575537150.0); // Same class of bug.
ExpectMatchesLibc("%e", -575537150.0);
ExpectMatchesLibc("%e", 127922.5625); // Log10 off by 1 + truncation.
// %g routing must use the post-rounding exponent. 9.5 rounds to 10 with
// %.1g (1 sig digit), bumping the exponent from 0 to 1, which triggers %e.
// 0.95 (actually 0.94999...) rounds to 0.9, NOT 1.0 — exponent stays at -1.
ExpectMatchesLibc("%.1g", 9.5);
ExpectMatchesLibc("%.1g", 0.95);
ExpectMatchesLibc("%.2g", 9.95);
ExpectMatchesLibc("%.2g", 99.5);
ExpectMatchesLibc("%.3g", 999.5);
ExpectMatchesLibc("%.1g", 0.0095);
}
TEST(Printf, FloatLargePrecision) {
// Precisions beyond 17 significant digits produce trailing zeros. These
// previously caused a stack buffer overflow when the zeros were written
// into the fixed-size formatting buffer. Verify they are now streamed
// directly to the output without overflow.
// %e with large precision: 1 digit + '.' + 400 zeros + 'e+00' = 406 chars.
std::string result = Format("%.400e", 1.0);
EXPECT_EQ(result.substr(0, 4), "1.00");
EXPECT_EQ(result.substr(result.size() - 4), "e+00");
EXPECT_EQ((int)result.size(), 406);
// %f with large precision on a large value. DBL_MAX has ~309 integral
// digits + '.' + 100 fractional digits = ~410 chars.
result = Format("%.100f", 1e200);
EXPECT_GT((int)result.size(), 200);
// Should end with many zeros (digits beyond 17 significant).
EXPECT_EQ(result.substr(result.size() - 5), "00000");
// %f with large precision on a small value.
result = Format("%.50f", 0.1);
EXPECT_EQ(result[0], '0');
EXPECT_EQ(result[1], '.');
EXPECT_EQ((int)result.size(), 52); // "0." + 50 digits.
// %f with extended precision on very small fractions. The fractional digit
// path extends effective_precision beyond 17 for small values where leading
// zeros don't consume significant digits. Exercises the full 19-digit path.
result = Format("%.19f", 1e-18);
EXPECT_EQ(result.substr(0, 2), "0.");
EXPECT_EQ((int)result.size(), 21); // "0." + 19 digits.
result = Format("%.18f", 1e-18);
EXPECT_EQ(result.substr(0, 2), "0.");
EXPECT_EQ((int)result.size(), 20); // "0." + 18 digits.
// Dry-run (NULL buffer) should return correct length even for huge output.
int dry_run_length = iree_snprintf(nullptr, 0, "%.500e", 1.0);
EXPECT_EQ(dry_run_length, 506); // 1 + '.' + 500 + 'e+00' = 506.
}
//===----------------------------------------------------------------------===//
// Multiple specifiers in one format string
//===----------------------------------------------------------------------===//
TEST(Printf, MultipleSpecifiers) {
EXPECT_EQ(Format("%d + %d = %d", 1, 2, 3), "1 + 2 = 3");
EXPECT_EQ(Format("%s: %d", "count", 42), "count: 42");
EXPECT_EQ(Format("[%08X]", 255u), "[000000FF]");
}
//===----------------------------------------------------------------------===//
// Error handling
//===----------------------------------------------------------------------===//
// Helper for testing intentionally-invalid format strings without triggering
// -Wformat warnings. Uses iree_vsnprintf to bypass format checking.
static int format_invalid(char* buffer, size_t count, const char* format, ...) {
va_list args;
va_start(args, format);
int result = iree_vsnprintf(buffer, count, format, args);
va_end(args);
return result;
}
TEST(Printf, UnknownSpecifier) {
char buffer[32];
EXPECT_EQ(format_invalid(buffer, sizeof(buffer), "%q", 42), -1);
}
TEST(Printf, TruncatedFormat) {
char buffer[32];
// Format string ending with '%' and no specifier.
EXPECT_EQ(format_invalid(buffer, sizeof(buffer), "hello%"), -1);
}
//===----------------------------------------------------------------------===//
// Buffer truncation
//===----------------------------------------------------------------------===//
TEST(Printf, Truncation) {
// Full output is "hello" (5 chars).
EXPECT_EQ(FormatTruncated(6, "%s", "hello"), "hello"); // Exact fit.
EXPECT_EQ(FormatTruncated(4, "%s", "hello"), "hel"); // Truncated.
EXPECT_EQ(FormatTruncated(1, "%s", "hello"), ""); // Only NUL.
// Return value is still the full length.
char buffer[4];
int result = iree_snprintf(buffer, sizeof(buffer), "%s", "hello");
EXPECT_EQ(result, 5); // Would need 5 chars.
EXPECT_EQ(std::string(buffer), "hel"); // But only wrote 3 + NUL.
}
TEST(Printf, DryRun) {
// NULL buffer: measure only.
int result = iree_snprintf(nullptr, 0, "hello %d", 42);
EXPECT_EQ(result, 8); // "hello 42" = 8 chars.
}
TEST(Printf, ZeroSizeBuffer) {
char buffer = 'X';
int result = iree_snprintf(&buffer, 0, "hello");
EXPECT_EQ(result, 5);
EXPECT_EQ(buffer, 'X'); // Should not write anything.
}
//===----------------------------------------------------------------------===//
// Callback mode (iree_fctprintf)
//===----------------------------------------------------------------------===//
TEST(Printf, CallbackBasic) {
EXPECT_EQ(FormatCallback("%d + %s", 42, "hello"), "42 + hello");
}
TEST(Printf, CallbackMatchesBuffer) {
// Verify callback and buffer modes produce identical output.
auto check = [](const char* format, auto... args) {
std::string buffer_result = Format(format, args...);
std::string callback_result = FormatCallback(format, args...);
EXPECT_EQ(buffer_result, callback_result) << "Format: " << format;
};
check("%d", 42);
check("%s", "hello");
check("%.3f", 3.14159);
check("%08X", 0xDEADBEEFu);
check("%+10.2f", -3.14);
}
//===----------------------------------------------------------------------===//
// Differential testing against libc
//===----------------------------------------------------------------------===//
TEST(Printf, DifferentialIntegers) {
ExpectMatchesLibc("%d", 0);
ExpectMatchesLibc("%d", 42);
ExpectMatchesLibc("%d", -42);
ExpectMatchesLibc("%d", INT_MAX);
ExpectMatchesLibc("%d", INT_MIN);
ExpectMatchesLibc("%u", 0u);
ExpectMatchesLibc("%u", UINT_MAX);
ExpectMatchesLibc("%x", 255u);
ExpectMatchesLibc("%X", 255u);
ExpectMatchesLibc("%o", 8u);
ExpectMatchesLibc("%08x", 255u);
ExpectMatchesLibc("%#x", 255u);
ExpectMatchesLibc("%#X", 255u);
ExpectMatchesLibc("%#o", 8u);
ExpectMatchesLibc("%+d", 42);
ExpectMatchesLibc("% d", 42);
ExpectMatchesLibc("%10d", 42);
ExpectMatchesLibc("%-10d", 42);
ExpectMatchesLibc("%010d", 42);
ExpectMatchesLibc("%.5d", 42);
ExpectMatchesLibc("%.0d", 0);
ExpectMatchesLibc("%lld", (long long)INT64_MAX);
ExpectMatchesLibc("%lld", (long long)INT64_MIN);
ExpectMatchesLibc("%llu", (unsigned long long)UINT64_MAX);
ExpectMatchesLibc("%zu", (size_t)42);
}
TEST(Printf, DifferentialStrings) {
ExpectMatchesLibc("%s", "hello");
ExpectMatchesLibc("%.3s", "hello");
ExpectMatchesLibc("%10s", "hello");
ExpectMatchesLibc("%-10s", "hello");
ExpectMatchesLibc("%10.3s", "hello");
ExpectMatchesLibc("%-10.3s", "hello");
}
TEST(Printf, DifferentialFloats) {
ExpectMatchesLibc("%f", 0.0);
ExpectMatchesLibc("%f", 1.0);
ExpectMatchesLibc("%f", -1.0);
ExpectMatchesLibc("%f", 3.14159);
ExpectMatchesLibc("%.0f", 3.0);
ExpectMatchesLibc("%.2f", 3.14);
ExpectMatchesLibc("%e", 0.0);
ExpectMatchesLibc("%e", 1.0);
ExpectMatchesLibc("%e", 100.0);
ExpectMatchesLibc("%.2e", 1.0);
ExpectMatchesLibc("%g", 0.0);
ExpectMatchesLibc("%g", 1.0);
ExpectMatchesLibc("%g", 100.0);
ExpectMatchesLibc("%g", 0.0001);
}
//===----------------------------------------------------------------------===//
// PRI macro compatibility (verifies our format specifiers work with standard
// PRI macros that libc format-checking attributes expect)
//===----------------------------------------------------------------------===//
TEST(Printf, PRIMacros) {
EXPECT_EQ(Format("%" PRId64, (int64_t)42), "42");
EXPECT_EQ(Format("%" PRIu64, (uint64_t)42), "42");
EXPECT_EQ(Format("%" PRIx64, (uint64_t)255), "ff");
EXPECT_EQ(Format("%" PRIX64, (uint64_t)255), "FF");
EXPECT_EQ(Format("%" PRId32, (int32_t)-42), "-42");
EXPECT_EQ(Format("%" PRIu32, (uint32_t)42), "42");
}
//===----------------------------------------------------------------------===//
// Coverage: edge cases for paths not exercised by differential tests
//===----------------------------------------------------------------------===//
TEST(Printf, VariadicCallbackWrapper) {
// Exercise iree_fctprintf (variadic) vs iree_vfctprintf (va_list).
std::string result;
int length =
iree_fctprintf(format_callback_append, &result, "%d-%s", 42, "ok");
EXPECT_EQ(length, 5);
EXPECT_EQ(result, "42-ok");
}
TEST(Printf, LargeWidthPrecisionClamping) {
// Width/precision > IREE_PRINTF_MAX_WIDTH_PRECISION (10000) gets clamped.
// This exercises the clamping path in iree_printf_parse_uint and the dynamic
// width/precision parser.
std::string result = Format("%99999d", 42);
EXPECT_GE((int)result.size(), 10000);
EXPECT_LE((int)result.size(), 10001);
// Dynamic width clamping via *.
result = Format("%*d", 99999, 42);
EXPECT_GE((int)result.size(), 10000);
EXPECT_LE((int)result.size(), 10001);
// Dynamic precision clamping via *.
result = Format("%.*d", 99999, 42);
EXPECT_GE((int)result.size(), 10000);
EXPECT_LE((int)result.size(), 10001);
}
TEST(Printf, ExponentialLargePrecisionZero) {
// %.400e of 0.0 should produce "0." followed by 400 zeros then "e+00".
// This exercises the trailing_zeros overflow path in format_exponential.
std::string result = Format("%.400e", 0.0);
EXPECT_EQ((int)result.size(), 406); // "0." + 400 zeros + "e+00"
EXPECT_EQ(result.substr(0, 2), "0.");
EXPECT_EQ(result.substr(result.size() - 4), "e+00");
}
TEST(Printf, SubnormalExponential) {
// Subnormals exercise the multi-step normalization path where the
// correction loops (normalized < 1.0 / normalized >= 10.0) may fire.
std::string result = Format("%e", 5e-324); // Smallest subnormal.
EXPECT_NE(result.find("e-"), std::string::npos); // Must have exponent.
result = Format("%e", 2.2250738585072014e-308); // Smallest normal.
EXPECT_NE(result.find("e-"), std::string::npos);
}
} // namespace