Skip to content

writer-json-sarif: add tool to rule properties #147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 77 additions & 56 deletions src/lib/writer-json-sarif.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,43 @@

using namespace boost::json;

void SarifTreeEncoder::initToolVersion()
struct RuleProps {
int cweId;
std::string scRuleId;
std::string tool;
};

struct SarifTreeEncoder::Private {
using TRuleMap = std::map<std::string, RuleProps>;
TRuleMap ruleMap;

TScanProps scanProps;
boost::json::object driver;
boost::json::array results;
CtxEventDetector ctxEvtDetetor;

void initToolVersion();
void serializeRules();
};

SarifTreeEncoder::SarifTreeEncoder():
d(new Private)
{
}

SarifTreeEncoder::~SarifTreeEncoder() = default;

void SarifTreeEncoder::Private::initToolVersion()
{
std::string tool;
auto it = scanProps_.find("tool");
if (scanProps_.end() != it)
auto it = this->scanProps.find("tool");
if (this->scanProps.end() != it)
// read "tool" scan property
tool = it->second;

std::string ver;
it = scanProps_.find("tool-version");
if (scanProps_.end() != it) {
it = this->scanProps.find("tool-version");
if (this->scanProps.end() != it) {
// read "tool-version" scan property
ver = it->second;

Expand Down Expand Up @@ -65,17 +91,17 @@ void SarifTreeEncoder::initToolVersion()
ver = CS_VERSION;
uri = "https://github.com/csutils/csdiff";
}
else if (scanProps_.end() != (it = scanProps_.find("tool-url")))
else if (this->scanProps.end() != (it = this->scanProps.find("tool-url")))
// read "tool-url" scan property
uri = it->second;

driver_["name"] = std::move(tool);
this->driver["name"] = std::move(tool);

if (!ver.empty())
driver_["version"] = std::move(ver);
this->driver["version"] = std::move(ver);

if (!uri.empty())
driver_["informationUri"] = std::move(uri);
this->driver["informationUri"] = std::move(uri);
}

static void sarifEncodeShellCheckRule(object *rule, const std::string &ruleID)
Expand All @@ -84,10 +110,9 @@ static void sarifEncodeShellCheckRule(object *rule, const std::string &ruleID)
rule->emplace("name", ruleID);

// properties.tags[]
object props = {
{ "tags", { "ShellCheck" } }
};
rule->emplace("properties", std::move(props));
array tags = { "ShellCheck" };
object &props = rule->at("properties").as_object();
props["tags"] = std::move(tags);

// help.text && help.markdown
auto helpURI = "https://github.com/koalaman/shellcheck/wiki/" + ruleID;
Expand All @@ -107,15 +132,8 @@ static void sarifEncodeCweRule(object *rule, const int cwe, bool append = false)
array cweList = { "CWE-" + cweStr };

// properties.cwe[]
if (append) {
object &props = rule->at("properties").as_object();
props["cwe"] = std::move(cweList);
} else {
object props = {
{ "cwe", std::move(cweList) }
};
rule->emplace("properties", std::move(props));
}
object &props = rule->at("properties").as_object();
props["cwe"] = std::move(cweList);

// help.text
auto helpText =
Expand All @@ -132,41 +150,40 @@ static void sarifEncodeCweRule(object *rule, const int cwe, bool append = false)
}
}

