/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2021 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 "mainwindow.h" #include "ui_mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include const QString WORK_FOLDER(QDir::homePath() + "/triage"); const QString DACA2_PACKAGES(QDir::homePath() + "/daca2-packages"); const int MAX_ERRORS = 100; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), mVersionRe("^(master|main|your|head|[12].[0-9][0-9]?) (.*)$"), hFiles{"*.hpp", "*.h", "*.hxx", "*.hh", "*.tpp", "*.txx", "*.ipp", "*.ixx"}, srcFiles{"*.cpp", "*.cxx", "*.cc", "*.c++", "*.C", "*.c", "*.cl"} { ui->setupUi(this); std::srand(static_cast(std::time(nullptr))); QDir workFolder(WORK_FOLDER); if (!workFolder.exists()) { workFolder.mkdir(WORK_FOLDER); } ui->results->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->results, &QListWidget::customContextMenuRequested, this, &MainWindow::resultsContextMenu); mFSmodel.setRootPath(WORK_FOLDER); mFSmodel.setReadOnly(true); mFSmodel.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); ui->directoryTree->setModel(&mFSmodel); QHeaderView * header = ui->directoryTree->header(); for (int i = 1; i < header->length(); ++i) // hide all except [0] header->hideSection(i); ui->directoryTree->setRootIndex(mFSmodel.index(WORK_FOLDER)); ui->hFilesFilter->setToolTip(hFiles.join(',')); ui->srcFilesFilter->setToolTip(srcFiles.join(',')); } MainWindow::~MainWindow() { delete ui; } void MainWindow::loadFile() { ui->statusBar->clearMessage(); const QString fileName = QFileDialog::getOpenFileName(this, tr("daca results file"), WORK_FOLDER, tr("Text files (*.txt *.log);;All (*.*)")); if (fileName.isEmpty()) return; QFile file(fileName); file.open(QIODevice::ReadOnly | QIODevice::Text); QTextStream textStream(&file); load(textStream); } void MainWindow::loadFromClipboard() { ui->statusBar->clearMessage(); QString clipboardContent = QApplication::clipboard()->text(); QTextStream textStream(&clipboardContent); load(textStream); } void MainWindow::load(QTextStream &textStream) { QString url; QString errorMessage; QStringList versions; mAllErrors.clear(); while (true) { QString line = textStream.readLine(); if (line.isNull()) break; if (line.startsWith("ftp://")) { url = line; if (!errorMessage.isEmpty()) mAllErrors << errorMessage; errorMessage.clear(); } else if (!url.isEmpty() && QRegularExpression("^.*: (error|warning|style|note):.*$").match(line).hasMatch()) { const QRegularExpressionMatch matchRes = mVersionRe.match(line); if (matchRes.hasMatch()) { const QString version = matchRes.captured(1); if (versions.indexOf(version) < 0) versions << version; } if (line.indexOf(": note:") > 0) errorMessage += '\n' + line; else if (errorMessage.isEmpty()) { errorMessage = url + '\n' + line; } else { mAllErrors << errorMessage; errorMessage = url + '\n' + line; } } } if (!errorMessage.isEmpty()) mAllErrors << errorMessage; ui->version->clear(); if (versions.size() > 1) ui->version->addItem(""); ui->version->addItems(versions); filter(""); } void MainWindow::refreshResults() { filter(ui->version->currentText()); } void MainWindow::filter(QString filter) { QStringList allErrors; for (const QString &errorItem : mAllErrors) { if (filter.isEmpty()) { allErrors << errorItem; continue; } const QStringList lines = errorItem.split("\n"); if (lines.size() < 2) continue; if (lines[1].startsWith(filter)) allErrors << errorItem; } ui->results->clear(); if (ui->random100->isChecked() && allErrors.size() > MAX_ERRORS) { // remove items in /test/ for (int i = allErrors.size() - 1; i >= 0 && allErrors.size() > MAX_ERRORS; --i) { if (allErrors[i].indexOf("test") > 0) allErrors.removeAt(i); } std::random_shuffle(allErrors.begin(), allErrors.end()); ui->results->addItems(allErrors.mid(0, MAX_ERRORS)); ui->results->sortItems(); } else { ui->results->addItems(allErrors); } } bool MainWindow::runProcess(const QString &programName, const QStringList &arguments) { QProgressDialog dialog("Running external process: " + programName, "Kill", 0 /*min*/, 1 /*max*/, this); dialog.setWindowModality(Qt::WindowModal); dialog.setMinimumDuration(0 /*msec*/); dialog.setValue(0); QProcess process; process.setWorkingDirectory(WORK_FOLDER); process.start(programName, arguments); // async action bool success = false; bool state = (QProcess::Running == process.state() || QProcess::Starting == process.state()); while (!success && state) { success = process.waitForFinished(50 /*msec*/); // Not the best way to keep UI unfreeze, keep work async in other thread much more a Qt style QCoreApplication::processEvents(); if (dialog.wasCanceled()) { process.kill(); success = false; break; } state = (QProcess::Running == process.state() || QProcess::Starting == process.state()); } dialog.setValue(1); if (!success) { QString errorstr(programName); errorstr.append(": "); errorstr.append(process.errorString()); ui->statusBar->showMessage(errorstr); } else { int exitCode = process.exitCode(); if (exitCode != 0) { success = false; const QByteArray stderrOutput = process.readAllStandardError(); QString errorstr(programName); errorstr.append(QString(": exited with %1: ").arg(exitCode)); errorstr.append(stderrOutput); ui->statusBar->showMessage(errorstr); } } return success; } bool MainWindow::wget(const QString &url) { return runProcess("wget", QStringList{url}); } bool MainWindow::unpackArchive(const QString &archiveName) { // Unpack archive QStringList args; #ifdef Q_OS_WIN /* On Windows --force-local is necessary because tar wants to connect to a remote system * when a colon is found in the archiveName. So "C:/Users/blah/triage/package" would not work * without it. */ args << "--force-local"; #endif if (archiveName.endsWith(".tar.gz")) args << "-xzvf"; else if (archiveName.endsWith(".tar.bz2")) args << "-xjvf"; else if (archiveName.endsWith(".tar.xz")) args << "-xJvf"; else { // Try to automatically find an (un)compressor for this archive args << "-xavf"; } args << archiveName; return runProcess("tar", args); } void MainWindow::showResult(QListWidgetItem *item) { ui->statusBar->clearMessage(); if (!item->text().startsWith("ftp://")) return; const QStringList lines = item->text().split("\n"); if (lines.size() < 2) return; const QString url = lines[0]; QString msg = lines[1]; const QRegularExpressionMatch matchRes = mVersionRe.match(msg); if (matchRes.hasMatch()) msg = matchRes.captured(2); const QString archiveName = url.mid(url.lastIndexOf("/") + 1); const int pos1 = msg.indexOf(":"); const int pos2 = msg.indexOf(":", pos1+1); const QString fileName = WORK_FOLDER + '/' + msg.left(msg.indexOf(":")); const int lineNumber = msg.midRef(pos1+1, pos2-pos1-1).toInt(); if (!QFileInfo::exists(fileName)) { const QString daca2archiveFile {DACA2_PACKAGES + '/' + archiveName.mid(0,archiveName.indexOf(".tar.")) + ".tar.xz"}; if (QFileInfo::exists(daca2archiveFile)) { if (!unpackArchive(daca2archiveFile)) return; } else { const QString archiveFullPath {WORK_FOLDER + '/' + archiveName}; if (!QFileInfo::exists(archiveFullPath)) { // Download archive if (!wget(url)) return; } if (!unpackArchive(archiveFullPath)) return; } } showSrcFile(fileName, url, lineNumber); } void MainWindow::showSrcFile(const QString &fileName, const QString &url, const int lineNumber) { // Open file ui->code->setFocus(); QFile f(fileName); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { const QString errorMsg = QString("Opening file %1 failed: %2").arg(f.fileName(), f.errorString()); ui->statusBar->showMessage(errorMsg); } else { QTextStream textStream(&f); const QString fileData = textStream.readAll(); ui->code->setError(fileData, lineNumber, QStringList()); f.close(); ui->urlEdit->setText(url); ui->fileEdit->setText(fileName); ui->directoryTree->setCurrentIndex(mFSmodel.index(fileName)); } } void MainWindow::fileTreeFilter(const QString &str) { mFSmodel.setNameFilters(QStringList{"*" + str + "*"}); mFSmodel.setNameFilterDisables(false); } void MainWindow::findInFilesClicked() { ui->tabWidget->setCurrentIndex(1); ui->inFilesResult->clear(); const QString text = ui->filterEdit->text(); QStringList filter; if (ui->hFilesFilter->isChecked()) filter.append(hFiles); if (ui->srcFilesFilter->isChecked()) filter.append(srcFiles); QMimeDatabase mimeDatabase; QDirIterator it(WORK_FOLDER, filter, QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); const auto common_path_len = WORK_FOLDER.length() + 1; // let's remove common part of path improve UI while (it.hasNext()) { const QString fileName = it.next(); const QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileName); if (mimeType.isValid() && !mimeType.inherits(QStringLiteral("text/plain"))) { continue; } QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { int lineN = 0; QTextStream in(&file); while (!in.atEnd()) { ++lineN; QString line = in.readLine(); if (line.contains(text, Qt::CaseInsensitive)) { ui->inFilesResult->addItem(fileName.mid(common_path_len) + QString{":"} + QString::number(lineN)); } } } } } void MainWindow::directorytreeDoubleClick() { showSrcFile(mFSmodel.filePath(ui->directoryTree->currentIndex()), "", 1); } void MainWindow::searchResultsDoubleClick() { QString filename = ui->inFilesResult->currentItem()->text(); const auto idx = filename.lastIndexOf(':'); const int line = filename.midRef(idx + 1).toInt(); showSrcFile(WORK_FOLDER + QString{"/"} + filename.left(idx), "", line); } void MainWindow::resultsContextMenu(const QPoint& pos) { if (ui->results->selectedItems().isEmpty()) return; QMenu submenu; submenu.addAction("Copy"); QAction* menuItem = submenu.exec(ui->results->mapToGlobal(pos)); if (menuItem && menuItem->text().contains("Copy")) { QString text; for (const auto *res: ui->results->selectedItems()) text += res->text() + "\n"; QApplication::clipboard()->setText(text); } }