diff --git a/common/common.c b/common/common.c index 5e2d221f92..03aed57690 100644 --- a/common/common.c +++ b/common/common.c @@ -1463,7 +1463,10 @@ int validate_formatting_string(const char *fmt_dynamic, const char *fmt_referenc * is in position to statically check that the actual varargs match * that reference during build. * Returns 0 if the two reference strings minimize to the same value, - * or a negative value (and sets errno) in case of errors: + * a positive value if they are sufficiently compatible (but not equal): + * 1 dynamic format is the beginning of reference format + * (and there are some ignored left-over arguments) + * ...or a negative value (and sets errno) in case of errors: * -1 for memory-related errors * -2 for minimize_formatting_string() returning NULL * (also likely memory errors) @@ -1480,6 +1483,7 @@ int validate_formatting_string(const char *fmt_dynamic, const char *fmt_referenc size_t lenD = strlen(fmt_dynamic) + 1; size_t lenR = strlen(fmt_reference) + 1; char *bufD = xcalloc(lenD, sizeof(char)), *bufR = xcalloc(lenR, sizeof(char)); + size_t lenBufD; if (!bufD || !bufR) { if (bufD) @@ -1506,6 +1510,46 @@ int validate_formatting_string(const char *fmt_dynamic, const char *fmt_referenc return 0; } + /* Does the reference format start with the complete + * value of the dynamic format? (so bufR is same or + * longer than bufD, and with operation just ignoring + * extra passed arguments, if any) + */ + + /* First, strip dangling non-conversion characters */ + lenBufD = strlen(bufD); + while (lenBufD > 0) { + switch (bufD[lenBufD-1]) { + case '*': + case 'i': + case 'u': + case 'f': + case 'c': + case 's': + case 'p': + case 'n': + break; + + default: + lenBufD--; + bufD[lenBufD] = '\0'; + continue; + } + break; + } + + if (!strncmp(bufD, bufR, strlen(bufD))) { + if (verbosity >= 0) + upsdebugx(verbosity, + "%s: dynamic formatting string '%s' (normalized as '%s') " + "is a subset of expected '%s' (normalized as '%s'); " + "ignoring some passed arguments but okay", + __func__, fmt_dynamic, bufD, fmt_reference, bufR); + free(bufD); + free(bufR); + return 1; + } + /* This be should not be fatal right here, but may be in the caller logic */ if (verbosity >= 0) upsdebugx(verbosity, diff --git a/tests/nutlogtest.c b/tests/nutlogtest.c index cbc597e0ac..9cdb87e598 100644 --- a/tests/nutlogtest.c +++ b/tests/nutlogtest.c @@ -84,12 +84,26 @@ int main(void) { ret++; } - if (snprintf_dynamic(buf, sizeof(buf), dynfmt, "%s%d", "Single string via dynamic format", 1) < 0) { +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat" +#endif + if (snprintf_dynamic(buf, sizeof(buf), dynfmt, "%d", "Single string via dynamic format", 1) < 0) { upsdebugx(0, "D: snprintf_dynamic() correctly reports mis-matched formats"); } else { upsdebugx(0, "E: snprintf_dynamic() wrongly reports well-matched formats"); ret++; } +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW) +# pragma GCC diagnostic pop +#endif + + if (snprintf_dynamic(buf, sizeof(buf), dynfmt, "%s%d", "Single string via dynamic format, plus ignored garbage", 1) < 0) { + upsdebugx(0, "E: snprintf_dynamic() wrongly reports well-matched formats"); + ret++; + } else { + upsdebugx(0, "D: snprintf_dynamic() correctly reports mis-matched formats"); + } /* Note extra non-type chars in "expected" format are stripped */ p = mkstr_dynamic(dynfmt, " %.4s %%", "Single string inlined by mkstr_dynamic()"); @@ -103,24 +117,69 @@ int main(void) { } if (1) { /* scoping */ + int res; char **p, *fmtFloat[] = { "%f", " %A", " %0.1E%% ", NULL }, - *fmtNotFloat[] = { "%f%", "%m", "$f", NULL }; + *fmtNotFloat[] = { "%s", NULL }, + /* TODO: Add conversion? More methods? */ + *fmtConvertFloatOKAY[] = { "", "%f%", "%m", "$f", NULL }, + *fmtConvertFloatTODO[] = { "%lf", "%d", NULL }, + *fmtConvertIntOKAY[] = { "", NULL }, + *fmtConvertIntTODO[] = { "%ld", "%lld", "%hd", NULL } + ; for (p = &(fmtFloat[0]); *p; p++) { - if (validate_formatting_string(*p, "Voltage: %G is not %%d", 1) < 0) { - upsdebugx(0, "E: validate_formatting_string() expecting %%f equivalent failed for: '%s'", *p); + if ((res = validate_formatting_string(*p, "Voltage: %G is not %%d", 1)) < 0) { + upsdebugx(0, "E: validate_formatting_string() expecting %%f equivalent failed (%i) for: '%s'", res, *p); ret++; } else { - upsdebugx(0, "D: validate_formatting_string() expecting %%f equivalent passed for: '%s'", *p); + upsdebugx(0, "D: validate_formatting_string() expecting %%f equivalent passed (%i) for: '%s'", res, *p); } } for (p = &(fmtNotFloat[0]); *p; p++) { - if (validate_formatting_string("%f", *p, 1) < 0) { - upsdebugx(0, "D: validate_formatting_string() expecting %%f failed (as it should have) for: '%s'", *p); + if ((res = validate_formatting_string(*p, "%f", 1)) < 0) { + upsdebugx(0, "D: validate_formatting_string() expecting %%f failed (%i) (as it should have) for: '%s'", res, *p); + } else { + upsdebugx(0, "E: validate_formatting_string() expecting %%f passed (%i) (but should not have) for: '%s'", res, *p); + ret++; + } + } + + /* Auto-conversion or other non-exact equivalence */ + for (p = &(fmtConvertFloatOKAY[0]); *p; p++) { + if ((res = validate_formatting_string(*p, "%f", 1)) > 0) { + upsdebugx(0, "D: validate_formatting_string() expecting %%f passed (%i) (non-exactly) for: '%s'", res, *p); + } else { + upsdebugx(0, "E: validate_formatting_string() expecting %%f failed (%i) for: '%s'", res, *p); + ret++; + } + } + + for (p = &(fmtConvertIntOKAY[0]); *p; p++) { + if ((res = validate_formatting_string(*p, "%d", 1)) > 0) { + upsdebugx(0, "D: validate_formatting_string() expecting %%f passed (%i) (non-exactly) for: '%s'", res, *p); + } else { + upsdebugx(0, "E: validate_formatting_string() expecting %%f failed (%i) for: '%s'", res, *p); + ret++; + } + } + + /* TODO: Make such cases safely fit */ + for (p = &(fmtConvertFloatTODO[0]); *p; p++) { + if ((res = validate_formatting_string(*p, "%f", 1)) < 0) { + upsdebugx(0, "D: validate_formatting_string() expecting %%f failed (%i) (as it should have) for: '%s'", res, *p); + } else { + upsdebugx(0, "E: validate_formatting_string() expecting %%f passed (%i) (but should not have) for: '%s'", res, *p); + ret++; + } + } + + for (p = &(fmtConvertIntTODO[0]); *p; p++) { + if ((res = validate_formatting_string(*p, "%d", 1)) < 0) { + upsdebugx(0, "D: validate_formatting_string() expecting %%d failed (%i) (as it should have) for: '%s'", res, *p); } else { - upsdebugx(0, "E: validate_formatting_string() expecting %%f passed (but should not have) for: '%s'", *p); + upsdebugx(0, "E: validate_formatting_string() expecting %%d passed (%i) (but should not have) for: '%s'", res, *p); ret++; } }