Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProgramMemory: only copy mValues on write #6646

Merged
merged 6 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ TESTOBJ = test/fixture.o \
test/testpostfixoperator.o \
test/testpreprocessor.o \
test/testprocessexecutor.o \
test/testprogrammemory.o \
test/testsettings.o \
test/testsimplifytemplate.o \
test/testsimplifytokens.o \
Expand Down Expand Up @@ -902,6 +903,9 @@ test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp
test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/processexecutor.h externals/simplecpp/simplecpp.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testprocessexecutor.cpp

test/testprogrammemory.o: test/testprogrammemory.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/programmemory.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testprogrammemory.cpp

test/testsettings.o: test/testsettings.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsettings.cpp

Expand Down
67 changes: 42 additions & 25 deletions lib/programmemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ std::size_t ExprIdToken::Hash::operator()(ExprIdToken etok) const
}

void ProgramMemory::setValue(const Token* expr, const ValueFlow::Value& value) {
mValues[expr] = value;
copyOnWrite();

(*mValues)[expr] = value;
ValueFlow::Value subvalue = value;
const Token* subexpr = solveExprValue(
expr,
Expand All @@ -71,12 +73,12 @@ void ProgramMemory::setValue(const Token* expr, const ValueFlow::Value& value) {
},
subvalue);
if (subexpr)
mValues[subexpr] = std::move(subvalue);
(*mValues)[subexpr] = std::move(subvalue);
}
const ValueFlow::Value* ProgramMemory::getValue(nonneg int exprid, bool impossible) const
{
const ProgramMemory::Map::const_iterator it = mValues.find(exprid);
const bool found = it != mValues.cend() && (impossible || !it->second.isImpossible());
const ProgramMemory::Map::const_iterator it = mValues->find(exprid);
const bool found = it != mValues->cend() && (impossible || !it->second.isImpossible());
if (found)
return &it->second;
return nullptr;
Expand Down Expand Up @@ -147,26 +149,36 @@ void ProgramMemory::setContainerSizeValue(const Token* expr, MathLib::bigint val
}

void ProgramMemory::setUnknown(const Token* expr) {
mValues[expr].valueType = ValueFlow::Value::ValueType::UNINIT;
copyOnWrite();

(*mValues)[expr].valueType = ValueFlow::Value::ValueType::UNINIT;
}

bool ProgramMemory::hasValue(nonneg int exprid)
{
return mValues.find(exprid) != mValues.end();
return mValues->find(exprid) != mValues->end();
}

const ValueFlow::Value& ProgramMemory::at(nonneg int exprid) const {
return mValues.at(exprid);
return mValues->at(exprid);
}
ValueFlow::Value& ProgramMemory::at(nonneg int exprid) {
return mValues.at(exprid);
copyOnWrite();

return mValues->at(exprid);
}

void ProgramMemory::erase_if(const std::function<bool(const ExprIdToken&)>& pred)
{
for (auto it = mValues.begin(); it != mValues.end();) {
if (mValues->empty())
return;

// TODO: how to delay until we actuallly modify?
copyOnWrite();

for (auto it = mValues->begin(); it != mValues->end();) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be written as remove-erase?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly but that would not fix the TODO and just clean up the code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the call to remove_if() returns end(), no modification takes place, or am I missing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we would have to determine if a modification happens, then make the copy and afterwards iterate it again because the existing result is based on a different container.

We only need to make this optimization if it is a hot spot.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (pred(it->first))
it = mValues.erase(it);
it = mValues->erase(it);
else
++it;
}
Expand All @@ -179,25 +191,38 @@ void ProgramMemory::swap(ProgramMemory &pm)

void ProgramMemory::clear()
{
mValues.clear();
if (mValues->empty())
return;

copyOnWrite();

mValues->clear();
}

bool ProgramMemory::empty() const
{
return mValues.empty();
return mValues->empty();
}

// NOLINTNEXTLINE(performance-unnecessary-value-param) - technically correct but we are moving the given values
void ProgramMemory::replace(ProgramMemory pm)
{
for (auto&& p : pm.mValues) {
mValues[p.first] = std::move(p.second);
if (pm.empty())
return;

copyOnWrite();

for (auto&& p : (*pm.mValues)) {
(*mValues)[p.first] = std::move(p.second);
}
}

void ProgramMemory::insert(const ProgramMemory &pm)
void ProgramMemory::copyOnWrite()
{
for (auto&& p : pm)
mValues.insert(p);
if (mValues.use_count() == 1)
return;

mValues = std::make_shared<Map>(*mValues);
}

static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings);
Expand Down Expand Up @@ -448,14 +473,6 @@ ProgramMemoryState::ProgramMemoryState(const Settings* s) : settings(s)
assert(settings != nullptr);
}

void ProgramMemoryState::insert(const ProgramMemory &pm, const Token* origin)
{
if (origin)
for (auto&& p : pm)
origins.insert(std::make_pair(p.first.getExpressionId(), origin));
state.insert(pm);
}

void ProgramMemoryState::replace(ProgramMemory pm, const Token* origin)
{
if (origin)
Expand Down
26 changes: 15 additions & 11 deletions lib/programmemory.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
Expand Down Expand Up @@ -96,12 +97,12 @@ struct ExprIdToken {
};
};

struct ProgramMemory {
struct CPPCHECKLIB ProgramMemory {
using Map = std::unordered_map<ExprIdToken, ValueFlow::Value, ExprIdToken::Hash>;

ProgramMemory() = default;
ProgramMemory() : mValues(new Map()) {}

explicit ProgramMemory(Map values) : mValues(std::move(values)) {}
explicit ProgramMemory(Map values) : mValues(new Map(std::move(values))) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make_shared?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would construct a shared_ptr object with an shared_ptr which is unnecessary.


void setValue(const Token* expr, const ValueFlow::Value& value);
const ValueFlow::Value* getValue(nonneg int exprid, bool impossible = false) const;
Expand Down Expand Up @@ -131,22 +132,24 @@ struct ProgramMemory {

void replace(ProgramMemory pm);

void insert(const ProgramMemory &pm);

Map::iterator begin() {
return mValues.begin();
copyOnWrite();

return mValues->begin();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lacks copyOnWrite() as it returns something mutable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

Map::iterator end() {
return mValues.end();
copyOnWrite();

return mValues->end();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lacks copyOnWrite() as it returns something mutable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

Map::const_iterator begin() const {
return mValues.begin();
return mValues->cbegin();
}

Map::const_iterator end() const {
return mValues.end();
return mValues->cend();
}

friend bool operator==(const ProgramMemory& x, const ProgramMemory& y) {
Expand All @@ -158,7 +161,9 @@ struct ProgramMemory {
}

private:
Map mValues;
void copyOnWrite();

std::shared_ptr<Map> mValues;
};

struct ProgramMemoryState {
Expand All @@ -168,7 +173,6 @@ struct ProgramMemoryState {

explicit ProgramMemoryState(const Settings* s);

void insert(const ProgramMemory &pm, const Token* origin = nullptr);
void replace(ProgramMemory pm, const Token* origin = nullptr);

void addState(const Token* tok, const ProgramMemory::Map& vars);
Expand Down
83 changes: 83 additions & 0 deletions test/testprogrammemory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2024 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "fixture.h"
#include "helpers.h"
#include "token.h"
#include "programmemory.h"
#include "vfvalue.h"

class TestProgramMemory : public TestFixture {
public:
TestProgramMemory() : TestFixture("TestProgramMemory") {}

private:
void run() override {
TEST_CASE(copyOnWrite);
}

void copyOnWrite() const {
SimpleTokenList tokenlist("1+1;");
Token* tok = tokenlist.front();
const nonneg int id = 123;
tok->exprId(id);

ProgramMemory pm;
const ValueFlow::Value* v = pm.getValue(id);
ASSERT(!v);
pm.setValue(tok, ValueFlow::Value{41});

v = pm.getValue(id);
ASSERT(v);
ASSERT_EQUALS(41, v->intvalue);

// create a copy
ProgramMemory pm2 = pm;

// make sure the value was copied
v = pm2.getValue(id);
ASSERT(v);
ASSERT_EQUALS(41, v->intvalue);

// set a value in the copy to trigger copy-on-write
pm2.setValue(tok, ValueFlow::Value{42});

// make another copy and set another value
ProgramMemory pm3 = pm2;

// set a value in the copy to trigger copy-on-write
pm3.setValue(tok, ValueFlow::Value{43});

// make sure the value was set
v = pm2.getValue(id);
ASSERT(v);
ASSERT_EQUALS(42, v->intvalue);

// make sure the value was set
v = pm3.getValue(id);
ASSERT(v);
ASSERT_EQUALS(43, v->intvalue);

// make sure the original value remains unchanged
v = pm.getValue(id);
ASSERT(v);
ASSERT_EQUALS(41, v->intvalue);
}
};

REGISTER_TEST(TestProgramMemory)
1 change: 1 addition & 0 deletions test/testrunner.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
<ClCompile Include="testpostfixoperator.cpp" />
<ClCompile Include="testpreprocessor.cpp" />
<ClCompile Include="testprocessexecutor.cpp" />
<ClCompile Include="testprogrammemory.cpp" />
<ClCompile Include="testsettings.cpp" />
<ClCompile Include="testsimplifytemplate.cpp" />
<ClCompile Include="testsimplifytokens.cpp" />
Expand Down
Loading