Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why the code crashes when calling std::string::begin()?

Here is a simple code snippet to learn Command Pattern which is to simulate a simple editor.
But I really can’t figure out why it crashes when inserting.The backtrace seems tell me that the Document::content is not vaid when inserting is called.

#include <iostream>
#include <string>
#include <cstdint>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <functional>
#include <deque>
#include <limits>
#include <sstream>

class Document  
{
public:
    enum class DocPos:uint8_t
    {
        DOC_END,
        DOC_BGN,
        DOC_MID,
    };

    int insert(size_t row, size_t col, const std::string str, size_t& ins_pos)
    {
        size_t pos = 0;
        ins_pos = std::string::npos;
        for(int i = 0; i<row-1; i++)
        {
            pos = content.find('\n', pos);  //for simpleness, just consider '\n' other than `\r\n` and etc.
            if(std::string::npos == pos)
            {
                return -1;
            }
        }
    
        std::cout << "LOC A " << pos << " col=" << col << std::endl;
        auto itr = content.begin()+pos;
        std::cout << "*itr " << *itr << std::endl;
        for(int j=0; j<col-1; j++)
        {
            std::cout << "*itr " << *itr << std::endl;
            if(*itr++=='\n')
            {
                return -1;
            }
        }

        ins_pos = pos+col-1;
        std::cout << ins_pos << std::endl;
        content.insert(ins_pos, str);
        return 0;
    }

    int append(const std::string& str)
    {
        content += str;
        return 0;
    }

    int erase(std::size_t length, DocPos pos = DocPos::DOC_END, std::size_t offset=0)
    {
        switch(pos)
        {
            case DocPos::DOC_END:
                if(content.size() < length)
                {
                    return -1;
                }
                content.erase(content.end()-length, content.end());
                break;
            case DocPos::DOC_BGN:
                if(content.size() < length)
                {
                    return -1;
                }
                content.erase(content.begin(), content.begin()+offset);
                break;
            case DocPos::DOC_MID:
                if(std::distance(content.begin()+offset, content.end()) < length)
                {
                    return -1;
                }

                content.erase(content.begin()+offset, content.begin()+offset+length);
                break;
            default:
                return -2;
        }

        return 0;
    }

    friend std::ostream& operator<<(std::ostream& os, const Document& doc)
    {
        os << doc.content;
        return os;
    }
private:
    std::string content;
};

class ICommand
{
public:
    virtual int execute()=0;
    virtual int unexecute()=0;
    virtual ~ICommand() = default;
};

class Append final:  public ICommand
{
public:
    Append(std::shared_ptr<Document> doc_ptr, const std::string& str):m_doc_ptr(doc_ptr), m_str(str){};
    int execute() override
    {
        m_doc_ptr->append(m_str);
        return 0;
    }
    int unexecute() override
    {
        m_doc_ptr->erase(m_str.length(), Document::DocPos::DOC_END);
        return 0;
    }
    ~Append() 
    {
        //std::cout << "append resource is released, str=" << m_str << std::endl;
    }
private:
    std::shared_ptr<Document> m_doc_ptr;
    std::string m_str;
};

class Insert final: public ICommand
{
public:
    Insert(std::shared_ptr<Document> doc_ptr, size_t row, size_t col, const std::string str):m_row(row),m_col(col),m_str(str),m_ins_pos(std::string::npos){}
    int execute() override
    {
        std::size_t pos = std::string::npos;
        return m_doc_ptr->insert(m_row, m_col, m_str, m_ins_pos);
    }

    int unexecute() override
    {
        if(std::string::npos == m_ins_pos)
        {
            return -1;
        }

        return m_doc_ptr->erase(m_str.length(), Document::DocPos::DOC_MID, m_ins_pos);
    }
private:
    std::shared_ptr<Document> m_doc_ptr;
    size_t m_row;
    size_t m_col;
    std::string m_str;
    size_t m_ins_pos;
};

class Invoker
{
public:
    Invoker() = default;

    int execute(std::shared_ptr<ICommand> cmd)
    {
        cmd->execute();
        m_history.push_back(cmd);

        m_redo_list.clear();

        return 0;
    }
    
    int undo()
    {
        if(m_history.empty())
        {
            return -1;
        }

        auto& undo = m_history.back();
        undo ->unexecute();      
        m_redo_list.push_back(std::move(undo));
        m_history.pop_back();

        return 0;
    }
    int redo()
    {
        if(m_redo_list.empty())
        {
            return -1;
        }

        auto& redo = m_redo_list.back();
        redo ->execute();      
        m_history.push_back(std::move(redo));
        m_redo_list.pop_back();

        return 0;
    }
private:
    std::deque<std::shared_ptr<ICommand>> m_history;
    std::deque<std::shared_ptr<ICommand>> m_redo_list;
};