void SarifTreeEncoder::serializeRules()
void SarifTreeEncoder::Private::serializeRules()
{
array ruleList;
for (const auto &item : shellCheckMap_) {
for (const auto &item : this->ruleMap) {
const auto &id = item.first;
const RuleProps &rp = item.second;

object rule = {
{ "id", id }
{ "id", id },
{ "properties", object() }
};

sarifEncodeShellCheckRule(&rule, item.second);
if (1U == cweMap_.count(id))
sarifEncodeCweRule(&rule, cweMap_[id], /*append =*/ true);

ruleList.push_back(std::move(rule));
}

for (const auto &item : cweMap_) {
const auto &id = item.first;
if (1U == shellCheckMap_.count(id))
continue;
const bool haveScRule = !rp.scRuleId.empty();
if (haveScRule)
sarifEncodeShellCheckRule(&rule, rp.scRuleId);
else if (!rp.tool.empty()) {
// encode tool tag
array tags = { rp.tool };
object &props = rule["properties"].as_object();
props["tags"] = std::move(tags);
}

object rule = {
{ "id", id }
};
if (rp.cweId)
sarifEncodeCweRule(&rule, rp.cweId, /*append =*/ haveScRule);

sarifEncodeCweRule(&rule, item.second);
ruleList.push_back(std::move(rule));
}

driver_["rules"] = std::move(ruleList);
this->driver["rules"] = std::move(ruleList);
}

void SarifTreeEncoder::importScanProps(const TScanProps &scanProps)
{
scanProps_ = scanProps;
d->scanProps = scanProps;
}

static void sarifEncodeMsg(object *pDst, const std::string& text)
Expand Down Expand Up @@ -293,19 +310,23 @@ void SarifTreeEncoder::appendDef(const Defect &def)
static const RE reShellCheckMsg("(\\[)?(SC[0-9]+)(\\])?$");
boost::regex_search(keyEvt.event, sm, reShellCheckMsg);

// update ShellCheck rule map
shellCheckMap_[ruleId] = sm[2];
// update ShellCheck rule ID
d->ruleMap[ruleId].scRuleId = sm[2];
}

if (def.cwe) {
// update CWE map
cweMap_[ruleId] = def.cwe;
// update CWE ID
d->ruleMap[ruleId].cweId = def.cwe;

// encode per-warning CWE property
object cweProp = {{ "cwe", "CWE-" + std::to_string(def.cwe) }};
result["properties"] = std::move(cweProp);
}

if (!def.tool.empty())
// update tool for this rule
d->ruleMap[ruleId].tool = def.tool;

// key event severity level
sarifEncodeLevel(&result, keyEvt.event);

Expand All @@ -332,7 +353,7 @@ void SarifTreeEncoder::appendDef(const Defect &def)
continue;
}

if (ctxEvtDetetor_.isAnyCtxLine(evt))
if (d->ctxEvtDetetor.isAnyCtxLine(evt))
// code snippet
sarifEncodeSnippet(reg, evt.msg);

Expand All @@ -354,7 +375,7 @@ void SarifTreeEncoder::appendDef(const Defect &def)
result["relatedLocations"] = std::move(relatedLocs);

// append the `result` object to the `results` array
results_.push_back(std::move(result));
d->results.push_back(std::move(result));
}

void SarifTreeEncoder::writeTo(std::ostream &str)
Expand All @@ -365,27 +386,27 @@ void SarifTreeEncoder::writeTo(std::ostream &str)
{ "version", "2.1.0" }
};

if (!scanProps_.empty()) {
if (!d->scanProps.empty()) {
// scan props
root["inlineExternalProperties"] = {
{{ "externalizedProperties", jsonSerializeScanProps(scanProps_) }}
{{ "externalizedProperties", jsonSerializeScanProps(d->scanProps) }}
};
}

this->initToolVersion();
d->initToolVersion();

if (!cweMap_.empty() || !shellCheckMap_.empty())
// needs to run before we pick driver_
this->serializeRules();
if (!d->ruleMap.empty())
// needs to run before we pick d->driver
d->serializeRules();

object run0 = {
{ "tool", {
{ "driver", std::move(driver_) }
{ "driver", std::move(d->driver) }
}}
};

// results
run0["results"] = std::move(results_);
run0["results"] = std::move(d->results);

// mandatory: runs
root["runs"] = array{std::move(run0)};
Expand Down
18 changes: 4 additions & 14 deletions src/lib/writer-json-sarif.hh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
// validation: https://sarifweb.azurewebsites.net/Validation
class SarifTreeEncoder: public AbstractTreeEncoder {
public:
SarifTreeEncoder() = default;
SarifTreeEncoder();
~SarifTreeEncoder();

/// import supported scan properties
void importScanProps(const TScanProps &) override;
Expand All @@ -43,19 +44,8 @@ class SarifTreeEncoder: public AbstractTreeEncoder {
void writeTo(std::ostream &) override;

private:
void initToolVersion();
void serializeRules();

using TCweMap = std::map<std::string, int>;
TCweMap cweMap_;

using TShellCheckMap = std::map<std::string, std::string>;
TShellCheckMap shellCheckMap_;

TScanProps scanProps_;
boost::json::object driver_;
boost::json::array results_;
CtxEventDetector ctxEvtDetetor_;
struct Private;
std::unique_ptr<Private> d;
};

#endif /* H_GUARD_WRITER_JSON_SARIF_H */
Loading