#UB passing va_list as function parameter

73 messages · Page 1 of 1 (latest)

formal snow
#

va_list shows undefined behavior when passed to another function.
I provided a minimized example based on the codebase i (have to) work on, sample output and steps to reproduce below.
If you have any insight why this happens, I would be glad to hear from you!

Expected behavior:

test
42
3.14

Shown behavior:
On ARM: The int value changes with every execution.

test
1563430200
3.14

On x86: The int value is constant per execution.

test
4202506
3.14

Steps to reproduce:
1.) Copy code example below to "main.cpp"
2.) Build: e.g.: $ g++ main.cpp -Wall -Wpedantic -O0
3.) Execute: $ ./a.out

Disclaimer: The design of the code is bugprone, passing the number of va_args and doing type matching based on the index.
Content of main.cpp:

#include <cstdarg>
#include <cstdint>
#include <iostream>

void process_va_list(const uint64_t argument_index, va_list va) {
  va_list va_copy;
  va_copy(va_copy, va);

  // The commented lines below output the expected value

  // std::cout << va_arg(va_copy, const char*) << "\n";
  // std::cout << va_arg(va_copy, int) << "\n";
  // std::cout << va_arg(va_copy, double) << "\n";

  if (argument_index == 0) {
    std::cout << va_arg(va_copy, const char *) << "\n";
  } else if (argument_index == 1) {
    std::cout << va_arg(va_copy, int) << "\n";
  } else if (argument_index == 2) {
    std::cout << va_arg(va_copy, double) << "\n";
  }

  va_end(va_copy);
}

void va_function(uint64_t number_of_vargs, ...) {
  va_list args;
  va_start(args, number_of_vargs);

  for (uint64_t argument_index = 0; argument_index < number_of_vargs;
       argument_index++) {
    process_va_list(argument_index, args);
  }

  va_end(args);
}

int main() {
  const uint64_t number_of_vargs = 3;
  va_function(number_of_vargs, "test", 42, 3.14);
  return 0;
}

Edit: syntax highlighting in discord
Edit: add missing cstdint

grand heathBOT
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

charred helm
#

;compile -Wall -Wpedantic -O0

#include <cstdint>
#include <cstdarg>
#include <iostream>

void process_va_list(const uint64_t argument_index, va_list va) {
  va_list va_copy;
  va_copy(va_copy, va);

  // The commented lines below output the expected value

  // std::cout << va_arg(va_copy, const char*) << "\n";
  // std::cout << va_arg(va_copy, int) << "\n";
  // std::cout << va_arg(va_copy, double) << "\n";

  if (argument_index == 0) {
    std::cout << va_arg(va_copy, const char *) << "\n";
  } else if (argument_index == 1) {
    std::cout << va_arg(va_copy, int) << "\n";
  } else if (argument_index == 2) {
    std::cout << va_arg(va_copy, double) << "\n";
  }

  va_end(va_copy);
}

void va_function(uint64_t number_of_vargs, ...) {
  va_list args;
  va_start(args, number_of_vargs);

  for (uint64_t argument_index = 0; argument_index < number_of_vargs;
       argument_index++) {
    process_va_list(argument_index, args);
  }

  va_end(args);
}

int main() {
  const int number_of_vargs = 3;
  va_function(number_of_vargs, "test", 42, 3.14);
  return 0;
}
edgy ginkgoBOT
#
Program Output
test
4202506
3.14
formal snow
#

sorry

#

#include <cstdint> was missing in the op, i added it

charred helm
#

what you usually do here is go in cppinsights
which will help you greatly
I am going to debug this app
To see what is wrong

formal snow
#

Ok, thank you

#

can i move the post somehow?

charred helm
#

to where

#

to C-Help?

formal snow
#

is cppinsights a channel?

charred helm
#

no

#

its a website

#

for C++ apps

formal snow
#

ok

charred helm
#

It will help you greatly

formal snow
#

ok, thanks for the advice

