Skip to content
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

Rust: Implement database quality telemetry query #18697

Merged
merged 2 commits into from
Feb 7, 2025
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
28 changes: 1 addition & 27 deletions csharp/ql/src/Telemetry/DatabaseQuality.qll
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,7 @@
*/

import csharp

signature module StatsSig {
int getNumberOfOk();

int getNumberOfNotOk();

string getOkText();

string getNotOkText();
}

module ReportStats<StatsSig Stats> {
predicate numberOfOk(string key, int value) {
value = Stats::getNumberOfOk() and
key = "Number of " + Stats::getOkText()
}

predicate numberOfNotOk(string key, int value) {
value = Stats::getNumberOfNotOk() and
key = "Number of " + Stats::getNotOkText()
}

predicate percentageOfOk(string key, float value) {
value = Stats::getNumberOfOk() * 100.0 / (Stats::getNumberOfOk() + Stats::getNumberOfNotOk()) and
key = "Percentage of " + Stats::getOkText()
}
}
import codeql.util.ReportStats

module CallTargetStats implements StatsSig {
int getNumberOfOk() { result = count(Call c | exists(c.getTarget())) }
Expand Down
28 changes: 1 addition & 27 deletions java/ql/src/Telemetry/DatabaseQuality.qll
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,7 @@
*/

import java

signature module StatsSig {
int getNumberOfOk();

int getNumberOfNotOk();

string getOkText();

string getNotOkText();
}

module ReportStats<StatsSig Stats> {
predicate numberOfOk(string key, int value) {
value = Stats::getNumberOfOk() and
key = "Number of " + Stats::getOkText()
}

predicate numberOfNotOk(string key, int value) {
value = Stats::getNumberOfNotOk() and
key = "Number of " + Stats::getNotOkText()
}

predicate percentageOfOk(string key, float value) {
value = Stats::getNumberOfOk() * 100.0 / (Stats::getNumberOfOk() + Stats::getNumberOfNotOk()) and
key = "Percentage of " + Stats::getOkText()
}
}
import codeql.util.ReportStats

module CallTargetStats implements StatsSig {
int getNumberOfOk() { result = count(Call c | exists(c.getCallee())) }
Expand Down
46 changes: 46 additions & 0 deletions rust/ql/src/queries/telemetry/DatabaseQuality.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a rust/ql/src/queries/diagnostics and rust/ql/src/queries/summary directory and this introduces rust/ql/src/queries/telemetry as well. Are the new queries meaningfully different enough to justify a third location or should we condense back down to (any) two of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simply followed the same structure that we have for Java and C#.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll look into whether I can combine any of these directories after this PR is merged. Don't really want a proliferation of folders unless they're meaningfully different.

* Provides database quality statistics that are reported by
* `rust/telemetry/extractor-information`
* and perhaps warned about by `rust/diagnostics/database-quality`.
*/

import rust
import codeql.util.ReportStats

module CallTargetStats implements StatsSig {
int getNumberOfOk() { result = count(CallExprBase c | exists(c.getStaticTarget())) }

private predicate isLambdaCall(CallExpr call) {
exists(Expr receiver | receiver = call.getFunction() |
// All calls to complex expressions and local variable accesses are lambda calls
receiver instanceof PathExpr implies receiver = any(Variable v).getAnAccess()
)
}

additional predicate isNotOkCall(CallExprBase c) {
not exists(c.getStaticTarget()) and
not isLambdaCall(c)
}

int getNumberOfNotOk() { result = count(CallExprBase c | isNotOkCall(c)) }

string getOkText() { result = "calls with call target" }

string getNotOkText() { result = "calls with missing call target" }
}

module MacroCallTargetStats implements StatsSig {
int getNumberOfOk() { result = count(MacroCall c | c.hasExpanded()) }

additional predicate isNotOkCall(MacroCall c) { not c.hasExpanded() }

int getNumberOfNotOk() { result = count(MacroCall c | isNotOkCall(c)) }

string getOkText() { result = "macro calls with call target" }

string getNotOkText() { result = "macro calls with missing call target" }
}

module CallTargetStatsReport = ReportStats<CallTargetStats>;

module MacroCallTargetStatsReport = ReportStats<MacroCallTargetStats>;
41 changes: 41 additions & 0 deletions rust/ql/src/queries/telemetry/DatabaseQualityDiagnostics.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @name Low Rust analysis quality
* @description Low Rust analysis quality
* @kind diagnostic
* @id rust/diagnostic/database-quality
*/

import rust
import DatabaseQuality
import codeql.util.Unit

class DbQualityDiagnostic extends Unit {
DbQualityDiagnostic() {
exists(float percentageGood |
CallTargetStatsReport::percentageOfOk(_, percentageGood)
or
MacroCallTargetStatsReport::percentageOfOk(_, percentageGood)
|
percentageGood < 95
)
}

string toString() {
result =
"Scanning Rust code completed successfully, but the scan encountered issues. " +
"This may be caused by problems identifying dependencies or use of generated source code, among other reasons -- "
+
"see other CodeQL diagnostics reported on the CodeQL status page for more details of possible causes. "
+ "Addressing these warnings is advisable to avoid false-positive or missing results."
}
}

query predicate diagnosticAttributes(DbQualityDiagnostic e, string key, string value) {
exists(e) and // Quieten warning about unconstrained 'e'
key = ["visibilityCliSummaryTable", "visibilityTelemetry", "visibilityStatusPage"] and
value = "true"
}

from DbQualityDiagnostic d
select d, d.toString(), 1
/* 1 = Warning severity */
65 changes: 65 additions & 0 deletions rust/ql/src/queries/telemetry/ExtractorInformation.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @name Rust extraction information
* @description Information about the extraction for a Rust database
* @kind metric
* @tags summary telemetry
* @id rust/telemetry/extraction-information
*/

import rust
import DatabaseQuality
import codeql.rust.Diagnostics

predicate fileCount(string key, int value) {
key = "Number of files" and
value = strictcount(File f)
}

predicate fileCountByExtension(string key, int value) {
exists(string extension |
key = "Number of files with extension " + extension and
value = strictcount(File f | f.getExtension() = extension)
)
}

predicate numberOfLinesOfCode(string key, int value) {
key = "Number of lines of code" and
value = strictsum(File f | any() | f.getNumberOfLinesOfCode())
}

predicate numberOfLinesOfCodeByExtension(string key, int value) {
exists(string extension |
key = "Number of lines of code with extension " + extension and
value = strictsum(File f | f.getExtension() = extension | f.getNumberOfLinesOfCode())
)
}

predicate extractorDiagnostics(string key, int value) {
exists(int severity |
key = "Number of diagnostics with severity " + severity.toString() and
value = strictcount(Diagnostic d | d.getSeverity() = severity)
)
}

from string key, float value
where
(
fileCount(key, value) or
fileCountByExtension(key, value) or
numberOfLinesOfCode(key, value) or
numberOfLinesOfCodeByExtension(key, value) or
extractorDiagnostics(key, value) or
CallTargetStatsReport::numberOfOk(key, value) or
CallTargetStatsReport::numberOfNotOk(key, value) or
CallTargetStatsReport::percentageOfOk(key, value) or
MacroCallTargetStatsReport::numberOfOk(key, value) or
MacroCallTargetStatsReport::numberOfNotOk(key, value) or
MacroCallTargetStatsReport::percentageOfOk(key, value)
) and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's quite a bit of overlap with rust/summary/summary-statistics (files and lines of code extracted), the focus is a little different here but something to be aware of.

/* Infinity */
value != 1.0 / 0.0 and
/* -Infinity */
value != -1.0 / 0.0 and
/* NaN */
value != 0.0 / 0.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hurting my maths brain, but it does seem to be the way float works. :(

select key, value
31 changes: 31 additions & 0 deletions shared/util/codeql/util/ReportStats.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Provides the `ReportStats` module for reporting database quality statistics.
*/
module;

signature module StatsSig {
int getNumberOfOk();

int getNumberOfNotOk();

string getOkText();

string getNotOkText();
}

module ReportStats<StatsSig Stats> {
predicate numberOfOk(string key, int value) {
value = Stats::getNumberOfOk() and
key = "Number of " + Stats::getOkText()
}

predicate numberOfNotOk(string key, int value) {
value = Stats::getNumberOfNotOk() and
key = "Number of " + Stats::getNotOkText()
}

predicate percentageOfOk(string key, float value) {
value = Stats::getNumberOfOk() * 100.0 / (Stats::getNumberOfOk() + Stats::getNumberOfNotOk()) and
key = "Percentage of " + Stats::getOkText()
}
}