2525
2626#include < cstring>
2727#include < exception>
28+ #include < iostream>
2829#include < map>
2930#include < sstream>
3031#include < utility>
@@ -82,29 +83,56 @@ void AnalyzerInformation::close()
8283 }
8384}
8485
85- bool AnalyzerInformation::skipAnalysis (const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list<ErrorMessage> &errors)
86+ bool AnalyzerInformation::skipAnalysis (const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list<ErrorMessage> &errors, bool debug )
8687{
8788 const tinyxml2::XMLElement * const rootNode = analyzerInfoDoc.FirstChildElement ();
88- if (rootNode == nullptr )
89+ if (rootNode == nullptr ) {
90+ if (debug)
91+ std::cout << " discarding cached result - no root node found" << std::endl;
8992 return false ;
93+ }
9094
91- const char *attr = rootNode->Attribute (" hash" );
92- if (!attr || attr != std::to_string (hash))
95+ if (strcmp (rootNode->Name (), " analyzerinfo" ) != 0 ) {
96+ if (debug)
97+ std::cout << " discarding cached result - unexpected root node" << std::endl;
9398 return false ;
99+ }
94100
95- // Check for invalid license error or internal error, in which case we should retry analysis
96- for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement (); e; e = e->NextSiblingElement ()) {
97- if (std::strcmp (e->Name (), " error" ) == 0 &&
98- (e->Attribute (" id" , " premium-invalidLicense" ) ||
99- e->Attribute (" id" , " premium-internalError" ) ||
100- e->Attribute (" id" , " internalError" )
101- ))
102- return false ;
101+ const char * const attr = rootNode->Attribute (" hash" );
102+ if (!attr) {
103+ if (debug)
104+ std::cout << " discarding cached result - no 'hash' attribute found" << std::endl;
105+ return false ;
106+ }
107+ if (attr != std::to_string (hash)) {
108+ if (debug)
109+ std::cout << " discarding cached result - hash mismatch" << std::endl;
110+ return false ;
103111 }
104112
105113 for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement (); e; e = e->NextSiblingElement ()) {
106- if (std::strcmp (e->Name (), " error" ) == 0 )
107- errors.emplace_back (e);
114+ if (std::strcmp (e->Name (), " error" ) != 0 )
115+ continue ;
116+
117+ // TODO: discarding results on internalError doesn't make sense since that won't fix itself
118+ // Check for invalid license error or internal error, in which case we should retry analysis
119+ static const std::array<const char *, 3 > s_ids{
120+ " premium-invalidLicense" ,
121+ " premium-internalError" ,
122+ " internalError"
123+ };
124+ for (const auto * id : s_ids)
125+ {
126+ // cppcheck-suppress useStlAlgorithm
127+ if (e->Attribute (" id" , id)) {
128+ if (debug)
129+ std::cout << " discarding cached result - '" << id << " ' encountered" << std::endl;
130+ errors.clear ();
131+ return false ;
132+ }
133+ }
134+
135+ errors.emplace_back (e);
108136 }
109137
110138 return true ;
@@ -138,27 +166,43 @@ std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir
138166 filename = sourcefile;
139167 else
140168 filename = sourcefile.substr (pos + 1 );
169+ // TODO: is this correct? the above code will return files ending in '.aN'. Also does not consider the ID
141170 return Path::join (buildDir, std::move (filename)) + " .analyzerinfo" ;
142171}
143172
144- bool AnalyzerInformation::analyzeFile (const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list<ErrorMessage> &errors)
173+ bool AnalyzerInformation::analyzeFile (const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list<ErrorMessage> &errors, bool debug )
145174{
175+ if (mOutputStream .is_open ())
176+ throw std::runtime_error (" analyzer information file is already open" );
177+
146178 if (buildDir.empty () || sourcefile.empty ())
147179 return true ;
148- close ();
149180
150181 const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile (buildDir,sourcefile,cfg,fsFileId);
151182
152- tinyxml2::XMLDocument analyzerInfoDoc;
153- const tinyxml2::XMLError xmlError = analyzerInfoDoc.LoadFile (analyzerInfoFile.c_str ());
154- if (xmlError == tinyxml2::XML_SUCCESS && skipAnalysis (analyzerInfoDoc, hash, errors))
155- return false ;
183+ {
184+ tinyxml2::XMLDocument analyzerInfoDoc;
185+ const tinyxml2::XMLError xmlError = analyzerInfoDoc.LoadFile (analyzerInfoFile.c_str ());
186+ if (xmlError == tinyxml2::XML_SUCCESS) {
187+ if (skipAnalysis (analyzerInfoDoc, hash, errors, debug)) {
188+ if (debug)
189+ std::cout << " skipping analysis - loaded " << errors.size () << " cached finding(s) from '" << analyzerInfoFile << " '" << std::endl;
190+ return false ;
191+ }
192+ }
193+ else if (xmlError != tinyxml2::XML_ERROR_FILE_NOT_FOUND) {
194+ if (debug)
195+ std::cout << " discarding cached result - failed to load '" << analyzerInfoFile << " ' (" << tinyxml2::XMLDocument::ErrorIDToName (xmlError) << " )" << std::endl;
196+ }
197+ else if (debug)
198+ std::cout << " no cached result '" << analyzerInfoFile << " ' found" << std::endl;
199+ }
156200
157201 mOutputStream .open (analyzerInfoFile);
158- if (mOutputStream .is_open ()) {
159- mOutputStream << " <?xml version= \" 1.0 \" ?> \n " ;
160- mOutputStream << " <analyzerinfo hash =\" " << hash << " \" >\n " ;
161- }
202+ if (! mOutputStream .is_open ())
203+ throw std::runtime_error ( " failed to open ' " + analyzerInfoFile + " ' " ) ;
204+ mOutputStream << " <?xml version =\" 1.0 \" ? >\n " ;
205+ mOutputStream << " <analyzerinfo hash= \" " << hash << " \" > \n " ;
162206
163207 return true ;
164208}
@@ -175,6 +219,7 @@ void AnalyzerInformation::setFileInfo(const std::string &check, const std::strin
175219 mOutputStream << " <FileInfo check=\" " << check << " \" >\n " << fileInfo << " </FileInfo>\n " ;
176220}
177221
222+ // TODO: report detailed errors?
178223bool AnalyzerInformation::Info::parse (const std::string& filesTxtLine) {
179224 const std::string::size_type sep1 = filesTxtLine.find (sep);
180225 if (sep1 == std::string::npos)
@@ -202,37 +247,58 @@ bool AnalyzerInformation::Info::parse(const std::string& filesTxtLine) {
202247 return true ;
203248}
204249
205- // TODO: bail out on unexpected data
206- void AnalyzerInformation::processFilesTxt (const std::string& buildDir, const std::function<void (const char * checkattr, const tinyxml2::XMLElement* e, const Info& filesTxtInfo)>& handler)
250+ std::string AnalyzerInformation::processFilesTxt (const std::string& buildDir, const std::function<void (const char * checkattr, const tinyxml2::XMLElement* e, const Info& filesTxtInfo)>& handler, bool debug)
207251{
208252 const std::string filesTxt (buildDir + " /files.txt" );
209253 std::ifstream fin (filesTxt.c_str ());
210254 std::string filesTxtLine;
211255 while (std::getline (fin, filesTxtLine)) {
212256 AnalyzerInformation::Info filesTxtInfo;
213- if (!filesTxtInfo.parse (filesTxtLine)) {
214- return ;
215- }
257+ if (!filesTxtInfo.parse (filesTxtLine))
258+ return " failed to parse '" + filesTxtLine + " ' from '" + filesTxt + " '" ;
259+
260+ if (filesTxtInfo.afile .empty ())
261+ return " empty afile from '" + filesTxt + " '" ;
216262
217263 const std::string xmlfile = buildDir + ' /' + filesTxtInfo.afile ;
218264
219265 tinyxml2::XMLDocument doc;
220266 const tinyxml2::XMLError error = doc.LoadFile (xmlfile.c_str ());
267+ if (error == tinyxml2::XML_ERROR_FILE_NOT_FOUND) {
268+ /* FIXME: this can currently not be reported as an error because:
269+ * - --clang does not generate any analyzer information - see #14456
270+ * - markup files might not generate analyzer information
271+ * - files with preprocessor errors might not generate analyzer information
272+ */
273+ if (debug)
274+ std::cout << " '" + xmlfile + " ' from '" + filesTxt + " ' not found" ;
275+ continue ;
276+ }
277+
221278 if (error != tinyxml2::XML_SUCCESS)
222- return ;
279+ return " failed to load ' " + xmlfile + " ' from ' " + filesTxt + " ' " ;
223280
224281 const tinyxml2::XMLElement * const rootNode = doc.FirstChildElement ();
225282 if (rootNode == nullptr )
226- return ;
283+ return " no root node found in '" + xmlfile + " ' from '" + filesTxt + " '" ;
284+
285+ if (strcmp (rootNode->Name (), " analyzerinfo" ) != 0 )
286+ return " unexpected root node in '" + xmlfile + " ' from '" + filesTxt + " '" ;
227287
228288 for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement (); e; e = e->NextSiblingElement ()) {
229289 if (std::strcmp (e->Name (), " FileInfo" ) != 0 )
230290 continue ;
231291 const char *checkattr = e->Attribute (" check" );
232- if (checkattr == nullptr )
292+ if (checkattr == nullptr ) {
293+ if (debug)
294+ std::cout << " 'check' attribute missing in 'FileInfo' in '" << xmlfile << " ' from '" << filesTxt + " '" ;
233295 continue ;
296+ }
234297 handler (checkattr, e, filesTxtInfo);
235298 }
236299 }
300+
301+ // TODO: error on empty file?
302+ return " " ;
237303}
238304
0 commit comments