#

It shows the macro unroll, I assume?

charred helm
#

In CPPInsights for example you can see which operator is called for example instead of
cout<<
It will generate:
std::operator<<(std::cout.operator<<(

#

It will generate like the code in like more detail

#

I don't know how to describe it really

#

I'm sure there is some definition on the internet

#

for example va_start is __builtin_va_start(args, number_of_vargs);

formal snow
#

yes, it shows the corresponding std and macros

charred helm
#

yeah

formal snow
#

interesting, i will look int some implicit casts already

charred helm
#

I found the problem

#

oh wait no

#

ok I found it

#

genuinely

#

basically

#

what you are doing here

#

is that

#

you check if its for example the first argument

#

then

#

you print it

#

you need to advance it

#

using va_arg

#

does not

#

find the argument for example int

#

and because you are copying it

#

It is always the first index

#

so basically

formal snow
#

i understand

#

no need to explain

charred helm
#

you are interpreting the first argument as differnt types

formal snow
#

thank you xd

charred helm
#

ok

#

np

#

use

#

/solved I think it was

#

to mark this post

#

solved

formal snow
#

yes, i check

#

and if it works i will solve

charred helm
#

ok

#

one thing you can do is use refrences here

#include <cstdint>
#include <cstdarg>
#include <iostream>

void process_va_list(const uint64_t argument_index, va_list& va) {

    // The commented lines below output the expected value

    // std::cout << va_arg(va_copy, const char*) << "\n";
    // std::cout << va_arg(va_copy, int) << "\n";
    // std::cout << va_arg(va_copy, double) << "\n";

    if (argument_index == 0) {
        std::cout << va_arg(va, const char*) << "\n";
    }
    else if (argument_index == 1) {
        std::cout << va_arg(va, int) << "\n";
    }
    else if (argument_index == 2) {
        std::cout << va_arg(va, double) << "\n";
    }
}

void va_function(uint64_t number_of_vargs, ...) {
    va_list args;
    va_start(args, number_of_vargs);

    for (uint64_t argument_index = 0; argument_index < number_of_vargs;
        argument_index++) {
        process_va_list(argument_index, args);
    }

    va_end(args);
}

int main() {
    const int number_of_vargs = 3;
    va_function(number_of_vargs, "test", 42, 3.14);
    return 0;
}
#

;compile

#include <cstdint>
#include <cstdarg>
#include <iostream>

void process_va_list(const uint64_t argument_index, va_list& va) {

    // The commented lines below output the expected value

    // std::cout << va_arg(va_copy, const char*) << "\n";
    // std::cout << va_arg(va_copy, int) << "\n";
    // std::cout << va_arg(va_copy, double) << "\n";

    if (argument_index == 0) {
        std::cout << va_arg(va, const char*) << "\n";
    }
    else if (argument_index == 1) {
        std::cout << va_arg(va, int) << "\n";
    }
    else if (argument_index == 2) {
        std::cout << va_arg(va, double) << "\n";
    }
}

void va_function(uint64_t number_of_vargs, ...) {
    va_list args;
    va_start(args, number_of_vargs);

    for (uint64_t argument_index = 0; argument_index < number_of_vargs;
        argument_index++) {
        process_va_list(argument_index, args);
    }

    va_end(args);
}

int main() {
    const int number_of_vargs = 3;
    va_function(number_of_vargs, "test", 42, 3.14);
    return 0;
}
edgy ginkgoBOT
#
Program Output
test
42
3.14
charred helm
#

This is not recommended tho

#

however a handy fix

formal snow
#

Thank you for your help!

#

!solved

grand heathBOT
#

Thank you and let us know if you have any more questions!

This thread is now set to auto-hide after an hour of inactivity

charred helm
#

I mean if ur using modern C++ features I guess?

turbid bison
#

they are not a recent feature

charred helm
#

what I mean't by modern C++ features is features which are not derived from C stuff like va_args

formal snow