gcc 7.1 has introduced a new warning that tells you if you use functions such as snprintf and your arguments would result in output truncation.
The documentation implies that it is only raised if you don't check and act upon the return value:
Level 1 of -Wformat-truncation enabled by -Wformat employs a
conservative approach that warns only about calls to bounded functions
whose return value is unused and that will most likely result in
output truncation.
Here's a sample compilation unit, compiled with version 7.3.0 that illustrates the issue.
#include <stdio.h>
#include <stdlib.h>
int main() {
char w;
int size = snprintf(&w, 1, "%s", "hello world");
if(size<0) {
abort();
}
char *buffer = malloc(size+1);
snprintf(buffer, size+1, "%s", "hello world");
printf("Wrote %d characters: %s\n", size, buffer);
return 0;
}
Compiled like this:
$ gcc -Wformat-truncation=1 test.c
test.c: In function ‘main’:
test.c:8:31: warning: ‘%s’ directive output truncated writing 11 bytes into a region of size 1 [-Wformat-truncation=]
int size = snprintf(&w, 1, "%s", "hello world");
^~ ~~~~~~~~~~~~~
test.c:8:7: note: ‘snprintf’ output 12 bytes into a destination of size 1
int size = snprintf(&w, 1, "%s", "hello world");
Am I misinterpreting the documentation? I can't see how I could check the return value more than I am doing already.
Reference: previous SO question that implies the warning should not be raised. I really don't like disabling warnings and usually compile with -Wall -Werror so I'd appreciate some guidance here.
Related
When I ran the APUE sample code shown below on my Mac, it throws a EXC_BAD_ACCESS exception. I have checked the file cursor, and it is in the right position 12. I even try to replace the fprintf with fputc. After that, it works fine and the exception is gone. But I want to know what happened out there and why.
#include "apue.h"
#define BSZ 48
int main(){
FILE *fp;
char buf[BSZ];
memset(buf,'a',BSZ-2);
buf[BSZ-2]='\0';
buf[BSZ-1]='X';
if ((fp = fmemopen(buf,BSZ,"w+")) == NULL)
err_sys("fmemopen failed");
printf("initial buffer contents: %s\n",buf);
fprintf(fp, "hello, world");
printf("before flush: %s\n", buf);
fflush(fp);
printf("after fflush: %s\n",buf);
printf("len of string in buf = %ld\n",(long)strlen(buf));
memset(buf,'b', BSZ-2);
buf[BSZ-2]='\0';
buf[BSZ-1]='X';
fprintf(buf, "hello, world");
// fputc('a',fp);
fseek(fp,0,SEEK_SET);
printf("after fseek: %s\n",buf);
printf("len of string in buf = %ld\n", (long)strlen(buf));
}
Console out put as below:
/Users/heping/Documents/APUE-Example-Code/stdio/cmake-build-debug/memstr
initial buffer contents:
before flush: hello, world
after fflush: hello, world
len of string in buf = 12
Exception: EXC_BAD_ACCESS (code=1, address=0x8)
Process finished with exit code 9
Last stack frame like this: it seems like that something is wrong with the file lock, I think.
As already discussed in the comments, you passed the buffer buf to fprintf() and not the FILE pointer fp, which causes UB.
Every reasonable compiler will print a warning when you trying to do something like that you enabled the warning flags. You can avoid a lot of debugging when you enable all compiler warnings and only disable specific warnings when you are sure you do not want a warning for some type of error.
As mentioned here How to enable all compiler warnings in CLion?, you can enable warnings by adding the compiler flags to CMakeLists.txt. For all warnings for C code, adding this line:
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Werror")
-Wall will enable all normal warnings, -Wextra will enable additional warnings, -Wpedantic will warn about not strictly following the C standard you specified and -Werror will turn every warning into a error, so you have to fix the error before you can compile the program. In case you get warnings you do not want, you can disable them the same way, like this:
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Werror -Wno-cast-function-type")
-Wno-cast-function-type will disable warnings about casting function pointers to different function pointers. You can read about all the warning options here
I'm getting the following gcc format-truncation warning:
test.c:8:33: warning: ‘/input’ directive output may be truncated writing 6 bytes into a region of size between 1 and 20 [-Wformat-truncation=]
snprintf(dst, sizeof(dst), "%s-more", src);
^~~~~~
test.c:8:3: note: ‘snprintf’ output between 7 and 26 bytes into a destination of size 20
snprintf(dst, sizeof(dst), "%s-more", src);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on code like this:
char dst[20];
char src[20];
scanf("%s", src);
snprintf(dst, sizeof(dst), "%s-more", src);
printf("%s\n", dst);
I'm aware that it might be truncated - but this is exactly the reason why I'm using snprintf in the first place. Is there a way how to make it clear to the compiler that this is intended (without using a pragma or -Wno-format-truncation)?
The warning was added in gcc7.1, see gcc7.1 release changes.
From gcc docs:
Level 1 of -Wformat-truncation [...] warns only about calls to bounded functions whose return value is unused and that will most likely result in output truncation.
The issue was a bug report and was closed as NOTABUG:
Unhandled output truncation is typically a bug in the program. [...]
In cases when truncation is expected the caller typically checks the return value from the function and handles it somehow (e.g., by branching on it). In those cases, the warning is not issued. The source line printed by the warning suggests that this is not one of those cases. The warning is doing what it was designed to do.
But we can just check the return value of snprintf, which returns a negative value on error.
#include <stdio.h>
#include <stdlib.h>
int main() {
char dst[2], src[2] = "a";
// snprintf(dst, sizeof(dst), "%s!", src); // warns
int ret = snprintf(dst, sizeof(dst), "%s!", src);
if (ret < 0) {
abort();
}
// But don't we love confusing one liners?
for (int ret = snprintf(dst, sizeof(dst), "%s!", src); ret < 0;) exit(ret);
// Can we do better?
snprintf(dst, sizeof(dst), "%s!", src) < 0 ? abort() : (void)0;
// Don't we love obfuscation?
#define snprintf_nowarn(...) (snprintf(__VA_ARGS__) < 0 ? abort() : (void)0)
snprintf_nowarn(dst, sizeof(dst), "%s!", src);
}
Tested on https://godbolt.org/ with gcc7.1 gcc7.2 gcc7.3 gcc8.1 with -O{0,1,2,3} -Wall -Wextra -pedantic. Gives no warning. gcc8.1 optimizes/removes the call to abort() with optimization greater than -O1.
Oddly enough, when compiling as a C++ source file, the warning is still there even when we check the return value. All is fine in C. In C++ prefer std::format_to anyway. So:
We can just use compiler specific syntax to disable the warning.
#include <stdio.h>
#include <stdlib.h>
int main() {
char dst[2];
char src[2] = "a";
// does not warn in C
// warns in C++ with g++ newer than 10.1 with optimization -O2
int ret = snprintf(dst, sizeof(dst), "%s!", src);
if (ret < 0) {
abort();
}
// does not warn in C
// still warns in C++
ret = snprintf(dst, sizeof(dst), "%s!", "a");
if (ret < 0) {
abort();
}
// use compiler specific pragmas to disable the warning
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(dst, sizeof(dst), "%s!", "a");
#pragma GCC diagnostic pop
// wrapper macro with compiler specific pragmas
// works for any gcc
// works from g++ 10.1
#ifndef __GNUC__
#define snprintf_nowarn snprintf
#else
#define snprintf_nowarn(...) __extension__({ \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wformat-truncation\""); \
const int _snprintf_nowarn = snprintf(__VA_ARGS__); \
_Pragma("GCC diagnostic pop"); \
_snprintf_nowarn; \
})
#endif
snprintf_nowarn(dst, sizeof(dst), "%s!", "a");
}
This error is only triggered when length-limited *printf functions are called (e.g. snprintf, vsnprintf). In other words, it is not an indication that you may be overflowing a buffer, as may happen with sprintf; it only notifies you that you aren't checking whether snprintf is doing its job and truncating. (Side note: snprintf always null-terminates, so this can't result in a non-terminated string.)
Knowing that, I'm much more sanguine about disabling it globally using -Wno-format-truncation, rather than trying to coax gcc into ignoring a specific instance.
This page was useful to me:
https://www.fluentcpp.com/2019/08/30/how-to-disable-a-warning-in-cpp/
You could resolve the issue for a gcc/clang compiler by doing this:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(dst, sizeof(dst), "%s-more", src);
#pragma GCC diagnostic pop
The webpage above also has a solution for Visual Studio compiler warnings.
Also, Introducing a volatile temporary variable for the destination size is also a workaround here.
char dst[20];
char src[20];
volatile int dst_size = sizeof(dst);
snprintf(dst, dst_size, "%s-more", src);
As suggested by Martin Sebor, you may also use this snprintf_trunc macro;
#define snprintf_trunc(dst, size, ...) \
do { \
volatile size_t n = size; \
snprintf (dst, n, __VA_ARGS__); \
} while (0)
I want to match the regex (?<=SEARCH_THIS=").+(?<!"\n) in C with PCRE.
However, the following code doesn't work as expected.
#include <pcreposix.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
regex_t re;
regmatch_t matches[2];
char *regex = "(?<=SEARCH_THIS=\").+(?<!\"\n)";
char *file = "NO_MATCH=\"0\"\nSOMETHING_ELSE=\"1\"\nSOME_STUFF=\"1\"\nSEARCH_THIS=\"gimme that\"\nNOT_THIS=\"foobar\"\nTHIS_NEITHER=\"test\"\n";
puts("compiling regex");
int compErr = regcomp(&re, regex, REG_NOSUB | REG_EXTENDED);
if(compErr != 0){
char buffer[128];
regerror(compErr, &re, buffer, 100);
printf("regcomp failed: %s\n", buffer);
return 0;
}
puts("executing regex");
int err = regexec(&re, file, 2, matches, 0);
if(err == 0){
puts("no error");
printf("heres the match: [.%*s]",matches[0].rm_eo-matches[0].rm_so,file+matches[0].rm_so);
} else {
puts("some error here!");
char buffer[128];
regerror(err, &re, buffer, 100);
printf("regexec failed: %s\n", buffer);
}
return 0;
}
The console output is:
compiling regex
executing regex
some error here!
regexec failed: No match
I verified the functionality of this regex here
Any idea what is going wrong here?
EDIT #1
Compiler Version
$ arm-merlin-linux-uclibc-gcc --version
arm-merlin-linux-uclibc-gcc (GCC) 4.2.1
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Compile Command
$ arm-merlin-linux-uclibc-gcc -lpcre ./re_test.c -o re_test.o
There are actually a few issues with your code.
First, you use %*s in an attempt to restrict the length of the printed string. However, the integer width before the s formatter is the minimum length of what gets printed; if the corresponding string's length is less than what's given, it'll be padded with spaces. If the length is greater than what's given, it'll just output the whole string. You'll need some other method of restricting the length of the outputted string (just avoid modifying *file, because file points to a constant string).
Second, you specify the REG_NOSUB option in your regcomp call, but according to the man page, this means that no substring positions are stored in the pmatch argument - thus, even if your regexec did work, the following printf would be using uninitialized values (which is undefined behavior).
Finally, I suspect the problem is that the \" and \n characters need to be doubly-escaped; i.e. you need to use \\\" and \\n in your regex string. While the code you gave worked for me (Ubuntu 14.04 x64), the doubly-escaped version also works.
Taking all of this into account, this is the output I get:
compiling regex
executing regex
no error
heres the match: [.gimme that"]
I'm a C noob, going back to school for my masters in CS so I'm taking some time to ramp up my skills. I wanted to see if anybody could lend some assistance on why I'm having problems compiling the following code. I've been following the videos on WiBit.net and develop on a 64 bit Linux environment (Ubuntu 13.10). I am using gedit and the gcc compiler no IDE.
This code runs on my Win 7 VM without errors, however when I try to execute it on my host Linux environment I'm getting errors:
Source Code: This example calls the strcmp and strcmpi functions
#include <stdio.h>
#include <string.h>
int main()
{
char str1[255];
char str2[255];
printf("str1: "); gets(str1);
printf("str2: "); gets(str2);
if(strcmp(str1, str2) == 0)
printf("Strings match exactly!");
else if(strcmpi(str1, str2) == 0)
printf("Strings match when ignoring case!");
return 0;
}
Error Message (Linux ONLY):
$gcc main.c -o demo -lm -pthread -lgmp -lreadline 2>&1
/tmp/ccwqdQMN.o: In function main':
main.c:(.text+0x25): warning: thegets' function is dangerous and should not be used.
main.c:(.text+0x8f): undefined reference to `strcmpi'
collect2: error: ld returned 1 exit status
Source Code 2: This example uses the strupr and strlwr functions
#include <stdio.h>
#include <string.h>
int main()
{
char str1[255];
char str2[255];
printf("str1: "); gets(str1);
printf("str2: "); gets(str2);
strlwr(str1);
strupr(str2);
puts (str1);
puts (str2);
return 0;
}
Error Message (Linux ONLY):
$gcc main.c -o demo -lm -pthread -lgmp -lreadline 2>&1
/tmp/ccWnIfnz.o: In function main':
main.c:(.text+0x25): warning: thegets' function is dangerous and should not be used.
main.c:(.text+0x57): undefined reference to strlwr'
main.c:(.text+0x6b): undefined reference tostrupr'
collect2: error: ld returned 1 exit status
I would love a detailed explanation if someone is willing to help and not tear me apart haha. I know that for best practices we shouldn't use gets due to buffer overflow (for example the user enters a 750 character string). Best practices would use fgets instead but my question is whether I'm getting these errors because these functions aren't part of ANSI C or what. They do show up in the man files on my machine which is throwing me through a loop.
Thanks in advance!
UPDATE:
You guys are awesome. Took all of your advice and comments and was able to revise and make a sample program for string comparison as well as conversion to upper/lower. Glad I was able to get it running on both OSes error free as well.
Sample code:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main()
{
char str[255];
printf("Enter a string: "); fgets(str,255, stdin);
printf("Here is your original string, my master: %s\n", str);
//Now let's loop through and convert this to all lowercase
int i;
for(i = 0; str[i]; i++)
{
str[i] = tolower(str[i]);
}
printf("Here is a lowercase version of your string, my master: %s\n", str);
//Now we'll loop through and convert the string to uppercase
int j;
for(j = 0; str[j]; j++)
{
str[j] = toupper(str[j]);
}
printf("Here is a uppercase version of your string, my master: %s\n", str);
return 0;
}
strcmpi problem: strcasecmp() is the posix standard and so is it in linux.
strupr and strlwr doesn't exist in glibc, although you can implement them with a single line of code, as this:
c - convert a mixed-case string to all lower case
In the compilation, first you can find a warning, because the gcc doesn't find the functions in the included header. In such cases it thinks they are declared as int funcname(void). But later, while linking, it can't find the exported symbols of this nonexistant functions, and thus it can't create the executable. This second error is what stops the compilation.
There are too many difference in the c apis, although the posix standard handles them, microsoft don't follow it.
As you noted, the gets function is unsafe because it does not perform any boundary checking: you have called it with a 255-character string buffer, but if another program wrote a line longer than 255 characters, it could write data into your process's stack, and thereby cause your process to execute malicious code (or at the very least produce a segmentation fault).
Use fgets instead:
printf("str1: "); fgets(str1, 255, stdin);
printf("str2: "); fgets(str2, 255, stdin);
If you read the error output from the compiler carefully, you'll note that it's not issuing an error on your use of gets but a warning. Your code should still compile and execute if you fix the strcmpi call.
I'm writing some C code for an embedded linux system using an open_memstream and I don't understand why I am getting a compile warning: assignment makes pointer from integer without a cast
To make things simple, rather than pasting all my code I reproduced the problem with the small example from here:
#include <stdio.h>
#include <stdlib.h>
int
main (void)
{
FILE *stream;
char *buf;
size_t len;
off_t eob;
stream = open_memstream (&buf, &len);
if (stream == NULL)
/* handle error */ ;
fprintf (stream, "hello my world");
fflush (stream);
printf ("buf=%s, len=%zu\n", buf, len);
eob = ftello(stream);
fseeko (stream, 0, SEEK_SET);
fprintf (stream, "good-bye");
fseeko (stream, eob, SEEK_SET);
fclose (stream);
printf ("buf=%s, len=%zu\n", buf, len);
free (buf);
return 0;
}
The code works, but the compiler complains about the line stream = open_memstream (&buf, &len);
What integer is it talking about? We're passing in a pointer to a size_t as required by the function prototype.
FILE *open_memstream(char **bufp, size_t *sizep);
Is there a problem with this code, or do I need to take a look at my compiler? I want to get rid of this warning the right way.
UPDATE:
Using gcc 4.3.2, glibc 2.9
UPDATE 2:
Tried the following:
powerpc-860-linux-gnu-gcc -std=c99 -Wall -D_XOPEN_SOURCE=700 -c source.c
Result:
source.c: In function 'main':
source.c:12: warning: implicit declaration of function 'open_memstream'
source.c:12: warning: assignment makes pointer from integer without a cast
According to this, it seems that _XOPEN_SOURCE=700 is available since glibc 2.10.
Since I'm using glibc 2.9, what other alternatives do I have (other than upgrading glibc)?
UPDATE 3:
Adding the following got rid of the warning:
extern FILE *open_memstream(char **bufp, size_t *sizep);
Is there anything wrong with this solution?
UPDATE 4:
This worked instead of the extern:
powerpc-860-linux-gnu-gcc -std=c99 -Wall -D_GNU_SOURCE -c ops_cmds.c
So according to the manpage, need to use _GNU_SOURCE if glibc pre-2.10 (in my case) and _XOPEN_SOURCE=700 if 2.10+
Define:
#define _POSIX_C_SOURCE 200809L
or
#define _XOPEN_SOURCE 700
in your source code before including stdio.h. Or with gcc you can define and pass the macro value to the source file with -D option:
gcc -std=c99 -Wall -D_XOPEN_SOURCE=700 -c source.c
open_memstream is a POSIX function and its declaration is not visible in your program without this define.
The compiler is complaining about the return value of open_memstream, not about the arguments you pass in.
Your open_memstream is not declared, i.e. the compiler does not see the prototype. So the compiler (apparently pre-C99) assumes that it returns an int. You are forcing that int into stream pointer, which is what triggers the warning about "making pointer form integer".
Make sure open_memstream is declared before you attempt to use it. The prototype is supposed to reside in stdio.h, but it is only available in POSIX.1-2008. You have to enable it explicitly (see other answers).