int main()
{
    auto diary_ptr = std::make_shared<Document>();
    auto report_ptr = std::make_shared<Document>();

    auto diary_editor = std::unique_ptr<Invoker>(new Invoker);
    auto report_editor = std::unique_ptr<Invoker>(new Invoker);

    report_editor->execute(std::make_shared<Append>(report_ptr, "Work Report\n"));

    diary_editor->execute(std::make_shared<Append>(diary_ptr, "5/2/2024 "));
    diary_editor->execute(std::make_shared<Append>(diary_ptr, "thursday"));
    diary_editor->execute(std::make_shared<Append>(diary_ptr, "\n"));
    diary_editor->execute(std::make_shared<Append>(diary_ptr, "A white doggy lies in the sunshine."));
    diary_editor->execute(std::make_shared<Append>(diary_ptr, "An old man worrys about the coming strom repoted by the weather report."));
    std::cout << *diary_ptr << std::endl;

    std::cout << "===undo" << std::endl;
    diary_editor->undo();
    std::cout << *diary_ptr << std::endl;

    std::cout << "===append" << std::endl;
    diary_editor->execute(std::make_shared<Append>(diary_ptr, "Some children run after a scaried cat."));
    std::cout << *diary_ptr << std::endl;

    std::cout << "===insert" << std::endl;
    diary_editor->execute(std::make_shared<Insert>(diary_ptr, 1, 10, " rainy"));
    std::cout << *diary_ptr << std::endl;

    report_editor->execute(std::make_shared<Append>(report_ptr, "1. implenments done\n"));
    report_editor->execute(std::make_shared<Append>(report_ptr, "2. unit tests done\n"));

    auto test_undo_redo = [](std::unique_ptr<Invoker>& editor, std::shared_ptr<Document>& doc_ptr, int times){
        std::stringstream org_ss;
        org_ss << *doc_ptr;


        for(int i=0; i<times; i++)
        {
            editor->undo();
        }

        for(int i=0; i<times; i++)
        {
            editor->redo();
        }

        std::stringstream cur_ss;
        cur_ss << *doc_ptr;

        return cur_ss.str() == org_ss.str();
    };

    if(!test_undo_redo(diary_editor, diary_ptr, 3))
    {
        std::cout << "not euqal" << std::endl;
    }

    if(!test_undo_redo(report_editor, report_ptr, 2))
    {
        std::cout << "not euqal" << std::endl;
    }

    std::cout << *report_ptr << std::endl;

    return 0;
}

Here is the the log when the program crashed:

(gdb) bt
#0  0x00007ffff7ed4e74 in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) ()
   from /lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x0000555555557497 in Document::insert (this=0x0, row=1, col=10, 
    str=" rainy", ins_pos=@0x55555557bfe8: 18446744073709551615) at main.cpp:36
#2  0x0000555555557d26 in Insert::execute (this=0x55555557bfa0) at main.cpp:140
#3  0x0000555555557e0f in Invoker::execute (this=0x55555557af30, 
    cmd=std::shared_ptr<ICommand> (use count 1, weak count 0) = {...})
    at main.cpp:167
#4  0x0000555555556ad1 in main () at main.cpp:223
(gdb) 

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

See frame 1

#1  0x0000555555557497 in Document::insert (this=0x0, row=1, col=10, 
                                            ^~~~~~~~

Document used to call insert on was nullptr.
So let’s see why

#2  0x0000555555557d26 in Insert::execute (this=0x55555557bfa0) at main.cpp:140

main.cpp:140 would be this line:

        return m_doc_ptr->insert(m_row, m_col, m_str, m_ins_pos);

Okay, so where is m_doc_ptr initialised? Well, it isn’t. Constructor doesn’t use doc_ptr in any way.


Compiler could’ve caught it for you if you enabled warnings. My compiler immediately tells me something is off about the code:

<source>: In constructor 'Insert::Insert(std::shared_ptr<Document>, size_t, size_t, std::string)':
<source>:135:38: warning: unused parameter 'doc_ptr' [-Wunused-parameter]
  135 |     Insert(std::shared_ptr<Document> doc_ptr, size_t row, size_t col, const std::string str):m_row(row),m_col(col),m_str(str),m_ins_pos(std::string::npos){}
      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading