Skip to content

[BUG] Dangers of move-from-last-use #839

Closed
@YehezkelShB

Description

@YehezkelShB

Describe the bug
Move from last use is dangerous when there are dependencies between objects, e.g. for synchronization or using shared/weak_ptr.

To Reproduce
cpp code:

#include <mutex>

class Obj
{
private:
    std::mutex m_mutex;
    std::string m_name;

public:
    std::unique_lock<decltype(m_mutex)> lock();
    // some setters

    void waitForEvent(std::unique_lock<decltype(m_mutex)>& lock);

    // To allow calling waitForEvent(obj.lock());
    void waitForEvent(std::unique_lock<decltype(m_mutex)>&& lock);
    void setName(std::string name) { m_name = name; }
};

void f(Obj& obj)
{
    auto lock = obj.lock();
    obj.waitForEvent(lock); // <-- last usage of lock, as much as the compiler sees
    // use obj setters; if implicit move happened, this is a data race
    obj.setName("new name");
}

Rewritten as cpp2:

Obj: type =
{
    lock: (this) -> _ = { return std::unique_lock(m_mutex); }
    // some setters

    waitForEvent: (this, inout l: std::unique_lock<std::mutex>) = {}

    // To allow calling waitForEvent(obj.lock());
    waitForEvent: (this, move _: std::unique_lock<std::mutex>) = {}

    setName: (this, name: std::string) = { m_name = name; }

    private m_mutex: std::mutex;
    private m_name: std::string;
}

f: (inout obj: Obj) = {
    lock := obj.lock();
    obj.waitForEvent(lock); // <-- last usage of lock, as much as the compiler sees
    // use obj setters; if implicit move happened, this is a data race
    obj.setName("new name");
}

Resulted with:

//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"

#line 1 "test_implicit_move.cpp2"
class Obj;
#line 2 "test_implicit_move.cpp2"


//=== Cpp2 type definitions and function declarations ===========================

#line 1 "test_implicit_move.cpp2"
class Obj
#line 2 "test_implicit_move.cpp2"
 {
    public: [[nodiscard]] auto lock() const& -> auto;
    // some setters

    public: auto waitForEvent(std::unique_lock<std::mutex>& l) const& -> void;

    // To allow calling waitForEvent(obj.lock());
    public: auto waitForEvent([[maybe_unused]] std::unique_lock<std::mutex>&& unnamed_param_2) const& -> void;

    public: auto setName(cpp2::in<std::string> name) const& -> void;

    private: std::mutex m_mutex; 
    private: std::string m_name; 
    public: Obj() = default;
    public: Obj(Obj const&) = delete; /* No 'that' constructor, suppress copy */
    public: auto operator=(Obj const&) -> void = delete;

#line 15 "test_implicit_move.cpp2"
};

auto f(Obj& obj) -> void;

//=== Cpp2 function definitions =================================================

#line 1 "test_implicit_move.cpp2"

#line 3 "test_implicit_move.cpp2"
    [[nodiscard]] auto Obj::lock() const& -> auto{return std::unique_lock(m_mutex); }

#line 6 "test_implicit_move.cpp2"
    auto Obj::waitForEvent(std::unique_lock<std::mutex>& l) const& -> void{}

#line 9 "test_implicit_move.cpp2"
    auto Obj::waitForEvent([[maybe_unused]] std::unique_lock<std::mutex>&& unnamed_param_2) const& -> void{}

    auto Obj::setName(cpp2::in<std::string> name) const& -> void{m_name = name; }

#line 17 "test_implicit_move.cpp2"
auto f(Obj& obj) -> void{
    auto lock {CPP2_UFCS_0(lock, obj)}; 
    CPP2_UFCS(waitForEvent, obj, std::move(lock));// <-- last usage of lock, as much as the compiler sees
    // use obj setters; if implicit move happened, this is a data race
    CPP2_UFCS(setName, obj, "new name");
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions