diff --git a/Makefile b/Makefile index 506b9ca6c21f..c1226d8d23d4 100644 --- a/Makefile +++ b/Makefile @@ -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 \ @@ -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/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 diff --git a/lib/programmemory.cpp b/lib/programmemory.cpp index e6b7cc25d051..969b384b370f 100644 --- a/lib/programmemory.cpp +++ b/lib/programmemory.cpp @@ -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, @@ -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; @@ -147,26 +149,33 @@ 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& pred) { - for (auto it = mValues.begin(); it != mValues.end();) { + // TODO: how to delay until we actuallly modify? + copyOnWrite(); + + for (auto it = mValues->begin(); it != mValues->end();) { if (pred(it->first)) - it = mValues.erase(it); + it = mValues->erase(it); else ++it; } @@ -179,25 +188,39 @@ void ProgramMemory::swap(ProgramMemory &pm) void ProgramMemory::clear() { - mValues.clear(); + copyOnWrite(); + + mValues->clear(); } bool ProgramMemory::empty() const { - return mValues.empty(); + return mValues->empty(); } void ProgramMemory::replace(ProgramMemory pm) { - for (auto&& p : pm.mValues) { - mValues[p.first] = std::move(p.second); + copyOnWrite(); + + for (auto&& p : (*pm.mValues)) { + (*mValues)[p.first] = std::move(p.second); } } void ProgramMemory::insert(const ProgramMemory &pm) { + copyOnWrite(); + for (auto&& p : pm) - mValues.insert(p); + mValues->insert(p); +} + +void ProgramMemory::copyOnWrite() +{ + if (mValues.use_count() == 1) + return; + + mValues = std::shared_ptr(new Map(*mValues)); } static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings); diff --git a/lib/programmemory.h b/lib/programmemory.h index 4e34cafa00c4..a5490b81b71e 100644 --- a/lib/programmemory.h +++ b/lib/programmemory.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -96,12 +97,12 @@ struct ExprIdToken { }; }; -struct ProgramMemory { +struct CPPCHECKLIB ProgramMemory { using Map = std::unordered_map; - ProgramMemory() = default; + ProgramMemory() : mValues(new Map()) {} - explicit ProgramMemory(Map values) : mValues(std::move(values)) {} + explicit ProgramMemory(Map values) : mValues(new Map(std::move(values))) {} void setValue(const Token* expr, const ValueFlow::Value& value); const ValueFlow::Value* getValue(nonneg int exprid, bool impossible = false) const; @@ -134,19 +135,23 @@ struct ProgramMemory { void insert(const ProgramMemory &pm); Map::iterator begin() { - return mValues.begin(); + copyOnWrite(); + + return mValues->begin(); } Map::iterator end() { - return mValues.end(); + copyOnWrite(); + + return mValues->end(); } 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) { @@ -158,7 +163,9 @@ struct ProgramMemory { } private: - Map mValues; + void copyOnWrite(); + + std::shared_ptr mValues; }; struct ProgramMemoryState { diff --git a/test/testprogrammemory.cpp b/test/testprogrammemory.cpp new file mode 100644 index 000000000000..68da2a9c4cfc --- /dev/null +++ b/test/testprogrammemory.cpp @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +#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() { + 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{42}); + + v = pm.getValue(id); + ASSERT(v); + ASSERT_EQUALS(42, v->intvalue); + + // create a copy + ProgramMemory pm2 = pm; + + // make sure the value was copied + v = pm2.getValue(id); + ASSERT(v); + ASSERT_EQUALS(42, v->intvalue); + + // set a value in the copy to trigger copy-on-write + pm2.setValue(tok, ValueFlow::Value{43}); + + // make sure the value was set + v = pm2.getValue(id); + ASSERT(v); + ASSERT_EQUALS(43, v->intvalue); + + // make sure the original value remains unchanged + v = pm.getValue(id); + ASSERT(v); + ASSERT_EQUALS(42, v->intvalue); + } + + // TODO: test more levels of inheritence +}; + +REGISTER_TEST(TestProgramMemory) diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index ca0cb73244f7..66dc928a0947 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -83,6 +83,7 @@ +