2011-08-22 18:54:23 +02:00
/*
* Cppcheck - A tool for static C / C + + code analysis
2018-06-10 22:07:21 +02:00
* Copyright ( C ) 2007 - 2018 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"
2018-04-09 06:43:48 +02:00
# include <tinyxml2.h>
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
2017-05-27 04:33:47 +02:00
# include <stack>
# include <sstream>
# include <utility>
2011-08-22 18:54:23 +02:00
2018-04-24 22:19:24 +02:00
class ErrorLogger ;
2018-04-09 06:43:48 +02:00
static bool isValidGlobPattern ( const std : : string & pattern )
{
for ( std : : string : : const_iterator i = pattern . begin ( ) ; i ! = pattern . end ( ) ; + + i ) {
if ( * i = = ' * ' | | * i = = ' ? ' ) {
std : : string : : const_iterator j = i + 1 ;
if ( j ! = pattern . end ( ) & & ( * j = = ' * ' | | * j = = ' ? ' ) ) {
return false ;
}
}
}
return true ;
}
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
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 )
return " Invalid suppression xml file format, expected <suppress> element but got a < " + std : : string ( e - > Name ( ) ) + ' > ' ;
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 ;
else
return " Unknown suppression element < " + std : : string ( e2 - > Name ( ) ) + " >, expected <id>/<fileName>/<lineNumber>/<symbolName> " ;
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 " " ;
}
2011-08-22 18:54:23 +02:00
std : : string Suppressions : : addSuppressionLine ( const std : : string & line )
{
std : : istringstream lineStream ( line ) ;
2018-04-09 06:43:48 +02:00
Suppressions : : Suppression suppression ;
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-18 12:58:14 +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 )
{
// Check that errorId is valid..
if ( suppression . errorId . empty ( ) ) {
return " Failed to add suppression. No id. " ;
}
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 " " ;
}
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
{
if ( ! errorId . empty ( ) & & ! matchglob ( errorId , errmsg . errorId ) )
return false ;
if ( ! fileName . empty ( ) & & ! matchglob ( fileName , errmsg . getFileName ( ) ) )
return false ;
2018-09-18 12:58:14 +02:00
if ( lineNumber > NO_LINE & & lineNumber ! = errmsg . lineNumber )
2018-04-09 06:43:48 +02:00
return false ;
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 ;
return true ;
}
std : : string Suppressions : : Suppression : : getText ( ) const
{
std : : string ret ;
if ( ! errorId . empty ( ) )
ret = errorId ;
if ( ! fileName . empty ( ) )
ret + = " fileName= " + fileName ;
2018-09-18 12:58:14 +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 ;
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 ;
}
2018-04-24 22:19:24 +02:00
void Suppressions : : dump ( std : : ostream & out )
{
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-18 12:58:14 +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 ) < < ' \" ' ;
out < < " /> " < < std : : endl ;
}
out < < " </suppressions> " < < std : : endl ;
}
2018-05-11 09:01:08 +02:00
# include <iostream>
2018-04-09 06:43:48 +02:00
std : : list < Suppressions : : Suppression > Suppressions : : getUnmatchedLocalSuppressions ( const std : : string & file , const bool unusedFunctionChecking ) const
{
std : : list < Suppression > result ;
2018-07-15 14:51:33 +02:00
for ( const Suppression & s : mSuppressions ) {
2018-04-09 06:43:48 +02:00
if ( s . matched )
continue ;
if ( ! unusedFunctionChecking & & s . errorId = = " unusedFunction " )
continue ;
2018-05-11 09:01:08 +02:00
if ( file . empty ( ) | | ! s . isLocal ( ) | | s . fileName ! = file )
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 ) {
2018-04-09 06:43:48 +02:00
if ( s . matched )
continue ;
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 ;
}
bool Suppressions : : matchglob ( const std : : string & pattern , const std : : string & name )
2011-08-22 18:54:23 +02:00
{
const char * p = pattern . c_str ( ) ;
const char * n = name . c_str ( ) ;
std : : stack < std : : pair < const char * , const char * > > backtrack ;
2011-10-13 20:53:06 +02:00
for ( ; ; ) {
2011-08-22 18:54:23 +02:00
bool matching = true ;
2011-10-13 20:53:06 +02:00
while ( * p ! = ' \0 ' & & matching ) {
switch ( * p ) {
2011-08-22 18:54:23 +02:00
case ' * ' :
// Step forward until we match the next character after *
2018-04-09 18:59:18 +02:00
while ( * n ! = ' \0 ' & & * n ! = p [ 1 ] ) {
2011-08-22 18:54:23 +02:00
n + + ;
}
2018-04-09 18:59:18 +02:00
if ( * n ! = ' \0 ' ) {
2011-08-22 18:54:23 +02:00
// If this isn't the last possibility, save it for later
backtrack . push ( std : : make_pair ( p , n ) ) ;
}
break ;
case ' ? ' :
// Any character matches unless we're at the end of the name
2018-04-09 18:59:18 +02:00
if ( * n ! = ' \0 ' ) {
2011-08-22 18:54:23 +02:00
n + + ;
2011-10-13 20:53:06 +02:00
} else {
2011-08-22 18:54:23 +02:00
matching = false ;
}
break ;
default :
// Non-wildcard characters match literally
2011-10-13 20:53:06 +02:00
if ( * n = = * p ) {
2011-08-22 18:54:23 +02:00
n + + ;
2018-04-09 06:43:48 +02:00
} else if ( * n = = ' \\ ' & & * p = = ' / ' ) {
n + + ;
} else if ( * n = = ' / ' & & * p = = ' \\ ' ) {
n + + ;
2011-10-13 20:53:06 +02:00
} else {
2011-08-22 18:54:23 +02:00
matching = false ;
}
break ;
}
p + + ;
}
// If we haven't failed matching and we've reached the end of the name, then success
2011-10-13 20:53:06 +02:00
if ( matching & & * n = = ' \0 ' ) {
2011-08-22 18:54:23 +02:00
return true ;
}
2013-02-10 07:43:09 +01:00
// If there are no other paths to try, then fail
2011-10-13 20:53:06 +02:00
if ( backtrack . empty ( ) ) {
2011-08-22 18:54:23 +02:00
return false ;
}
// Restore pointers from backtrack stack
p = backtrack . top ( ) . first ;
n = backtrack . top ( ) . second ;
backtrack . pop ( ) ;
// Advance name pointer by one because the current position didn't work
n + + ;
}
}