2011-08-22 18:54:23 +02:00
/*
* Cppcheck - A tool for static C / C + + code analysis
2022-02-05 11:45:17 +01:00
* Copyright ( C ) 2007 - 2022 Cppcheck team .
2011-08-22 18:54:23 +02:00
*
* 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 "suppressions.h"
2017-05-27 04:33:47 +02:00
2018-04-24 22:19:24 +02:00
# include "errorlogger.h"
2018-04-09 06:43:48 +02:00
# include "mathlib.h"
2011-08-22 18:54:23 +02:00
# include "path.h"
2020-01-24 07:06:09 +01:00
# include "utils.h"
2022-03-30 19:24:53 +02:00
# include "tokenize.h"
2011-08-22 18:54:23 +02:00
2012-07-24 21:21:05 +02:00
# include <algorithm>
2011-08-22 19:21:25 +02:00
# include <cctype> // std::isdigit, std::isalnum, etc
2022-01-27 19:03:20 +01:00
# include <cstdlib>
# include <cstring>
2020-11-04 21:01:48 +01:00
# include <functional> // std::bind, std::placeholders
2011-08-22 18:54:23 +02:00
2022-01-27 19:03:20 +01:00
# include <tinyxml2.h>
2018-08-17 08:20:39 +02:00
static bool isAcceptedErrorIdChar ( char c )
{
switch ( c ) {
case ' _ ' :
case ' - ' :
case ' . ' :
2018-08-17 19:55:21 +02:00
return true ;
2018-08-17 08:20:39 +02:00
default :
2018-08-17 19:55:21 +02:00
return std : : isalnum ( c ) ;
2018-08-17 08:20:39 +02:00
}
}
2011-08-22 18:54:23 +02:00
std : : string Suppressions : : parseFile ( std : : istream & istr )
{
// Change '\r' to '\n' in the istr
std : : string filedata ;
std : : string line ;
while ( std : : getline ( istr , line ) )
filedata + = line + " \n " ;
2012-07-24 21:21:05 +02:00
std : : replace ( filedata . begin ( ) , filedata . end ( ) , ' \r ' , ' \n ' ) ;
2011-08-22 18:54:23 +02:00
// Parse filedata..
std : : istringstream istr2 ( filedata ) ;
2011-10-13 20:53:06 +02:00
while ( std : : getline ( istr2 , line ) ) {
2011-08-22 18:54:23 +02:00
// Skip empty lines
if ( line . empty ( ) )
continue ;
// Skip comments
2019-01-12 15:21:47 +01:00
if ( line . length ( ) > 1 & & line [ 0 ] = = ' # ' )
continue ;
2011-08-22 18:54:23 +02:00
if ( line . length ( ) > = 2 & & line [ 0 ] = = ' / ' & & line [ 1 ] = = ' / ' )
continue ;
const std : : string errmsg ( addSuppressionLine ( line ) ) ;
if ( ! errmsg . empty ( ) )
return errmsg ;
}
return " " ;
}
2018-04-09 06:43:48 +02:00
std : : string Suppressions : : parseXmlFile ( const char * filename )
{
tinyxml2 : : XMLDocument doc ;
2018-05-29 13:24:48 +02:00
const tinyxml2 : : XMLError error = doc . LoadFile ( filename ) ;
2018-04-09 06:43:48 +02:00
if ( error = = tinyxml2 : : XML_ERROR_FILE_NOT_FOUND )
return " File not found " ;
2018-04-10 16:32:47 +02:00
if ( error ! = tinyxml2 : : XML_SUCCESS )
return " Failed to parse XML file " ;
2018-04-09 06:43:48 +02:00
const tinyxml2 : : XMLElement * const rootnode = doc . FirstChildElement ( ) ;
for ( const tinyxml2 : : XMLElement * e = rootnode - > FirstChildElement ( ) ; e ; e = e - > NextSiblingElement ( ) ) {
2018-04-10 16:32:47 +02:00
if ( std : : strcmp ( e - > Name ( ) , " suppress " ) ! = 0 )
2020-07-21 11:27:03 +02:00
return " Invalid suppression xml file format, expected <suppress> element but got a \" " + std : : string ( e - > Name ( ) ) + ' \" ' ;
2018-04-10 16:32:47 +02:00
Suppression s ;
for ( const tinyxml2 : : XMLElement * e2 = e - > FirstChildElement ( ) ; e2 ; e2 = e2 - > NextSiblingElement ( ) ) {
const char * text = e2 - > GetText ( ) ? e2 - > GetText ( ) : " " ;
if ( std : : strcmp ( e2 - > Name ( ) , " id " ) = = 0 )
s . errorId = text ;
else if ( std : : strcmp ( e2 - > Name ( ) , " fileName " ) = = 0 )
s . fileName = text ;
else if ( std : : strcmp ( e2 - > Name ( ) , " lineNumber " ) = = 0 )
s . lineNumber = std : : atoi ( text ) ;
else if ( std : : strcmp ( e2 - > Name ( ) , " symbolName " ) = = 0 )
s . symbolName = text ;
2020-07-21 11:27:03 +02:00
else if ( * text & & std : : strcmp ( e2 - > Name ( ) , " hash " ) = = 0 )
std : : istringstream ( text ) > > s . hash ;
2018-04-10 16:32:47 +02:00
else
2020-07-21 11:27:03 +02:00
return " Unknown suppression element \" " + std : : string ( e2 - > Name ( ) ) + " \" , expected id/fileName/lineNumber/symbolName/hash " ;
2018-04-09 06:43:48 +02:00
}
2018-04-10 16:32:47 +02:00
const std : : string err = addSuppression ( s ) ;
if ( ! err . empty ( ) )
return err ;
2018-04-09 06:43:48 +02:00
}
return " " ;
}
2020-02-23 19:49:53 +01:00
std : : vector < Suppressions : : Suppression > Suppressions : : parseMultiSuppressComment ( const std : : string & comment , std : : string * errorMessage )
2020-02-23 18:04:24 +01:00
{
std : : vector < Suppression > suppressions ;
2020-02-23 19:49:53 +01:00
// If this function is called we assume that comment starts with "cppcheck-suppress[".
const std : : string : : size_type start_position = comment . find ( " [ " ) ;
const std : : string : : size_type end_position = comment . find ( " ] " , start_position ) ;
if ( end_position = = std : : string : : npos ) {
2020-02-23 18:04:24 +01:00
if ( errorMessage & & errorMessage - > empty ( ) )
* errorMessage = " Bad multi suppression ' " + comment + " '. legal format is cppcheck-suppress[errorId, errorId symbolName=arr, ...] " ;
return suppressions ;
}
2020-02-23 19:49:53 +01:00
// parse all suppressions
for ( std : : string : : size_type pos = start_position ; pos < end_position ; ) {
const std : : string : : size_type pos1 = pos + 1 ;
pos = comment . find ( " , " , pos1 ) ;
const std : : string : : size_type pos2 = ( pos < end_position ) ? pos : end_position ;
if ( pos1 = = pos2 )
continue ;
2020-02-23 18:04:24 +01:00
Suppression s ;
2020-02-23 19:49:53 +01:00
std : : istringstream iss ( comment . substr ( pos1 , pos2 - pos1 ) ) ;
2020-02-23 18:04:24 +01:00
2020-02-23 19:49:53 +01:00
iss > > s . errorId ;
2020-02-23 18:04:47 +01:00
if ( ! iss ) {
2020-02-23 18:04:24 +01:00
if ( errorMessage & & errorMessage - > empty ( ) )
* errorMessage = " Bad multi suppression ' " + comment + " '. legal format is cppcheck-suppress[errorId, errorId symbolName=arr, ...] " ;
suppressions . clear ( ) ;
return suppressions ;
}
2020-02-23 18:04:47 +01:00
while ( iss ) {
2020-02-23 19:49:53 +01:00
std : : string word ;
2020-02-23 18:04:24 +01:00
iss > > word ;
if ( ! iss )
break ;
if ( word . find_first_not_of ( " +-*/%#; " ) = = std : : string : : npos )
break ;
2020-02-23 18:04:47 +01:00
if ( word . compare ( 0 , 11 , " symbolName= " ) = = 0 ) {
2020-02-23 19:49:53 +01:00
s . symbolName = word . substr ( 11 ) ;
2020-02-23 18:04:47 +01:00
} else {
2020-02-23 18:04:24 +01:00
if ( errorMessage & & errorMessage - > empty ( ) )
* errorMessage = " Bad multi suppression ' " + comment + " '. legal format is cppcheck-suppress[errorId, errorId symbolName=arr, ...] " ;
suppressions . clear ( ) ;
return suppressions ;
}
}
suppressions . push_back ( s ) ;
2020-02-23 19:49:53 +01:00
}
2020-02-23 18:04:24 +01:00
return suppressions ;
}
2011-08-22 18:54:23 +02:00
std : : string Suppressions : : addSuppressionLine ( const std : : string & line )
{
2020-08-20 21:49:07 +02:00
std : : istringstream lineStream ;
2018-04-09 06:43:48 +02:00
Suppressions : : Suppression suppression ;
2020-08-20 21:49:07 +02:00
// Strip any end of line comments
std : : string : : size_type endpos = std : : min ( line . find ( " # " ) , line . find ( " // " ) ) ;
if ( endpos ! = std : : string : : npos ) {
while ( endpos > 0 & & std : : isspace ( line [ endpos - 1 ] ) ) {
endpos - - ;
}
lineStream . str ( line . substr ( 0 , endpos ) ) ;
} else {
lineStream . str ( line ) ;
}
2018-04-09 06:43:48 +02:00
if ( std : : getline ( lineStream , suppression . errorId , ' : ' ) ) {
if ( std : : getline ( lineStream , suppression . fileName ) ) {
2011-08-22 18:54:23 +02:00
// If there is not a dot after the last colon in "file" then
// the colon is a separator and the contents after the colon
// is a line number..
// Get position of last colon
2018-04-09 06:43:48 +02:00
const std : : string : : size_type pos = suppression . fileName . rfind ( ' : ' ) ;
2011-08-22 18:54:23 +02:00
// if a colon is found and there is no dot after it..
if ( pos ! = std : : string : : npos & &
2018-04-09 06:43:48 +02:00
suppression . fileName . find ( ' . ' , pos ) = = std : : string : : npos ) {
2011-08-22 18:54:23 +02:00
// Try to parse out the line number
2011-10-13 20:53:06 +02:00
try {
2018-04-09 06:43:48 +02:00
std : : istringstream istr1 ( suppression . fileName . substr ( pos + 1 ) ) ;
istr1 > > suppression . lineNumber ;
2011-10-13 20:53:06 +02:00
} catch ( . . . ) {
2018-09-18 12:58:14 +02:00
suppression . lineNumber = Suppressions : : Suppression : : NO_LINE ;
2011-08-22 18:54:23 +02:00
}
2018-09-23 16:50:51 +02:00
if ( suppression . lineNumber ! = Suppressions : : Suppression : : NO_LINE ) {
2018-04-09 06:43:48 +02:00
suppression . fileName . erase ( pos ) ;
2011-08-22 18:54:23 +02:00
}
}
}
}
2018-04-09 06:43:48 +02:00
suppression . fileName = Path : : simplifyPath ( suppression . fileName ) ;
return addSuppression ( suppression ) ;
}
std : : string Suppressions : : addSuppression ( const Suppressions : : Suppression & suppression )
{
2020-11-04 21:01:48 +01:00
// Check if suppression is already in list
2020-11-06 19:50:05 +01:00
auto foundSuppression = std : : find_if ( mSuppressions . begin ( ) , mSuppressions . end ( ) ,
std : : bind ( & Suppression : : isSameParameters , & suppression , std : : placeholders : : _1 ) ) ;
2020-11-04 21:01:48 +01:00
if ( foundSuppression ! = mSuppressions . end ( ) ) {
// Update matched state of existing global suppression
if ( ! suppression . isLocal ( ) & & suppression . matched )
foundSuppression - > matched = suppression . matched ;
return " " ;
}
2018-04-09 06:43:48 +02:00
// Check that errorId is valid..
2020-07-21 11:27:03 +02:00
if ( suppression . errorId . empty ( ) & & suppression . hash = = 0 )
2018-04-09 06:43:48 +02:00
return " Failed to add suppression. No id. " ;
2020-07-15 13:03:07 +02:00
2018-04-09 06:43:48 +02:00
if ( suppression . errorId ! = " * " ) {
for ( std : : string : : size_type pos = 0 ; pos < suppression . errorId . length ( ) ; + + pos ) {
2018-08-17 08:20:39 +02:00
if ( suppression . errorId [ pos ] < 0 | | ! isAcceptedErrorIdChar ( suppression . errorId [ pos ] ) ) {
2018-04-09 06:43:48 +02:00
return " Failed to add suppression. Invalid id \" " + suppression . errorId + " \" " ;
}
if ( pos = = 0 & & std : : isdigit ( suppression . errorId [ pos ] ) ) {
return " Failed to add suppression. Invalid id \" " + suppression . errorId + " \" " ;
}
}
}
if ( ! isValidGlobPattern ( suppression . errorId ) )
return " Failed to add suppression. Invalid glob pattern ' " + suppression . errorId + " '. " ;
if ( ! isValidGlobPattern ( suppression . fileName ) )
return " Failed to add suppression. Invalid glob pattern ' " + suppression . fileName + " '. " ;
2018-06-17 08:16:37 +02:00
mSuppressions . push_back ( suppression ) ;
2011-08-22 18:54:23 +02:00
return " " ;
}
2020-11-04 21:01:48 +01:00
std : : string Suppressions : : addSuppressions ( const std : : list < Suppression > & suppressions )
{
for ( const auto & newSuppression : suppressions ) {
auto errmsg = addSuppression ( newSuppression ) ;
if ( errmsg ! = " " )
return errmsg ;
}
return " " ;
}
2018-04-09 06:43:48 +02:00
void Suppressions : : ErrorMessage : : setFileName ( const std : : string & s )
{
2018-06-17 08:19:10 +02:00
mFileName = Path : : simplifyPath ( s ) ;
2018-04-09 06:43:48 +02:00
}
2018-04-11 08:18:00 +02:00
bool Suppressions : : Suppression : : parseComment ( std : : string comment , std : : string * errorMessage )
{
if ( comment . size ( ) < 2 )
return false ;
2018-06-17 16:39:28 +02:00
if ( comment . find ( ' ; ' ) ! = std : : string : : npos )
comment . erase ( comment . find ( ' ; ' ) ) ;
2018-04-11 08:18:00 +02:00
if ( comment . find ( " // " , 2 ) ! = std : : string : : npos )
comment . erase ( comment . find ( " // " , 2 ) ) ;
if ( comment . compare ( comment . size ( ) - 2 , 2 , " */ " ) = = 0 )
comment . erase ( comment . size ( ) - 2 , 2 ) ;
std : : istringstream iss ( comment . substr ( 2 ) ) ;
std : : string word ;
iss > > word ;
if ( word ! = " cppcheck-suppress " )
return false ;
iss > > errorId ;
if ( ! iss )
return false ;
while ( iss ) {
iss > > word ;
if ( ! iss )
break ;
2018-06-09 22:50:51 +02:00
if ( word . find_first_not_of ( " +-*/%#; " ) = = std : : string : : npos )
break ;
2018-04-11 08:18:00 +02:00
if ( word . compare ( 0 , 11 , " symbolName= " ) = = 0 )
symbolName = word . substr ( 11 ) ;
else if ( errorMessage & & errorMessage - > empty ( ) )
* errorMessage = " Bad suppression attribute ' " + word + " '. You can write comments in the comment after a ; or //. Valid suppression attributes; symbolName=sym " ;
}
return true ;
}
2018-04-09 06:43:48 +02:00
bool Suppressions : : Suppression : : isSuppressed ( const Suppressions : : ErrorMessage & errmsg ) const
{
2020-07-21 11:27:03 +02:00
if ( hash > 0 & & hash ! = errmsg . hash )
2020-07-15 13:03:07 +02:00
return false ;
2018-04-09 06:43:48 +02:00
if ( ! errorId . empty ( ) & & ! matchglob ( errorId , errmsg . errorId ) )
return false ;
if ( ! fileName . empty ( ) & & ! matchglob ( fileName , errmsg . getFileName ( ) ) )
return false ;
2020-10-02 18:56:26 +02:00
if ( lineNumber ! = NO_LINE & & lineNumber ! = errmsg . lineNumber ) {
if ( ! thisAndNextLine | | lineNumber + 1 ! = errmsg . lineNumber )
return false ;
}
2018-04-09 06:43:48 +02:00
if ( ! symbolName . empty ( ) ) {
for ( std : : string : : size_type pos = 0 ; pos < errmsg . symbolNames . size ( ) ; ) {
const std : : string : : size_type pos2 = errmsg . symbolNames . find ( ' \n ' , pos ) ;
std : : string symname ;
if ( pos2 = = std : : string : : npos ) {
symname = errmsg . symbolNames . substr ( pos ) ;
pos = pos2 ;
} else {
symname = errmsg . symbolNames . substr ( pos , pos2 - pos ) ;
pos = pos2 + 1 ;
}
if ( matchglob ( symbolName , symname ) )
return true ;
}
return false ;
}
return true ;
}
bool Suppressions : : Suppression : : isMatch ( const Suppressions : : ErrorMessage & errmsg )
{
if ( ! isSuppressed ( errmsg ) )
return false ;
matched = true ;
2022-03-30 19:24:53 +02:00
checked = true ;
2018-04-09 06:43:48 +02:00
return true ;
}
std : : string Suppressions : : Suppression : : getText ( ) const
{
std : : string ret ;
if ( ! errorId . empty ( ) )
ret = errorId ;
if ( ! fileName . empty ( ) )
ret + = " fileName= " + fileName ;
2018-09-23 16:50:51 +02:00
if ( lineNumber ! = NO_LINE )
2018-04-09 06:43:48 +02:00
ret + = " lineNumber= " + MathLib : : toString ( lineNumber ) ;
if ( ! symbolName . empty ( ) )
ret + = " symbolName= " + symbolName ;
2020-07-21 11:27:03 +02:00
if ( hash > 0 )
ret + = " hash= " + MathLib : : toString ( hash ) ;
2018-04-09 06:43:48 +02:00
if ( ret . compare ( 0 , 1 , " " ) = = 0 )
return ret . substr ( 1 ) ;
return ret ;
}
bool Suppressions : : isSuppressed ( const Suppressions : : ErrorMessage & errmsg )
{
const bool unmatchedSuppression ( errmsg . errorId = = " unmatchedSuppression " ) ;
2018-07-15 14:51:33 +02:00
for ( Suppression & s : mSuppressions ) {
2018-04-09 06:43:48 +02:00
if ( unmatchedSuppression & & s . errorId ! = errmsg . errorId )
continue ;
if ( s . isMatch ( errmsg ) )
return true ;
}
return false ;
}
2018-05-11 09:01:08 +02:00
bool Suppressions : : isSuppressedLocal ( const Suppressions : : ErrorMessage & errmsg )
{
const bool unmatchedSuppression ( errmsg . errorId = = " unmatchedSuppression " ) ;
2018-07-15 14:51:33 +02:00
for ( Suppression & s : mSuppressions ) {
2018-05-11 09:01:08 +02:00
if ( ! s . isLocal ( ) )
continue ;
if ( unmatchedSuppression & & s . errorId ! = errmsg . errorId )
continue ;
if ( s . isMatch ( errmsg ) )
return true ;
}
return false ;
}
2019-06-29 07:53:32 +02:00
void Suppressions : : dump ( std : : ostream & out ) const
2018-04-24 22:19:24 +02:00
{
out < < " <suppressions> " < < std : : endl ;
2018-06-17 08:16:37 +02:00
for ( const Suppression & suppression : mSuppressions ) {
2018-04-24 22:19:24 +02:00
out < < " <suppression " ;
out < < " errorId= \" " < < ErrorLogger : : toxml ( suppression . errorId ) < < ' " ' ;
2018-04-24 22:42:25 +02:00
if ( ! suppression . fileName . empty ( ) )
2018-04-24 22:19:24 +02:00
out < < " fileName= \" " < < ErrorLogger : : toxml ( suppression . fileName ) < < ' " ' ;
2018-09-23 16:50:51 +02:00
if ( suppression . lineNumber ! = Suppression : : NO_LINE )
2018-04-24 22:19:24 +02:00
out < < " lineNumber= \" " < < suppression . lineNumber < < ' " ' ;
if ( ! suppression . symbolName . empty ( ) )
out < < " symbolName= \" " < < ErrorLogger : : toxml ( suppression . symbolName ) < < ' \" ' ;
2020-07-21 11:27:03 +02:00
if ( suppression . hash > 0 )
out < < " hash= \" " < < suppression . hash < < ' \" ' ;
2018-04-24 22:19:24 +02:00
out < < " /> " < < std : : endl ;
}
out < < " </suppressions> " < < std : : endl ;
}
2018-04-09 06:43:48 +02:00
std : : list < Suppressions : : Suppression > Suppressions : : getUnmatchedLocalSuppressions ( const std : : string & file , const bool unusedFunctionChecking ) const
{
2020-07-29 12:13:21 +02:00
std : : string tmpFile = Path : : simplifyPath ( file ) ;
2018-04-09 06:43:48 +02:00
std : : list < Suppression > result ;
2018-07-15 14:51:33 +02:00
for ( const Suppression & s : mSuppressions ) {
2022-03-30 19:24:53 +02:00
if ( s . matched | | ( ( s . lineNumber ! = Suppression : : NO_LINE ) & & ! s . checked ) )
2018-04-09 06:43:48 +02:00
continue ;
2020-07-21 11:27:03 +02:00
if ( s . hash > 0 )
2020-07-15 13:03:07 +02:00
continue ;
2018-04-09 06:43:48 +02:00
if ( ! unusedFunctionChecking & & s . errorId = = " unusedFunction " )
continue ;
2020-07-29 12:13:21 +02:00
if ( tmpFile . empty ( ) | | ! s . isLocal ( ) | | s . fileName ! = tmpFile )
2018-04-09 06:43:48 +02:00
continue ;
result . push_back ( s ) ;
}
return result ;
}
std : : list < Suppressions : : Suppression > Suppressions : : getUnmatchedGlobalSuppressions ( const bool unusedFunctionChecking ) const
{
std : : list < Suppression > result ;
2018-07-15 14:51:33 +02:00
for ( const Suppression & s : mSuppressions ) {
2022-03-30 19:24:53 +02:00
if ( s . matched | | ( ( s . lineNumber ! = Suppression : : NO_LINE ) & & ! s . checked ) )
2018-04-09 06:43:48 +02:00
continue ;
2020-07-21 11:27:03 +02:00
if ( s . hash > 0 )
2020-07-15 13:03:07 +02:00
continue ;
2018-04-09 06:43:48 +02:00
if ( ! unusedFunctionChecking & & s . errorId = = " unusedFunction " )
continue ;
2018-05-11 09:01:08 +02:00
if ( s . isLocal ( ) )
2018-04-09 06:43:48 +02:00
continue ;
result . push_back ( s ) ;
}
return result ;
}
2020-11-04 21:01:48 +01:00
2021-08-21 22:00:45 +02:00
const std : : list < Suppressions : : Suppression > & Suppressions : : getSuppressions ( ) const
2020-11-04 21:01:48 +01:00
{
return mSuppressions ;
}
2022-03-30 19:24:53 +02:00
void Suppressions : : markUnmatchedInlineSuppressionsAsChecked ( const Tokenizer & tokenizer ) {
int currLineNr = - 1 ;
int currFileIdx = - 1 ;
for ( const Token * tok = tokenizer . tokens ( ) ; tok ; tok = tok - > next ( ) ) {
if ( currFileIdx ! = tok - > fileIndex ( ) | | currLineNr ! = tok - > linenr ( ) ) {
currLineNr = tok - > linenr ( ) ;
currFileIdx = tok - > fileIndex ( ) ;
for ( auto & suppression : mSuppressions ) {
if ( ! suppression . checked & & ( suppression . lineNumber = = currLineNr ) & & ( suppression . fileName = = tokenizer . list . file ( tok ) ) ) {
suppression . checked = true ;
}
}
}
}
}