|
| 1 | +#include "SARIFGenerator.h" |
| 2 | +#include "Paths.h" |
| 3 | + |
| 4 | +#include "loguru.h" |
| 5 | + |
| 6 | +#include <fstream> |
| 7 | +#include <regex> |
| 8 | +#include <unordered_map> |
| 9 | + |
| 10 | +using namespace tests; |
| 11 | + |
| 12 | +namespace sarif { |
| 13 | + // Here is a temporary solution that restores the correct project-relative path from |
| 14 | + // the abstract relative path, provided by KLEE in stack trace inside a `XXXX.err` file. |
| 15 | + // There is no clear reason why KLEE is using the wrong base for relative path. |
| 16 | + // The correct way to extract the full path for a stack file is in checking entries like |
| 17 | + // !820 = !DIFile(filename: "test/suites/cli/complex_structs.c", directory: "/home/utbot/tmp/UTBotCpp/server") |
| 18 | + // in upper laying file `assembly.ll`; then we may call the `fs::relative(src, path)`. |
| 19 | + // For example the function call: |
| 20 | + // getInProjectPath("/home/utbot/tmp/UTBotCpp/server/test/suites/object-file", |
| 21 | + // "test/suites/object-file/op/source2.c") |
| 22 | + // returns |
| 23 | + // "op/source2.c" |
| 24 | + fs::path getInProjectPath(const fs::path &path, const fs::path &src) { |
| 25 | + fs::path relToProject; |
| 26 | + auto p = path.begin(); |
| 27 | + auto s = src.begin(); |
| 28 | + bool foundStartFragment = false; |
| 29 | + while (p != path.end() && s != src.end()) { |
| 30 | + if (*p == *s) { |
| 31 | + foundStartFragment = true; |
| 32 | + ++s; |
| 33 | + } else if (foundStartFragment) { |
| 34 | + break; |
| 35 | + } |
| 36 | + ++p; |
| 37 | + } |
| 38 | + if (foundStartFragment && p == path.end()) { |
| 39 | + while (s != src.end()) { |
| 40 | + relToProject = relToProject / *s; |
| 41 | + ++s; |
| 42 | + } |
| 43 | + } |
| 44 | + return relToProject; |
| 45 | + } |
| 46 | + |
| 47 | + void sarifAddTestsToResults(const utbot::ProjectContext &projectContext, |
| 48 | + const Tests &tests, |
| 49 | + json &results) { |
| 50 | + LOG_S(INFO) << "{stack"; |
| 51 | + for (const auto &it : tests.methods) { |
| 52 | + for (const auto &methodTestCase : it.second.testCases) { |
| 53 | + json result; |
| 54 | + const std::vector<std::string> &descriptors = methodTestCase.errorDescriptors; |
| 55 | + std::string key; |
| 56 | + std::string value; |
| 57 | + json stackLocations; |
| 58 | + json codeFlowsLocations; |
| 59 | + json testLocation; |
| 60 | + bool canAddThisTestToSARIF = false; |
| 61 | + for (const std::string &descriptor : descriptors) { |
| 62 | + std::stringstream streamOfDescriptor(descriptor); |
| 63 | + std::string lineInDescriptor; |
| 64 | + bool firstCallInStack = false; |
| 65 | + while (getline(streamOfDescriptor, lineInDescriptor)) { |
| 66 | + if (lineInDescriptor.empty() || lineInDescriptor[0] == '#') |
| 67 | + continue; |
| 68 | + if (isspace(lineInDescriptor[0])) { |
| 69 | + if (key == "Stack") { |
| 70 | + const std::regex stack_regex( |
| 71 | + R"regex(\s+#(.*) in ([^ ]*) [(][^)]*[)] at ([^:]*):(\d+))regex"); |
| 72 | + std::smatch stack_match; |
| 73 | + if (!std::regex_match(lineInDescriptor, stack_match, stack_regex)) { |
| 74 | + LOG_S(ERROR) << "wrong `Stack` line: " << lineInDescriptor; |
| 75 | + } else { |
| 76 | + const fs::path &srcPath = fs::path(stack_match[3]); |
| 77 | + const fs::path &relPathInProject = getInProjectPath(projectContext.projectPath, srcPath); |
| 78 | + const fs::path &fullPathInProject = projectContext.projectPath / relPathInProject; |
| 79 | + if (Paths::isSubPathOf(projectContext.buildDir, fullPathInProject)) { |
| 80 | + continue; |
| 81 | + } |
| 82 | + if (!relPathInProject.empty() && fs::exists(fullPathInProject)) { |
| 83 | + // stackLocations from project source |
| 84 | + json locationWrapper; |
| 85 | + locationWrapper["module"] = "project"; |
| 86 | + { |
| 87 | + json location; |
| 88 | + location["physicalLocation"]["artifactLocation"]["uri"] = relPathInProject; |
| 89 | + location["physicalLocation"]["artifactLocation"]["uriBaseId"] = "%SRCROOT%"; |
| 90 | + location["physicalLocation"]["region"]["startLine"] = std::stoi(stack_match[4]); // line number |
| 91 | + // commented, duplicated in message |
| 92 | + // location["logicalLocations"][0]["fullyQualifiedName"] = stack_match[2]; // call name |
| 93 | + location["message"]["text"] = stack_match[2].str() + std::string(" (source)"); // info for ANALYSIS STEP |
| 94 | + if (firstCallInStack) { |
| 95 | + firstCallInStack = false; |
| 96 | + result["locations"].push_back(location); |
| 97 | + stackLocations["message"]["text"] = "UTBot generated"; |
| 98 | + codeFlowsLocations["message"]["text"] = "UTBot generated"; |
| 99 | + } |
| 100 | + locationWrapper["location"] = location; |
| 101 | + } |
| 102 | + stackLocations["frames"].push_back(locationWrapper); |
| 103 | + codeFlowsLocations["locations"].push_back(locationWrapper); |
| 104 | + } else if (firstCallInStack) { |
| 105 | + // stackLocations from runtime, that is called by tested function |
| 106 | + json locationWrapper; |
| 107 | + locationWrapper["module"] = "external"; |
| 108 | + { |
| 109 | + json location; |
| 110 | + location["physicalLocation"]["artifactLocation"] ["uri"] = srcPath.filename(); // just a name |
| 111 | + location["physicalLocation"]["artifactLocation"] ["uriBaseId"] = "%PATH%"; |
| 112 | + location["physicalLocation"]["region"]["startLine"] = std::stoi(stack_match[4]); // line number |
| 113 | + // commented, duplicated in message |
| 114 | + // location["logicalLocations"][0]["fullyQualifiedName"] = stack_match[2]; // call name |
| 115 | + location["message"]["text"] = stack_match[2].str() + std::string(" (external)"); // info for ANALYSIS STEP |
| 116 | + locationWrapper["location"] = location; |
| 117 | + } |
| 118 | + stackLocations["frames"].push_back(locationWrapper); |
| 119 | + codeFlowsLocations["locations"].push_back(locationWrapper); |
| 120 | + } else { |
| 121 | + // the rest is the KLEE calls that are not applicable for navigation |
| 122 | + LOG_S(INFO) << "Skip path in stack frame :" << srcPath; |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + } else { |
| 127 | + size_t pos = lineInDescriptor.find(':'); |
| 128 | + if (pos == std::string::npos) { |
| 129 | + LOG_S(ERROR) << "no key:" << lineInDescriptor; |
| 130 | + } else { |
| 131 | + if (key == "Stack") { |
| 132 | + // Check stack validity |
| 133 | + if (firstCallInStack) { |
| 134 | + LOG_S(ERROR) << "no visible stack in descriptor:" << descriptor; |
| 135 | + } else { |
| 136 | + canAddThisTestToSARIF = true; |
| 137 | + } |
| 138 | + } |
| 139 | + firstCallInStack = true; |
| 140 | + |
| 141 | + key = lineInDescriptor.substr(0, pos); |
| 142 | + value = lineInDescriptor.substr(pos + 1); |
| 143 | + if (key == "Error") { |
| 144 | + result["message"]["text"] = value; |
| 145 | + result["level"] = "error"; |
| 146 | + result["kind"] = "fail"; |
| 147 | + } else if (key == ERROR_ID_KEY) { |
| 148 | + result["ruleId"] = value; |
| 149 | + } else if (key == "Stack") { |
| 150 | + stackLocations = json(); |
| 151 | + codeFlowsLocations = json(); |
| 152 | + } else if (key == TEST_FILE_KEY) { |
| 153 | + testLocation = json(); |
| 154 | + testLocation["physicalLocation"]["artifactLocation"]["uri"] = fs::relative(value, projectContext.projectPath); |
| 155 | + testLocation["physicalLocation"]["artifactLocation"]["uriBaseId"] = "%SRCROOT%"; |
| 156 | + } else if (key == TEST_LINE_KEY) { |
| 157 | + testLocation["physicalLocation"]["region"]["startLine"] = std::stoi(value); // line number |
| 158 | + } else if (key == TEST_NAME_KEY) { |
| 159 | + // commented, duplicated in message |
| 160 | + // testLocation["logicalLocations"][0]["fullyQualifiedName"] = value; // call name |
| 161 | + testLocation["message"]["text"] = value + std::string(" (test)"); // info for ANALYSIS STEP |
| 162 | + { |
| 163 | + json locationWrapper; |
| 164 | + locationWrapper["location"] = testLocation; |
| 165 | + locationWrapper["module"] = "test"; |
| 166 | + |
| 167 | + stackLocations["frames"].push_back(locationWrapper); |
| 168 | + codeFlowsLocations["locations"].push_back(locationWrapper); |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + if (canAddThisTestToSARIF) { |
| 177 | + result["stacks"].push_back(stackLocations); |
| 178 | + result["codeFlows"][0]["threadFlows"].push_back(codeFlowsLocations); |
| 179 | + results.push_back(result); |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + LOG_S(INFO) << "}stack"; |
| 184 | + } |
| 185 | + |
| 186 | + std::string sarifPackResults(const json &results) { |
| 187 | + // POINT 3 |
| 188 | + json sarifJson; |
| 189 | + sarifJson["$schema"] = "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json"; |
| 190 | + sarifJson["version"] = "2.1.0"; |
| 191 | + { |
| 192 | + json runs; |
| 193 | + { |
| 194 | + json runAkaTestCase; |
| 195 | + runAkaTestCase["tool"]["driver"]["name"] = "UTBotCpp"; |
| 196 | + runAkaTestCase["tool"]["driver"]["informationUri"] = "https://utbot.org"; |
| 197 | + runAkaTestCase["results"] = results; |
| 198 | + runs.push_back(runAkaTestCase); |
| 199 | + } |
| 200 | + sarifJson["runs"] = runs; |
| 201 | + } |
| 202 | + return sarifJson.dump(2); |
| 203 | + } |
| 204 | +} |
0 commit comments