Forwarding variadic template parameter to printf-like function - c++

I have the following log function:
template<typename... Arguments>
void Log(const char* file, const int line, int level, const char* fmt, Arguments... args)
{
std::string formattedFile;
if (file)
{
boost::filesystem::path p(file);
formattedFile = p.filename().string();
}
std::string message{boost::str(boost::format("%1%:%2% [%3%] - %s") % formattedFile % line % m_uxid % fmt)};
__android_log_print(level, m_tag.c_str(), message.c_str(), args...);
}
This application is run on Android using NDK, so this is the logging system for that platform. The problem is that __android_log_print() fails to compile with:
error: format not a string literal and no format arguments [-Werror=format-security]
__android_log_print(level, m_tag.c_str(), message.c_str(), std::forward<Arguments>(args)...);
^
I'm not sure what this means. Am I not using the variadic template argument correctly?

Untrusted input into printf can be a security problem. Enforcing the format by using a string literal is one way of improving security
Turning warnings into errors will cause the build to fail so you are forced to address the warning.
GCC's warning options have this to say
-Werror:
Make all warnings into errors.
-Wformat-security:
Warn about uses of format functions that represent possible security problems.
At present, this warns about calls to printf and scanf functions where the format string is not a string literal and there are no format arguments
This may be a security hole if the format string came from untrusted input and contains %n.
What is generally advised is to create a std::string in your function and pass this with a %s format string literal to your logging function
__android_log_print(level, m_tag.c_str(), "%s", message.c_str());
Where message is built from processing args..., typically using something like boost::format or a std::stringstream.
If you want to use your provided fmt string and the variadic args, you can parse the arguments using a custom printf style function which produces a std::string
std::string va_string_printf(const char* format, va_list ap)
{
char stack_buf[256];
char* buf = stack_buf;
int buf_size = sizeof(stack_buf);
std::string out_str;
while(true)
{
va_list ap1;
va_copy(ap1, ap);
int min_buf_size = vsnprintf(buf, buf_size, format, ap1) + 1;
va_end(ap1);
if (min_buf_size > buf_size)
{
if (buf != stack_buf) // allocate a bigger buffer
delete[] buf;
buf = new char[min_buf_size];
buf_size = min_buf_size;
continue;
}
out_str = buf;
break;
}
if (buf != stack_buf)
delete[] buf;
return out_str;
}
std::string string_printf(const char* format, ...)
{
va_list ap;
va_start(ap, format);
std::string str = va_string_printf(format, ap);
va_end(ap);
return str;
}

Related

std::string formatting like sprintf / printf and also allow for no arguments

