pba
pba

Reputation: 524

__builtin_object_size always returns -1

I understood, that __builtin_object_size should give information about the size of an object, if it can be determined at compile time or the relevant call to malloc uses the attribute alloc_size. I think I compiled the code ok with gcc 9.3 on ubuntu 20.04 with

gcc -g -std=gnu11 -Wall -O2 compstrcpyos.c -o compstrcpyos.exe -lbsd

The code should at least find out in doit the size, but it doesn't. I get -1 as result.

void doit(char *buf1, char *buf2, const char *src) {
  printf("bos1=%zd, bos2=%zd\n",
       __builtin_object_size(buf1, 0),
       __builtin_object_size(buf2, 0));
  strlcpy(buf1, src, __builtin_object_size(buf1, 0));
  strlcpy(buf2, src, __builtin_object_size(buf2, 0));
  for (size_t i=0; i < strlen(buf1); ++i) {
    buf1[i] = tolower(src[i]);
  }  
  for (size_t i=0; i < strlen(buf2); ++i) {
    buf2[i] = tolower(src[i]);
  }  
}

void *malloc(size_t s) __attribute__((alloc_size (1)));

int main(int argc, char *argv[]) {
  if (argc < 2) { return -1; }
  char buf1[20];
  char *buf2 = malloc(20);
  printf("bos1=%zd, bos2=%zd\n",
         __builtin_object_size(buf1, 0),
         __builtin_object_size(buf2, 0));
  doit(buf1, buf2, argv[1]);
  printf("%s\n%s\n", buf1, buf2);
}

Running the code gives.

$ ./compstrcpyos.exe ABCDefghijklmnopq
bos1=20, bos2=20
bos1=-1, bos2=-1
abcdefghijklmnopq
abcdefghijklmnopq

I tried to add the option -fsanitize=object-size, I tried with the constants 1, 2 and 3 instead 0 in the call to __builtin_object_size, but never got 20 in doit. What do I need to do get 20, 20 on the second line (that is in doit) as well. Any help appreciated.

Upvotes: 0

Views: 1082

Answers (2)

rici
rici

Reputation: 241881

I think you are over-estimating the reach of the compiler:

I understood, that __builtin_object_size should give information about the size of an object, if it can be determined at compile time

Up to here, fine.

or the relevant call to malloc uses the attribute alloc_size.

But this is not a disjunction ("or"). The compiler can only know what is known at compile time. So there is no alternative to being able to determine the size at compile-time. Now, if an object is allocated dynamically, the compiler may still be able to determine its size, provided that:

  • It is allocated by a function which the compiler recognises as a dynamic allocation function, and
  • The compiler can deduce from the argument(s) to the dynamic allocation function what the size of the allocated object is, and
  • The compiler can determine the values of those argument(s).

For the first two points, the compiler relies on the alloc_size attribute. If the allocation function is not marked with this attribute, the compiler cannot determine the size of the object. So it's a conjunction ("and"). But even that is not sufficient; the compiler also needs to be able to determine the value of the arguments used when the object was allocated.

For that to work through a function call, the function call basically must be inlined. Static functions will normally be inlined if they are only called in one place. (They may be inlined elsewhere, but a large function is unlikely to be inlined if it is called -- or potentially called -- from more than one call site.)

In short, you would have been more accurate had you said:

__builtin_object_size should give information about the size of an object, if it can be determined at compile time, which requires the relevant call to malloc to have the attribute alloc_size and that its arguments can be determined at compile time.

Upvotes: 1

Ben Zotto
Ben Zotto

Reputation: 71048

Indirecting through the function call erases the ability of the compiler to feel confident in determining the sizes. In theory, that function could be called by other callers, including ones outside the translation unit (file). If you add a static qualifier to the doit function declaration that would signal that there are never external callers, and it's then possible (in this particular example) to make a definitive compile-time determination.

For what it's worth, __builtin_object_size is not the idiomatic way of saying "how big is this thing". It appears intended to be used in ways like the example here in super defensive coding inside one scope.

The idiomatic way of passing a buffer with an explicit size to a function is to pass the buffer and also the size of it as separate arguments (or as part of a context struct or whatnot)

Upvotes: 3

Related Questions