diff --git a/tools/reduce.cpp b/tools/reduce.cpp deleted file mode 100644 index 01d78ee7f..000000000 --- a/tools/reduce.cpp +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2019 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 "cppcheck.h" -#include "mathlib.h" -#include "path.h" - -#include -#include -#include -#include -#include -#include - -class ReduceSettings : public Settings { -public: - ReduceSettings() : filename(0), linenr(0), hang(false), maxtime(0) { } - - const char *filename; - std::size_t linenr; - bool hang; - unsigned int maxtime; -}; - -class CppcheckExecutor : public ErrorLogger { -private: - CppCheck cppcheck; - std::string pattern; - bool foundLine; - std::time_t stopTime; - -public: - explicit CppcheckExecutor(const ReduceSettings & settings) - : ErrorLogger() - , cppcheck(*this, false) - , foundLine(false) - , stopTime(0) { - - if (!settings.hang) - pattern = ":" + MathLib::toString(settings.linenr) + "]"; - - cppcheck.settings() = settings; - } - - bool run(const char filename[], unsigned int maxtime) { - foundLine = false; - stopTime = std::time(0) + maxtime; - cppcheck.check(filename); - return foundLine; - } - - void reportOut(const std::string &/*outmsg*/) override { } - void reportErr(const ErrorLogger::ErrorMessage &msg) override { - if (!pattern.empty() && msg.toString(false).find(pattern) != std::string::npos) { - foundLine = true; - cppcheck.terminate(); - } - } - void reportProgress(const std::string &/*filename*/, const char /*stage*/[], const std::size_t /*value*/) override { - if (std::time(0) > stopTime) { - if (pattern.empty()) - foundLine = true; - else - std::cerr << "timeout. You might want to use a longer --maxtime timeout" << std::endl; - std::cout << "terminate" << std::endl; - cppcheck.terminate(); - } - } -}; - -static bool test(const ReduceSettings &settings, const std::vector &filedata, const std::size_t line1, const std::size_t line2) -{ - std::string path(settings.filename); - if (path.find_first_of("\\/") != std::string::npos) - path = path.erase(1 + path.find_last_of("\\/")); - else - path.clear(); - - const std::string tempfilename(path + "__temp__" + std::strrchr(settings.filename,'.')); - std::ofstream fout(tempfilename.c_str()); - for (std::size_t i = 0; i < filedata.size(); i++) - fout << ((i>=line1 && i<=line2) ? "" : filedata[i]) << std::endl; - fout.close(); - - CppcheckExecutor cppcheck(settings); - return cppcheck.run(tempfilename.c_str(), settings.maxtime); -} - -static bool test(const ReduceSettings &settings, const std::vector &filedata, const std::size_t line) -{ - return test(settings, filedata, line, line); -} - -#ifdef GDB_HELPERS -static void printstr(const std::vector &filedata, int i1, int i2) -{ - std::cout << filedata.size(); - for (int i = i1; i < i2; ++i) - std::cout << i << ":" << filedata[i] << std::endl; -} -#endif - -static char getEndChar(const std::string &line) -{ - std::size_t pos = line.find_last_not_of(" \t"); - return (pos == std::string::npos) ? '\0' : line[pos]; -} - -static std::vector readfile(const std::string &filename) -{ - std::vector filedata; - std::ifstream fin(filename.c_str()); - std::string line; - bool blockComment = false; - while (std::getline(fin,line)) { - // replace various space characters with space - for (std::string::size_type pos = 0; pos < line.size(); ++pos) { - if (line[pos] & 0x80 || std::isspace(line[pos])) - line[pos] = ' '; - } - - // remove block comments TODO: Handle /* inside strings - if (blockComment) { - if (line.find("*/") == std::string::npos) - line.clear(); - else { - if (line.find("*/") + 2U == line.size()) - line.clear(); - else - line = line.substr(line.find("*/") + 2U); - blockComment = false; - } - } - while (!blockComment && line.find("/*") != std::string::npos) { - std::string::size_type pos = line.find("/*"); - if (line.find("*/",pos) == std::string::npos) { - blockComment = true; - if (pos==0) - line.clear(); - else - line = line.substr(0,pos); - } else { - blockComment = false; - line = line.erase(pos, 2U + line.find("*/", pos) - pos); - } - } - - // Remove // comments - if (line.find("//") != std::string::npos) - line = line.substr(0, line.find("//")); - - // empty line - if (line.find_first_not_of(" ") == std::string::npos) - line.clear(); - else { - const std::string::size_type pos = line.find_first_not_of(" "); - - // remove spaces before leading # - if (line[pos]=='#') - line = line.substr(pos); - } - - // remove trailing spaces - while (!line.empty() && std::isspace(line[line.size()-1U])) - line = line.substr(0, line.size() - 1U); - - filedata.push_back(line); - } - - // put function declarations in a single line.. - for (unsigned int linenr = 0U; linenr+1U < filedata.size(); ++linenr) { - // Does this look like start of a function declaration? - if (filedata[linenr].empty() || - !std::isalpha(filedata[linenr][0U]) || - getEndChar(filedata[linenr]) != ',' || - filedata[linenr].find("(") == std::string::npos || - filedata[linenr].find(")") != std::string::npos) - continue; - - // Where does function declaration end? - unsigned int linenr2 = linenr + 1U; - while (linenr2 < filedata.size() && - getEndChar(filedata[linenr2]) == ',' && - filedata[linenr2].find("(") == std::string::npos && - filedata[linenr2].find(")") == std::string::npos) - ++linenr2; - - // If function declaration looks correct.. simplify it - if (linenr2 < filedata.size() && - getEndChar(filedata[linenr2]) == ';' && - filedata[linenr2].find("(") == std::string::npos && - filedata[linenr2].size() > 2U && - filedata[linenr2].find(")") == filedata[linenr2].size() - 2U) { - std::string code; - for (unsigned int i = linenr; i <= linenr2; i++) { - code = code + filedata[i]; - filedata[i].clear(); - } - filedata[linenr] = code; - } - } - - // put #define statements in a single line.. - for (unsigned int linenr = 0U; linenr+1U < filedata.size(); ++linenr) { - // is this a multiline #define statement? - if (filedata[linenr].compare(0,8,"#define ")!=0 || getEndChar(filedata[linenr])!='\\') - continue; - - // where does statement end? - unsigned int linenr2 = linenr + 1U; - while (linenr2 < filedata.size() && getEndChar(filedata[linenr2]) == '\\') - ++linenr2; - - // simplify - if (linenr2 < filedata.size()) { - std::string code; - for (unsigned int i = linenr; i <= linenr2; i++) { - code = code + filedata[i].substr(0,filedata[i].size() - 1U); - filedata[i].clear(); - } - filedata[linenr] = code; - } - } - - return filedata; -} - -static bool removeMacrosInGlobalScope(const ReduceSettings &settings, std::vector &filedata) -{ - bool changed = false; - - // Remove macros in global scope.. - for (std::size_t i = 0; i < filedata.size(); i++) { - const std::string line = (i==0U&&filedata.empty()) ? std::string(";") : filedata[i]; - if (line.empty()) - continue; - - const char startChar = line[0]; - const char endChar = line[line.size() - 1U]; - - bool decl = bool(!std::isspace(startChar) && (endChar=='}' || endChar==';')); - while (decl) { - decl = false; // might be set to true below - std::size_t pos = i + 1U; - while (pos < filedata.size() && filedata[pos].empty()) - ++pos; - if (pos >= filedata.size()) - break; - const std::string &s = filedata[pos]; // possible macro : make sure it matches '[A-Z0-9_]+\(.*\)' - std::string::size_type si = 0; - while (si < s.size() && ((s[si]>='A' && s[si]<='Z') || (i>0 && s[si]>='0' && s[si]<='9') || s[si]=='_')) - si++; - while (si < s.size() && std::isspace(s[si])) - si++; - if (si == 0U || si >= s.size() || s[si] != '(') - break; - si++; - unsigned int parlevel = 1; - while (si < s.size() && parlevel >= 1U) { - if (s[si] == '(') - ++parlevel; - else if (s[si] == ')') - --parlevel; - si++; - } - if (!(parlevel == 0U && si == s.size())) - break; - if (test(settings, filedata, pos)) { - decl = true; - filedata[pos].clear(); - std::cout << "removed declaration at line " << pos << std::endl; - changed = true; - } else { - std::cout << "kept declaration at line " << pos << std::endl; - } - } - } - - return changed; -} - -static bool removeBlocksOfCode(const ReduceSettings &settings, std::vector &filedata) -{ - bool changed = false; - - // Remove blocks of code.. - for (std::size_t i = 0; i < filedata.size(); i++) { - const std::string line = (i==0U&&filedata.empty()) ? std::string(";") : filedata[i]; - if (line.empty()) - continue; - - const char startChar = line[0]; - const char endChar = line[line.size() - 1U]; - - // some kind of single line declaration - if (std::isalpha(startChar) && endChar==';') { - if (test(settings, filedata, i)) { - filedata[i].clear(); - std::cout << "removed declaration at line " << i << std::endl; - changed = true; - } else { - std::cout << "kept declaration at line " << i << std::endl; - } - } - - // remove a function body below a '}' - bool decl = bool(!std::isspace(startChar) && (endChar=='}' || endChar==';')); - while (decl) { - decl = false; // might be set to true below - std::size_t pos = ++i; - while (pos < filedata.size() && filedata[pos].empty()) - ++pos; - if ((pos+2U < filedata.size()) && (std::isalpha(filedata[pos].at(0)))) { - - // does code block start with "{" - std::size_t pos2 = pos; - - // struct X { .. - if (filedata[pos].find_first_of("();}") == std::string::npos && filedata[pos].at(filedata[pos].size()-1U) == '{') { - - } - - // function declaration .. - else { - if (filedata[pos].find("(") != std::string::npos && filedata[pos].find(")")==std::string::npos) { - ++pos2; - while (pos2+2U < filedata.size() && !filedata[pos2].empty() && filedata[pos2].find_first_of("(){}") == std::string::npos) - ++pos2; - if (filedata[pos2].find_first_of("({}")!=std::string::npos || filedata[pos2].find(")") == std::string::npos) - break; - } - pos2++; - if (pos2 < filedata.size() && !filedata[pos2].empty() && filedata[pos2].at(filedata[pos2].find_first_not_of(" ")) == ':') { - pos2++; - while (pos2 < filedata.size() && !filedata[pos2].empty() && filedata[pos2].at(filedata[pos2].find_first_not_of(" ")) == ',') - pos2++; - } - if (pos2+2U >= filedata.size() || filedata[pos2] != "{") - break; - } - pos2++; - - // find end of block.. - int level = 0; - while ((pos2 < filedata.size()) && (filedata[pos2].empty() || std::isspace(filedata[pos2].at(0)) || (std::isalpha(filedata[pos2].at(0)) && filedata[pos2].at(filedata[pos2].size()-1U) == ':') || filedata[pos2].compare(0,3,"#if")==0 || filedata[pos2].compare(0,3,"#el")==0 || filedata[pos2]=="#endif")) { - if (filedata[pos2].compare(0,3,"#if") == 0) - ++level; - else if (filedata[pos2] == "#endif") - --level; - ++pos2; - } - if (level != 0) - break; - - // does block of code end with a '}' - if ((pos2 < filedata.size()) && (filedata[pos2] == "}" || filedata[pos2] == "};")) { - if (test(settings, filedata, pos, pos2)) { - for (i = pos; i <= pos2; i++) - filedata[i].clear(); - std::cout << "removed block of code at lines " << pos << "-" << pos2 << std::endl; - decl = true; - changed = true; - } else { - std::cout << "kept block of code at lines " << pos << "-" << pos2 << std::endl; - } - } - } - } - } - return changed; -} - -static bool removeClassAndStructMembers(const ReduceSettings &settings, std::vector &filedata) -{ - bool changed = false; - - // remove class and struct members - for (std::size_t i = 0; i + 2U < filedata.size(); i++) { - if ((filedata[i].compare(0,6,"class ")==0 || filedata[i].compare(0,7,"struct ")==0) && filedata[i].find(";")==std::string::npos && filedata[i+1]=="{") { - bool decl = true; - for (std::size_t pos = i+2U; pos < filedata.size(); pos++) { - const std::string line = filedata[pos]; - if (line.empty()) - continue; - - // count { and } - unsigned int c1=0, c2=0; - for (std::string::size_type c = 0; c < line.size(); c++) { - if (line[c] == '{') - ++c1; - else if (line[c] == '}') - ++c2; - } - if (c2>0 && (c1!=1 || c2!=1)) - break; - - const char endChar = line[line.size() - 1U]; - - if (decl && (endChar == ';' || (c1==1 && c2==1 && endChar=='}'))) { - if (test(settings, filedata, pos)) { - std::cout << "removed struct/class declaration at line " << pos << std::endl; - filedata[pos].clear(); - changed = true; - } else { - std::cout << "kept struct/class declaration at line " << pos << std::endl; - } - } - - if (line[0] != '#') { - if (decl && std::isalpha(line[0]) && endChar == ':') { - decl = true; - for (std::string::size_type linepos = 0U; linepos+1U < line.size(); ++linepos) - decl &= (std::isspace(line[linepos]) || std::isalpha(line[linepos])); - } else - decl = bool(endChar == ';' || endChar == '}'); - } - } - } - } - - return changed; -} - -static bool removeIfEndIf(const ReduceSettings &settings, std::vector &filedata) -{ - bool changed = false; - - // #if - #endif - for (std::size_t i = 0; i < filedata.size(); ++i) { - while (filedata[i].compare(0,3,"#if") == 0) { - std::size_t pos2 = i + 1; - while (pos2 < filedata.size() && filedata[pos2].empty()) - ++pos2; - if (pos2 < filedata.size() && filedata[pos2] == "#endif") { - if (test(settings, filedata, i, pos2)) { - std::cout << "Removed #if - #endif block at lines " << i << "-" << pos2 << std::endl; - filedata[i].clear(); - filedata[pos2].clear(); - i = 0; - changed = true; - } else { - std::cout << "Kept #if - #endif block at lines " << i << "-" << pos2 << std::endl; - break; - } - } else { - break; - } - } - } - - // #ifndef UNUSED_ID - for (std::size_t i = 0; i < filedata.size(); ++i) { - if (filedata[i].compare(0,8,"#ifndef ") == 0) { - bool erase = true; - bool def = false; - const std::string id(filedata[i].substr(8)); - for (std::size_t i2 = 0; i2 < filedata.size(); i2++) { - if (i2 == i) - continue; - if (filedata[i2].find(id) != std::string::npos) { - if (!def && filedata[i2].compare(0,8,"#define ")==0) - def = true; - else - erase = false; - } - } - if (erase) { - unsigned int level = 0; - for (std::size_t i2 = i + 1U; i2 < filedata.size(); i2++) { - if (filedata[i2].compare(0,3,"#if")==0) - ++level; - else if (filedata[i2] == "#else") - break; - else if (filedata[i2] == "#endif") { - if (level > 0) - --level; - else { - std::vector filedata2(filedata); - filedata2[i].clear(); - filedata2[i2].clear(); - - if (test(settings, filedata2, i)) { - std::cout << "Removed #ifndef at line " << i << std::endl; - filedata.swap(filedata2); - changed = true; - } else { - std::cout << "Kept #ifndef at line " << i << std::endl; - break; - } - - break; - } - } - } - } - } - } - - return changed; -} - -static bool removeUnusedDefines(const ReduceSettings &settings, std::vector &filedata) -{ - bool changed = false; - - for (std::size_t i = 0; i < filedata.size(); ++i) { - if (filedata[i].compare(0,8,"#define ")==0 && filedata[i].find("\\")==std::string::npos) { - // Try to remove macro.. - - if (test(settings, filedata, i)) { - std::cout << "Removed #define at line " << i << std::endl; - filedata[i].clear(); - changed = true; - } else { - std::cout << "Kept #define at line " << i << std::endl; - } - } - } - - return changed; -} - -static bool removeSingleLines(const ReduceSettings &settings, std::vector &filedata) -{ - bool changed = false; - - bool decl = true; - for (std::size_t i = 0; i < filedata.size(); ++i) { - const std::string line = filedata[i]; - if (line.empty()) - continue; - - const char endChar = line[line.size() - 1U]; - if (decl && endChar == ';') { - if (test(settings, filedata, i)) { - std::cout << "Removed statement at line " << i << std::endl; - filedata[i].clear(); - } else { - std::cout << "Kept statement at line " << i << std::endl; - } - } else { - decl = bool (endChar == ';' || endChar == '{' || endChar == '}'); - } - } - - return changed; -} - -// Try to remove stuff from statements -static bool cleanupStatements(const ReduceSettings &settings, std::vector &filedata) -{ - bool changed = false; - - for (std::size_t i = 0; i < filedata.size(); ++i) { - std::string line = filedata[i]; - if (line.empty()) - continue; - - for (std::string::size_type pos = 0U; pos < line.size(); ++pos) { - - // function parameter.. - if (std::strchr("(,", line[pos])) { - const std::string::size_type pos1 = (line[pos] == ',') ? pos : (pos + 1U); - std::string::size_type pos2 = pos + 1; - while (pos2 < line.size() && std::isspace(line[pos2])) - ++pos2; - while (pos2 < line.size() && (std::isalnum(line[pos2]) || line[pos2]=='_')) - ++pos2; - while (pos2 < line.size() && std::isspace(line[pos2])) - ++pos2; - if (pos2 >= pos+2U && pos2=line.size() || line[pos2]!='(') - continue; - pos2++; - while (pos2 < line.size() && std::isalpha(line[pos2])) - ++pos2; - while (pos2 < line.size() && std::isspace(line[pos2])) - ++pos2; - if (pos2>=line.size() || line[pos2]!='*') - continue; - while (pos2 < line.size() && line[pos2]=='*') - ++pos2; - if (pos2] [--inconclusive] [--debug-warnings] [--max-configs=] [--platform=] [--library=] [--std=] filename [linenr]" << std::endl; - return EXIT_FAILURE; - } - - std::cout << "make sure " << (settings.hang ? "hang" : "false positive") << " can be reproduced" << std::endl; - - // Execute Cppcheck on the file.. - { - CppcheckExecutor cppcheck(settings); - if (!cppcheck.run(settings.filename, settings.maxtime)) { - std::cerr << "Can't reproduce false positive at line " << settings.linenr << std::endl; - return EXIT_FAILURE; - } - } - - // Read file.. - std::vector filedata(readfile(settings.filename)); - - // Write resulting code.. - if (!test(settings, filedata, ~0)) { - std::cerr << "Cleanup failed." << std::endl; - return EXIT_FAILURE; - } - - // Remove includes.. - std::set headers; - for (std::size_t i = 0; i < filedata.size(); i++) { - if (filedata[i].compare(0,8,"#include")==0) { - if (test(settings, filedata, i)) { - std::cout << "removed #include : " << filedata[i] << std::endl; - filedata[i].clear(); - } else { - std::string header = filedata[i]; - header = header.substr(1U + header.find_first_of("\"<")); - header = header.erase(header.find_last_of("\">")); - if (headers.find(header) != headers.end()) { - std::cerr << "Failed to reduce headers" << std::endl; - return EXIT_FAILURE; - } - headers.insert(header); - std::string path(settings.filename); - if (path.find_first_of("\\/") != std::string::npos) - path = path.erase(1 + path.find_last_of("\\/")); - else - path.clear(); - std::cout << "expand #include : " << (path+header) << std::endl; - std::vector data(readfile(path+header)); - if (!data.empty()) { - filedata[i].clear(); - filedata.insert(filedata.begin()+i, data.begin(), data.end()); - settings.linenr += data.size(); - } - } - } - } - - bool changed = true; - while (changed) { - changed = false; - changed |= removeMacrosInGlobalScope(settings,filedata); - changed |= removeBlocksOfCode(settings,filedata); - changed |= removeClassAndStructMembers(settings,filedata); - changed |= removeIfEndIf(settings,filedata); - changed |= removeUnusedDefines(settings,filedata); - - if (settings.hang) { - changed |= removeSingleLines(settings,filedata); - changed |= cleanupStatements(settings,filedata); - } - } - - // Write resulting code.. - { - const std::string outfilename(std::string("__out__") + std::strrchr(settings.filename,'.')); - std::ofstream fout; - if (!print) - fout.open(outfilename.c_str()); - std::ostream &os = print ? std::cout : fout; - for (std::size_t i = 0; i < filedata.size(); i++) { - if (!filedata[i].empty()) - os << filedata[i] << std::endl; - } - } - - return EXIT_SUCCESS; -}