Skip to content

Commit 0f28f3e

Browse files
authored
generate AddonInfo only once (#4958)
Currently the `AddonInfo` is generated and discarded on each addon invocation. This leads to an unnecessary process invocation for each addon on each file. Also if an addon is completely broken we will still perform the whole analysis only for it to be failed at the end so we should bail out early if we know it doesn't work at all.
1 parent cc44966 commit 0f28f3e

13 files changed

+365
-247
lines changed

Makefile

Lines changed: 123 additions & 119 deletions
Large diffs are not rendered by default.

cli/cppcheckexecutor.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ bool CppCheckExecutor::parseFromArgs(Settings &settings, int argc, const char* c
150150
if (!loadLibraries(settings))
151151
return false;
152152

153+
if (!loadAddons(settings))
154+
return false;
155+
153156
// Check that all include paths exist
154157
{
155158
for (std::list<std::string>::iterator iter = settings.includePaths.begin();
@@ -406,6 +409,22 @@ bool CppCheckExecutor::loadLibraries(Settings& settings)
406409
return result;
407410
}
408411

412+
bool CppCheckExecutor::loadAddons(Settings& settings)
413+
{
414+
bool result = true;
415+
for (const std::string &addon: settings.addons) {
416+
AddonInfo addonInfo;
417+
const std::string failedToGetAddonInfo = addonInfo.getAddonInfo(addon, settings.exename);
418+
if (!failedToGetAddonInfo.empty()) {
419+
std::cout << failedToGetAddonInfo << std::endl;
420+
result = false;
421+
continue;
422+
}
423+
settings.addonInfos.emplace_back(std::move(addonInfo));
424+
}
425+
return result;
426+
}
427+
409428
#ifdef _WIN32
410429
// fix trac ticket #439 'Cppcheck reports wrong filename for filenames containing 8-bit ASCII'
411430
static inline std::string ansiToOEM(const std::string &msg, bool doConvert)

cli/cppcheckexecutor.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ class CppCheckExecutor : public ErrorLogger {
153153
*/
154154
bool loadLibraries(Settings& settings);
155155

156+
/**
157+
* @brief Load addons
158+
* @param settings Settings
159+
* @return Returns true if successful
160+
*/
161+
static bool loadAddons(Settings& settings);
162+
156163
/**
157164
* @brief Write the checkers report
158165
*/

gui/mainwindow.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,7 @@ Settings MainWindow::getCppcheckSettings()
999999

10001000
addonFilePath.replace(QChar('\\'), QChar('/'));
10011001

1002+
// TODO: use picojson to generate the JSON
10021003
QString json;
10031004
json += "{ \"script\":\"" + addonFilePath + "\"";
10041005
if (!pythonCmd.isEmpty())
@@ -1014,6 +1015,9 @@ Settings MainWindow::getCppcheckSettings()
10141015
}
10151016
json += " }";
10161017
result.addons.emplace(json.toStdString());
1018+
AddonInfo addonInfo;
1019+
addonInfo.getAddonInfo(json.toStdString(), result.exename);
1020+
result.addonInfos.emplace_back(std::move(addonInfo));
10171021
}
10181022

10191023
if (isCppcheckPremium()) {

lib/addoninfo.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Cppcheck - A tool for static C/C++ code analysis
3+
* Copyright (C) 2007-2023 Cppcheck team.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "addoninfo.h"
20+
21+
#include "path.h"
22+
#include "utils.h"
23+
24+
#include <fstream>
25+
#include <sstream>
26+
#include <string>
27+
28+
#include "json.h"
29+
30+
static std::string getFullPath(const std::string &fileName, const std::string &exename) {
31+
if (Path::isFile(fileName))
32+
return fileName;
33+
34+
const std::string exepath = Path::getPathFromFilename(exename);
35+
if (Path::isFile(exepath + fileName))
36+
return exepath + fileName;
37+
if (Path::isFile(exepath + "addons/" + fileName))
38+
return exepath + "addons/" + fileName;
39+
40+
#ifdef FILESDIR
41+
if (Path::isFile(FILESDIR + ("/" + fileName)))
42+
return FILESDIR + ("/" + fileName);
43+
if (Path::isFile(FILESDIR + ("/addons/" + fileName)))
44+
return FILESDIR + ("/addons/" + fileName);
45+
#endif
46+
return "";
47+
}
48+
49+
static std::string parseAddonInfo(AddonInfo& addoninfo, const picojson::value &json, const std::string &fileName, const std::string &exename) {
50+
const std::string& json_error = picojson::get_last_error();
51+
if (!json_error.empty()) {
52+
return "Loading " + fileName + " failed. " + json_error;
53+
}
54+
if (!json.is<picojson::object>())
55+
return "Loading " + fileName + " failed. Bad json.";
56+
picojson::object obj = json.get<picojson::object>();
57+
if (obj.count("args")) {
58+
if (!obj["args"].is<picojson::array>())
59+
return "Loading " + fileName + " failed. args must be array.";
60+
for (const picojson::value &v : obj["args"].get<picojson::array>())
61+
addoninfo.args += " " + v.get<std::string>();
62+
}
63+
64+
if (obj.count("ctu")) {
65+
// ctu is specified in the config file
66+
if (!obj["ctu"].is<bool>())
67+
return "Loading " + fileName + " failed. ctu must be boolean.";
68+
addoninfo.ctu = obj["ctu"].get<bool>();
69+
} else {
70+
addoninfo.ctu = false;
71+
}
72+
73+
if (obj.count("python")) {
74+
// Python was defined in the config file
75+
if (obj["python"].is<picojson::array>()) {
76+
return "Loading " + fileName +" failed. python must not be an array.";
77+
}
78+
addoninfo.python = obj["python"].get<std::string>();
79+
} else {
80+
addoninfo.python = "";
81+
}
82+
83+
if (obj.count("executable")) {
84+
if (!obj["executable"].is<std::string>())
85+
return "Loading " + fileName + " failed. executable must be a string.";
86+
addoninfo.executable = getFullPath(obj["executable"].get<std::string>(), fileName);
87+
return "";
88+
}
89+
90+
return addoninfo.getAddonInfo(obj["script"].get<std::string>(), exename);
91+
}
92+
93+
std::string AddonInfo::getAddonInfo(const std::string &fileName, const std::string &exename) {
94+
if (fileName[0] == '{') {
95+
picojson::value json;
96+
const std::string err = picojson::parse(json, fileName);
97+
(void)err; // TODO: report
98+
return parseAddonInfo(*this, json, fileName, exename);
99+
}
100+
if (fileName.find('.') == std::string::npos)
101+
return getAddonInfo(fileName + ".py", exename);
102+
103+
if (endsWith(fileName, ".py")) {
104+
scriptFile = Path::fromNativeSeparators(getFullPath(fileName, exename));
105+
if (scriptFile.empty())
106+
return "Did not find addon " + fileName;
107+
108+
std::string::size_type pos1 = scriptFile.rfind('/');
109+
if (pos1 == std::string::npos)
110+
pos1 = 0;
111+
else
112+
pos1++;
113+
std::string::size_type pos2 = scriptFile.rfind('.');
114+
if (pos2 < pos1)
115+
pos2 = std::string::npos;
116+
name = scriptFile.substr(pos1, pos2 - pos1);
117+
118+
runScript = getFullPath("runaddon.py", exename);
119+
120+
return "";
121+
}
122+
123+
if (!endsWith(fileName, ".json"))
124+
return "Failed to open addon " + fileName;
125+
126+
std::ifstream fin(fileName);
127+
if (!fin.is_open())
128+
return "Failed to open " + fileName;
129+
picojson::value json;
130+
fin >> json;
131+
return parseAddonInfo(*this, json, fileName, exename);
132+
}

lib/addoninfo.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Cppcheck - A tool for static C/C++ code analysis
3+
* Copyright (C) 2007-2023 Cppcheck team.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#ifndef addonInfoH
20+
#define addonInfoH
21+
22+
#include "config.h"
23+
24+
#include <string>
25+
26+
struct CPPCHECKLIB AddonInfo {
27+
std::string name;
28+
std::string scriptFile; // addon script
29+
std::string executable; // addon executable
30+
std::string args; // special extra arguments
31+
std::string python; // script interpreter
32+
bool ctu = false;
33+
std::string runScript;
34+
35+
std::string getAddonInfo(const std::string &fileName, const std::string &exename);
36+
};
37+
38+
#endif // addonInfoH

lib/cppcheck.cpp

Lines changed: 6 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
* You should have received a copy of the GNU General Public License
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
18+
1819
#include "cppcheck.h"
1920

21+
#include "addoninfo.h"
2022
#include "check.h"
2123
#include "checkunusedfunctions.h"
2224
#include "clangimport.h"
@@ -105,122 +107,6 @@ namespace {
105107
};
106108
}
107109

108-
namespace {
109-
struct AddonInfo {
110-
std::string name;
111-
std::string scriptFile; // addon script
112-
std::string executable; // addon executable
113-
std::string args; // special extra arguments
114-
std::string python; // script interpreter
115-
bool ctu = false;
116-
std::string runScript;
117-
118-
static std::string getFullPath(const std::string &fileName, const std::string &exename) {
119-
if (Path::isFile(fileName))
120-
return fileName;
121-
122-
const std::string exepath = Path::getPathFromFilename(exename);
123-
if (Path::isFile(exepath + fileName))
124-
return exepath + fileName;
125-
if (Path::isFile(exepath + "addons/" + fileName))
126-
return exepath + "addons/" + fileName;
127-
128-
#ifdef FILESDIR
129-
if (Path::isFile(FILESDIR + ("/" + fileName)))
130-
return FILESDIR + ("/" + fileName);
131-
if (Path::isFile(FILESDIR + ("/addons/" + fileName)))
132-
return FILESDIR + ("/addons/" + fileName);
133-
#endif
134-
return "";
135-
}
136-
137-
std::string parseAddonInfo(const picojson::value &json, const std::string &fileName, const std::string &exename) {
138-
const std::string& json_error = picojson::get_last_error();
139-
if (!json_error.empty()) {
140-
return "Loading " + fileName + " failed. " + json_error;
141-
}
142-
if (!json.is<picojson::object>())
143-
return "Loading " + fileName + " failed. Bad json.";
144-
picojson::object obj = json.get<picojson::object>();
145-
if (obj.count("args")) {
146-
if (!obj["args"].is<picojson::array>())
147-
return "Loading " + fileName + " failed. args must be array.";
148-
for (const picojson::value &v : obj["args"].get<picojson::array>())
149-
args += " " + v.get<std::string>();
150-
}
151-
152-
if (obj.count("ctu")) {
153-
// ctu is specified in the config file
154-
if (!obj["ctu"].is<bool>())
155-
return "Loading " + fileName + " failed. ctu must be boolean.";
156-
ctu = obj["ctu"].get<bool>();
157-
} else {
158-
ctu = false;
159-
}
160-
161-
if (obj.count("python")) {
162-
// Python was defined in the config file
163-
if (obj["python"].is<picojson::array>()) {
164-
return "Loading " + fileName +" failed. python must not be an array.";
165-
}
166-
python = obj["python"].get<std::string>();
167-
} else {
168-
python = "";
169-
}
170-
171-
if (obj.count("executable")) {
172-
if (!obj["executable"].is<std::string>())
173-
return "Loading " + fileName + " failed. executable must be a string.";
174-
executable = getFullPath(obj["executable"].get<std::string>(), fileName);
175-
return "";
176-
}
177-
178-
return getAddonInfo(obj["script"].get<std::string>(), exename);
179-
}
180-
181-
std::string getAddonInfo(const std::string &fileName, const std::string &exename) {
182-
if (fileName[0] == '{') {
183-
picojson::value json;
184-
const std::string err = picojson::parse(json, fileName);
185-
(void)err; // TODO: report
186-
return parseAddonInfo(json, fileName, exename);
187-
}
188-
if (fileName.find('.') == std::string::npos)
189-
return getAddonInfo(fileName + ".py", exename);
190-
191-
if (endsWith(fileName, ".py")) {
192-
scriptFile = Path::fromNativeSeparators(getFullPath(fileName, exename));
193-
if (scriptFile.empty())
194-
return "Did not find addon " + fileName;
195-
196-
std::string::size_type pos1 = scriptFile.rfind('/');
197-
if (pos1 == std::string::npos)
198-
pos1 = 0;
199-
else
200-
pos1++;
201-
std::string::size_type pos2 = scriptFile.rfind('.');
202-
if (pos2 < pos1)
203-
pos2 = std::string::npos;
204-
name = scriptFile.substr(pos1, pos2 - pos1);
205-
206-
runScript = getFullPath("runaddon.py", exename);
207-
208-
return "";
209-
}
210-
211-
if (!endsWith(fileName, ".json"))
212-
return "Failed to open addon " + fileName;
213-
214-
std::ifstream fin(fileName);
215-
if (!fin.is_open())
216-
return "Failed to open " + fileName;
217-
picojson::value json;
218-
fin >> json;
219-
return parseAddonInfo(json, fileName, exename);
220-
}
221-
};
222-
}
223-
224110
static std::string cmdFileName(std::string f)
225111
{
226112
f = Path::toNativeSeparators(f);
@@ -1502,14 +1388,10 @@ void CppCheck::executeAddons(const std::vector<std::string>& files)
15021388
fout << f << std::endl;
15031389
}
15041390

1505-
for (const std::string &addon : mSettings.addons) {
1506-
AddonInfo addonInfo;
1507-
const std::string &failedToGetAddonInfo = addonInfo.getAddonInfo(addon, mSettings.exename);
1508-
if (!failedToGetAddonInfo.empty()) {
1509-
reportOut(failedToGetAddonInfo, Color::FgRed);
1510-
mExitCode = 1;
1511-
continue;
1512-
}
1391+
// ensure all addons have already been resolved - TODO: remove when settings are const after creation
1392+
assert(mSettings.addonInfos.size() == mSettings.addons.size());
1393+
1394+
for (const AddonInfo &addonInfo : mSettings.addonInfos) {
15131395
if (addonInfo.name != "misra" && !addonInfo.ctu && endsWith(files.back(), ".ctu-info"))
15141396
continue;
15151397

0 commit comments

Comments
 (0)