boost::optional strange uninitialize issue
Asked Answered
D

0

6

Time to time gcc gives warning about work with boost::optional via maybe-uninitialized , but I have supposed that this is false positive (like described here https://github.com/boostorg/optional/issues/72).

But now valgrind reports the same issue during runtime, so something wrong, any idea what is the problem:

//Foo.hpp
#include <boost/optional.hpp>
#include <boost/variant.hpp>
#include <cstdio>
#include <string>

extern "C" {
typedef struct FooOpaque FooOpaque;

FooOpaque *Foo_new();

void Foo_delete(const FooOpaque *self);
}

class Foo {
public:
  explicit Foo(FooOpaque *o) noexcept : self_(o) {}
  Foo(const Foo &) = delete;
  Foo &operator=(const Foo &) = delete;

  Foo(Foo &&o) noexcept : self_(o.self_) { o.self_ = nullptr; }
  Foo &operator=(Foo &&o) noexcept {

    assert(this != &o);
    free_mem(this->self_);
    self_ = o.self_;
    o.self_ = nullptr;
    return *this;
  }
  ~Foo() noexcept { free_mem(this->self_); }

private:
  static void free_mem(FooOpaque *&p) noexcept {
    //    printf("test\n");
    if (p != nullptr) {
      Foo_delete(p);
    }
    p = nullptr;
  }

  FooOpaque *self_ = nullptr;
};

boost::variant<boost::optional<Foo>, std::string> f_res_opt(int var);
//Foo.cpp
#include <cassert>
#include <cstdint>
#include <cstring>

#include "Foo.hpp"

extern "C" {
struct FooOpaque {
  int data;
};

FooOpaque *Foo_new() {
  printf("Foo_new begin\n");
  auto p = new FooOpaque;
  p->data = 17;
  return p;
}

void Foo_delete(const FooOpaque *self) {
  assert(self != nullptr);
  printf("Foo_new delete\n");
  delete self;
}
}

boost::variant<boost::optional<Foo>, std::string> f_res_opt(int var) {
  switch (var) {
  case 0:
    return {boost::optional<Foo>{Foo{Foo_new()}}};
  case 1:
    return {boost::optional<Foo>{boost::none}};
  case 2:
    return {std::string{}};
  default:
    std::abort();
  }
}
// main.cpp
#include "Foo.hpp"

int main() {

  auto res1 = f_res_opt(0);
  auto res1_ok = boost::get<boost::optional<Foo>>(boost::move(res1));

  printf("step 2\n");

  auto res2 = f_res_opt(1);

  auto res2_ok = boost::get<boost::optional<Foo>>(boost::move(res2));

  printf("step 3\n");

  auto res3 = f_res_opt(2);

  auto res3_ok = boost::get<std::string>(boost::move(res3));
}

valgrind and gcc reports problem in line:

auto res2_ok = boost::get<boost::optional<Foo>>(boost::move(res2));

that potentialy Foo::free_mem may be used for unitialized memory.

The strange thing that if I enable printf in Foo::free_mem gcc warning (9.1.0) disappear and valgrind (3.15) doesn't report any problems, if I comment it back gcc and valgrind reports the problem.

I compile example with gcc in such way:

g++ -ggdb -O3 -Wall -std=c++11 -Wextra main.cpp foo.cpp

So what is going on, error in my code, in boost::optional, or simultaneous error in valgrind and g++?

May be this bug in g++ that generates wrong code and so valgrind also complain about error?

Diabolism answered 2/7, 2019 at 21:5 Comment(4)
Any reason to not use std::unique_ptr with custom deleter instead of FooForesaid
They lost me at variant-of-an-optional. The whole point of a variant is that each value is optional.Nietzsche
@StevenSudit "The whole point of a variant is that each value is optional" - No variant always contains one of variants, so without optional it would be variant<none_t, Foo, std::string>, but this variant has not exact semantic (I want either string with error or optional result value), and missed potential optimizations that boost::optional may provide.Diabolism
@Foresaid Plenty of reasons, in the real code Foo is not just some kind of smart pointer it contains methods to call "C API", of course it may inherit std::unique_ptr or contains it as member data, but Foo is generated, so to optimize compile time it is better to generate suitable code without any external include.Diabolism

© 2022 - 2025 — McMap. All rights reserved.