Bug 397313 - False positive on long double "uninitialised bytes"
Summary: False positive on long double "uninitialised bytes"
Status: REPORTED
Alias: None
Product: valgrind
Classification: Developer tools
Component: memcheck (show other bugs)
Version: 3.12 SVN
Platform: Other Linux
: NOR normal
Target Milestone: ---
Assignee: Julian Seward
URL: https://github.com/ornladios/ADIOS/is...
Keywords:
Depends on:
Blocks:
 
Reported: 2018-08-09 12:39 UTC by Axel
Modified: 2019-01-30 12:43 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Axel 2018-08-09 12:39:03 UTC
The following minimal example leads to a false positive in long double variables on uninitialized bytes:
(minimal example by Norbert Podhorszki, ORNL)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main (int argc, char ** argv) 
{
    char        filename[] = "test_longdouble_valgrind.data";
    long double ld1 = 1.2345e+80;

    long double *bufm = (long double *) malloc (sizeof(long double));
    long double *bufc = (long double *) calloc (1, sizeof(long double));

    memcpy (bufm, &ld1, sizeof(long double));
    memcpy (bufc, &ld1, sizeof(long double));

    int fd = creat(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    write(fd, bufm, sizeof(long double));
    write(fd, bufc, sizeof(long double));
    close(fd);
    free(bufm);
    free(bufc);
    return 0;
}

Valgrind complains about both the malloc'd and calloc'd buffers.

$ valgrind ./test_longdouble_valgrind 
==15574== Memcheck, a memory error detector
==15574== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==15574== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==15574== Command: ./test_longdouble_valgrind
==15574== 
==15574== Syscall param write(buf) points to uninitialised byte(s)
==15574==    at 0x4F312C0: __write_nocancel (syscall-template.S:84)
==15574==    by 0x40083F: main (in /home/adios/work/test/other_tests/test_longdouble_valgrind)
==15574==  Address 0x520404a is 10 bytes inside a block of size 16 alloc'd
==15574==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==15574==    by 0x4007D8: main (in /home/adios/work/test/other_tests/test_longdouble_valgrind)
==15574== 
==15574== Syscall param write(buf) points to uninitialised byte(s)
==15574==    at 0x4F312C0: __write_nocancel (syscall-template.S:84)
==15574==    by 0x400855: main (in /home/adios/work/test/other_tests/test_longdouble_valgrind)
==15574==  Address 0x520409a is 10 bytes inside a block of size 16 alloc'd
==15574==    at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==15574==    by 0x4007EB: main (in /home/adios/work/test/other_tests/test_longdouble_valgrind)
 

We are aware of the limitations listed here
  http://valgrind.org/docs/manual/manual-core.html#manual-core.limits
but this still seems to be a bug.


Further information:
https://github.com/ornladios/ADIOS/issues/184#issuecomment-411728907
Comment 1 Axel 2018-08-09 14:00:44 UTC
Additional system information:

Linux 4.9.0-5-amd64 x86_64 GNU/Linux
gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
sizeof(long double) == 16
Comment 2 Daniel Fahlgren 2019-01-28 23:28:15 UTC
When running with --track-origins=yes you can see where the uninitialised bytes originated from:

==7624==  Uninitialised value was created by a stack allocation
==7624==    at 0x10881A: main (test.c:11)

which indicates a local variable in main is to blame. The problem is that the FPU stack is 80 bits wide even if sizeof(long double) returns 16. After the assignment only the first 10 bytes are initialised since floating point instructions has been used.

Valgrind correctly reports the last 6 bytes as being uninitialised.

If you do a memset followed by an assignment the warning will go away, like:

  memset(&ld1, 0, sizeof ld1);
  ld1 = 1.2345e+80;
Comment 3 Daniel Fahlgren 2019-01-29 05:42:27 UTC
Just to clarify. The following example shows that long double ld1 = <value> does not work as you normally expect an assignment to work.

#include <stdio.h>
#include <string.h>

void func1(void)
{
  long double a;
  memset(&a, 0xFF, sizeof a);
}

void func2(void)
{
  long double a = 0;
  char *p = (char *) &a;
  for (int i = 0; i < sizeof a; i++)
    printf("%d: 0x%02x\n", i, p[i] & 0xff);
}

int main(int argc, char *argv[])
{
  func1()
  func2();
  return 0;
}
Comment 4 Axel 2019-01-30 11:13:28 UTC
Thanks, that's understandable.

But since it's ok for a long double to only store its values in 80 bits, what shall we do about it? Should that specific case be suppressed by valgrind?

Address/memory sanitizers seem to handle our snippets gracefully:

clang -g -fsanitize=address main.c && ./a.out
clang -g -fsanitize=memory main.c && ./a.out
gcc -g -fsanitize=address main.c && ./a.out

(all ok)

clang -g main.c && valgrind --track-origins=yes ./a.out

(this report)
Comment 5 Tom Hughes 2019-01-30 12:43:45 UTC
It's not really something we can do anything about - although we know that the store is only 80 bits when a long double FP value is saved to memory we don't know that the compiler reported the size as 16 bytes with sizeof.

In fact gcc will report different values for sizeof(long double) depending on what compiler options you use but none of that is known to us.

The sanitizers have the advantage of working at compile time and knowing how much space the compiler has actually reserved, but there is no easy way to determine that from the compiled code.

In principle I think -mlong-double-128 should fix this (at the expense of ABI compatibility...) but it doesn't seem to work for me for some reason.