I am trying to solve a problem just like this one: std::string formatting like sprintf: std::string formatting like sprintf
#include <memory>
#include <string>
#include <stdexcept>
template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
auto size = static_cast<size_t>( size_s );
std::unique_ptr<char[]> buf( new char[ size ] );
std::snprintf( buf.get(), size, format.c_str(), args ... );
return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}
However the problem I am running into is I want to make it work for cases where there is only the const string and no additional args:
`string_format( "just a string" );
If I use the linked solution I get a compile time warning: Not a string literal and no format arguments. I tried to make an intercept function to count the args and then only call my function if the arg size is greater than 0, but I dont think the complier is smart enough to realize what I am doing?
Example:
template<typename ... Args>
std::string pre_string_format( const std::string& format, Args ... args)
{
auto s = sizeof...(Args);
if( s > 0 )
{
return string_format( format, std::forward<Args> (args)...);
}
else
{
std::string retStr = format;
return retStr;
}
}
The underlying problem I am trying to solve is to make a print function that intercepts printf (not sprintf) and then appends things to the string before printing and also logs the prints to different files, etc. The solution provided in the link works great for this with a little tweaking, but it fails if there are args other than the string...
if is evaluated at runtime. Even if the compiler could know which branch is taken, all branches are compiled, hence you get the warning. constexpr if is evaluated at compile time. However, you can use a plain overload:
std::string string_format( const std::string& format) { return format; }

c++ converting const char* to char* for long buffer

I have an old function which I can't change the API
void TraceMsg(const char* fmt, ...)
{
if (!m_MessageFunctions[TraceLevel]) return;
char msgBuffer[MAX_LOG_MSG];
va_list argList;
va_start(argList, fmt);
vsnprintf(msgBuffer, MAX_LOG_MSG, fmt, argList);
va_end(argList);
m_MessageFunctions[TraceLevel](msgBuffer);
}
MAX_LOG_MSG = 2048
I got into a phase where I would like to allocate more space for the messages for the logger in a dynamic way
I have read this article: https://code-examples.net/en/q/4904e5
and changed my code into:
void TraceMsg(const char* fmt, ...)
{
if (!m_MessageFunctions[TraceLevel]) return;
va_list argList;
va_start(argList, fmt);
size_t size = vsnprintf(NULL, 0,fmt, argList);
char* msgBuffer = new char[size];
vsnprintf(msgBuffer, size, fmt, argList);
va_end(argList);
m_MessageFunctions[TraceLevel](msgBuffer);
delete[] msgBuffer;
}
how ever I get wierd characters like
2022-05-03 12:13:20,939 INFO Make graph edge Bayer#LSC_1_2 ->Input#DeMux_LSC§§§§н
2022-05-03 12:13:20,939 INFO Make graph edge Bayer#RGB_IR_2_0 ->0#Mux_X2B_BP§§§§нннннњйн‚€нннннннннннннннннннннннннннннннннннн
Can you please help?
The return value of vsnprintf is
The number of characters that would have been written if n had been
sufficiently large, not counting the terminating null character.
So you need to add 1 to this to make room for the null terminator.

How to convert std::string to wchar_t*

std::regex regexpy("y:(.+?)\"");
std::smatch my;
regex_search(value.text, my, regexpy);
y = my[1];
std::wstring wide_string = std::wstring(y.begin(), y.end());
const wchar_t* p_my_string = wide_string.c_str();
wchar_t* my_string = const_cast<wchar_t*>(p_my_string);
URLDownloadToFile(my_string, aDest);
I'm using Unicode, the encoding of the source string is ASCII, UrlDownloadToFile expands to UrlDownloadToFileW (wchar_t*) the code above compiles in debug mode, but with a lot of warnings like:
warning C4244: 'argument': conversion from 'wchar_t' to 'const _Elem', possible loss of data
So do I ask, how I could convert a std::string to a wchar_t?
First off, you don't need the const_cast, as URLDownloadToFileW() takes a const wchar_t* as input, so passing it wide_string.c_str() will work as-is:
URLDownloadToFile(..., wide_string.c_str(), ...);
That being said, you are constructing a std::wstring with the individual char values of a std::string as-is. That will work without data loss only for ASCII characters <= 127, which have the same numeric values in both ASCII and Unicode. For non-ASCII characters, you need to actually convert the char data to Unicode, such as with MultiByteToWideChar() (or equivilent), eg:
std::wstring to_wstring(const std::string &s)
{
std::wstring wide_string;
// NOTE: be sure to specify the correct codepage that the
// str::string data is actually encoded in...
int len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), s.size(), NULL, 0);
if (len > 0) {
wide_string.resize(len);
MultiByteToWideChar(CP_ACP, 0, s.c_str(), s.size(), &wide_string[0], len);
}
return wide_string;
}
URLDownloadToFileW(..., to_wstring(y).c_str(), ...);
That being said, there is a simpler solution. If the std::string is encoded in the user's default locale, you can simply call URLDownloadToFileA() instead, passing it the original std::string as-is, and let the OS handle the conversion for you, eg:
URLDownloadToFileA(..., y.c_str(), ...);
There is a cross-platform solution. You can use std::mbtowc.
std::wstring convert_mb_to_wc(std::string s) {
std::wstring out;
std::mbtowc(nullptr, 0, 0);
int offset;
size_t index = 0;
for (wchar_t wc;
(offset = std::mbtowc(&wc, &s[index], s.size() - index)) > 0;
index += offset) {
out.push_back(wc);
}
return out;
}
Adapted from an example on cppreference.com at https://en.cppreference.com/w/cpp/string/multibyte/mbtowc .

Why does vsnwprintf not translate normal strings to wide strings?

The following code does not produce the expected output. Why?
wchar_t* wchar_t_printf_return(wchar_t* formatstring, ...){
va_list argp;
va_start(argp, formatstring);
int templen = 256;
templen = vsnwprintf(NULL, 0, formatstring, argp)+3;
wchar_t *buffer = (wchar_t *) malloc ((templen+1)*sizeof(wchar_t));
memset(buffer, 0, (templen+1)*sizeof(*buffer));
int retval;
while ((retval = vsnwprintf(buffer, templen, formatstring, argp)) == -1 || (retval >= (templen-1))){
templen = templen &lt&lt 1;
buffer = (wchar_t *) realloc (buffer, (templen+1)*sizeof(wchar_t));
va_end(argp);
va_start(argp, formatstring);
}
va_end(argp);
buffer[templen] = L'\0';
return buffer;
}
int main(){
int i;
char *id = "2923BE84E16CD6AE529049F1F1BBE9EB";
wchar_t *val = wchar_t_printf_return(L"'%s'", id);
printf("%ls\n", val);
}
EDIT: to state more specifically, the printf in main should wrap the id in two single quotes thereby outputting: '2923BE84E16CD6AE529049F1F1BBE9EB'. The purpose of the main here is to illustrate the bug in the function, no more. The function is supposed to be an alternate of the printf family functions which return the result in a newly allocated buffer instead of a preexisting one. This is being run in cygwin compiled natively via gcc-3 with the -mno-cygwin option (aka mingw). Sorry for the confusion!
The %s specifier changes meaning depending on whether you are using a printf or wprintf family function. When used with a wprintf family function, the %s specifier indicates a wide string, but you're passing a narrow string. You need %hs to say "This is a narrow string."
(You seemed to be aware of this because you use %ls to print a wide string with a printf-family function, but you somehow forgot about it when going the other way.)
Try using %S for a translation, not %s.

Trying to combine two similar printf style debug messages in to a single function

I have two printf style debug logging functions (DebuglogfA, DebuglogfB). Both operate the same way but one of the logging functions takes a logging level as a parameters and ignores low level debug messages.
Currently I duplicate the code for each of these functions but I would like DebuglogfB to be able to call DebuglogfA if the debug level is high enough without having to create a temporary buffer in DebuglogfB.
void DebuglogfA( const char *lpszText, ...)
{
//Initialize variable argument list
va_list argList;
va_start(argList, lpszText);
char buffer[1024];
unsigned short length = snprintf_s(buffer, 1024, "[%d] ", CTime::GetCurrentTimeInSec() );
length += vsnprintf (buffer+length, 1024 - length, lpszText, argList );
LogSend( buffer, length );
}
void DebuglogfB ( const unsigned int level, const char *lpszText, ... )
{
if( level < 50 ) {
return; // To low to report.
}
//Initialize variable argument list
va_list argList;
va_start(argList, lpszText);
char buffer[1024];
unsigned short length = snprintf_s(buffer, 1024, "[%d] ", CTime::GetCurrentTimeInSec() );
length += vsnprintf (buffer+length, 1024 - length, lpszText, argList );
LogSend( buffer, length );
}
My question is:
How do I get function DebuglogfB to call DebuglogfA without creating a buffer for the message in DebuglogfB?
You could create a new function DebuglogfV which has const char *lpszText and va_list argList as parameters, and then let DebuglogfA and DebuglogfB call it to perform the actual logging.