Skip to content
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
13 changes: 13 additions & 0 deletions cpp/ql/src/Telemetry/CompilerErrors.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @name Compiler errors
* @description A count of all compiler errors, grouped by error text.
* @kind metric
* @tags summary telemetry
* @id cpp/telemetry/compiler-errors
*/

import Metrics

from CppMetrics::ErrorCount m
where RankMetric<CppMetrics::ErrorCount>::getRank(m) <= 50
select m.toString(), m.getValue()
12 changes: 12 additions & 0 deletions cpp/ql/src/Telemetry/DatabaseQuality.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @name Database quality
* @description Metrics that indicate the quality of the database.
* @kind metric
* @tags summary telemetry
* @id cpp/telemetry/database-quality
*/

import Metrics

from QualityMetric m
select m.toString(), m.getValue()
29 changes: 29 additions & 0 deletions cpp/ql/src/Telemetry/Diagnostics.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import cpp

/**
* A syntax error.
*/
class SyntaxError extends CompilerError {
SyntaxError() {
this.getTag().matches("exp_%") or
this.getTag() =
[
"bad_data_member_initialization", "bad_pure_specifier", "bad_return", "bad_uuid_string",
"literal_without_initializer", "missing_class_definition", "missing_exception_declaration",
"nonstd_const_member_decl_not_allowed", "operator_name_not_allowed",
"wide_string_invalid_in_asm"
]
}
}

/**
* A cannot open file error.
* Typically this is due to a missing include.
*/
class CannotOpenFileError extends CompilerError {
CannotOpenFileError() { this.hasTag(["cannot_open_file", "cannot_open_file_reason"]) }

string getIncludedFile() {
result = this.getMessage().regexpCapture("cannot open source file '([^']+)'", 1)
Copy link
Contributor

@jketema jketema Nov 11, 2024

Choose a reason for hiding this comment

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

Note that this regex does not what you want in the case of cannot_open_file_reason, i.e., you get the reason as part of the result string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, I've no real way of testing this as I've never seen the extractor produce this error message. I suppose a .* at the end of the regex might be reasonable?

Copy link
Contributor

Choose a reason for hiding this comment

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

Are the file names always quoted? This is non-obvious from the frontend source code.

Copy link
Contributor Author

@calumgrant calumgrant Nov 11, 2024

Choose a reason for hiding this comment

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

Yes, I tested this on some real databases. But an integration test is clearly needed then. Qltest cannot test this situation due to the absence of SEMMLE_CPP_MISSING_INCLUDES_NOT_FATAL.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that having some integration test for this is the only thing that still needs addressing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}
}
12 changes: 12 additions & 0 deletions cpp/ql/src/Telemetry/ExtractionMetrics.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @name Extraction metrics
* @description Raw metrics relating to extraction.
* @kind metric
* @tags summary telemetry
* @id cpp/telemetry/extraction-metrics
*/

import Metrics

from ExtractionMetric m
select m.toString(), m.getValue()
269 changes: 269 additions & 0 deletions cpp/ql/src/Telemetry/Metrics.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import cpp
import Diagnostics

/**
* A metric is a string with a value.
*/
abstract class Metric extends string {
bindingset[this]
Metric() { any() }
}

/**
* A metric that we want to report in cpp/telemetry/extraction-metrics
*/
abstract class ExtractionMetric extends Metric {
bindingset[this]
ExtractionMetric() { any() }

/** Gets the value of this metric. */
abstract int getValue();
}

/**
* A metric that provides a baseline for a SuccessMetric.
*/
abstract class BaseMetric extends ExtractionMetric {
bindingset[this]
BaseMetric() { any() }
}

/**
* A metric that is relative to another metric,
* so can be used to calculate percentages.
*
* For clarity, metrics should express success,
* so higher values means better.
*/
abstract class SuccessMetric extends ExtractionMetric {
bindingset[this]
SuccessMetric() { any() }

/** Gets the metric this is relative to. */
abstract BaseMetric getBaseline();
}

/**
* A metric used to report database quality.
*/
class QualityMetric extends Metric {
BaseMetric baseMetric;
SuccessMetric relativeMetric;

QualityMetric() {
baseMetric = relativeMetric.getBaseline() and this = "Percentage of " + relativeMetric
}

float getValue() {
baseMetric.getValue() > 0 and
result = 100.0 * relativeMetric.getValue() / baseMetric.getValue()
}
}

signature class RankedMetric extends Metric {
int getValue();
}

module RankMetric<RankedMetric M> {
int getRank(M s) { s = rank[result](M m | | m order by m.getValue() desc) }
}

/** Various metrics we want to report. */
module CppMetrics {
class Compilations extends BaseMetric {
Compilations() { this = "compilations" }

override int getValue() { result = count(Compilation c) }
}

class SourceAndHeaderFiles extends BaseMetric {
SourceAndHeaderFiles() { this = "source/header files" }

override int getValue() { result = count(File f | f.fromSource()) }
}

class SourceAndHeaderFilesWithoutErrors extends SuccessMetric {
SourceAndHeaderFilesWithoutErrors() { this = "source/header files without errors" }

override int getValue() {
result = count(File f | f.fromSource() and not exists(CompilerError e | f = e.getFile()))
}

override SourceAndHeaderFiles getBaseline() { any() }
}

class CompilationsWithoutErrors extends SuccessMetric {
CompilationsWithoutErrors() { this = "compilations without errors" }

override int getValue() {
result = count(Compilation c | not exists(Diagnostic d | d.getFile() = c.getAFileCompiled()))
}

override Compilations getBaseline() { any() }
}

class Expressions extends BaseMetric {
Expressions() { this = "expressions" }

override int getValue() { result = count(Expr e) }
}

class SucceededExpressions extends SuccessMetric {
SucceededExpressions() { this = "non-error expressions" }

override int getValue() { result = count(Expr e) - count(ErrorExpr e) }

override Expressions getBaseline() { any() }
}

class TypedExpressions extends SuccessMetric {
TypedExpressions() { this = "expressions with a known type" }

override int getValue() { result = count(Expr e | not e.getType() instanceof ErroneousType) }

override Expressions getBaseline() { any() }
}

class Calls extends BaseMetric {
Calls() { this = "calls" }

override int getValue() { result = count(Call c) }
}

class CallsWithExplicitTarget extends SuccessMetric {
CallsWithExplicitTarget() { this = "calls with an explicit target" }

override int getValue() {
result = count(Call c | not c.getTarget().getADeclarationEntry().isImplicit())
}

override Calls getBaseline() { any() }
}

class Variables extends BaseMetric {
Variables() { this = "variables" }

override int getValue() { result = count(Variable v) }
}

class VariablesKnownType extends SuccessMetric {
VariablesKnownType() { this = "variables with a known type" }

override int getValue() {
result = count(Variable v | not v.getType() instanceof ErroneousType)
}

override Variables getBaseline() { any() }
}

class LinesOfText extends BaseMetric {
LinesOfText() { this = "lines of text" }

override int getValue() { result = sum(File f | | f.getMetrics().getNumberOfLines()) }
}

class LinesOfCode extends BaseMetric {
LinesOfCode() { this = "lines of code" }

override int getValue() { result = sum(File f | | f.getMetrics().getNumberOfLinesOfCode()) }
}

private predicate errorLine(File file, int line) {
exists(Locatable l, Location loc |
loc = l.getLocation() and
loc.getFile() = file and
line in [loc.getStartLine() .. loc.getEndLine()]
|
l instanceof Diagnostic
or
l instanceof ErrorExpr
)
}

class SucceededLines extends SuccessMetric {
SucceededLines() { this = "lines of code without errors" }

override int getValue() {
result =
sum(File f | | f.getMetrics().getNumberOfLinesOfCode()) -
count(File f, int line | errorLine(f, line))
}

override LinesOfCode getBaseline() { any() }
}

class Functions extends BaseMetric {
Functions() { this = "functions" }

override int getValue() { result = count(Function f) }
}

class SucceededFunctions extends SuccessMetric {
SucceededFunctions() { this = "functions without errors" }

override int getValue() { result = count(Function f | not f.hasErrors()) }

override Functions getBaseline() { any() }
}

class Includes extends BaseMetric {
Includes() { this = "#include directives" }

override int getValue() { result = count(Include i) + count(CannotOpenFileError e) }
}

class SucceededIncludes extends SuccessMetric {
SucceededIncludes() { this = "successfully resolved #include directives" }

override int getValue() { result = count(Include i) }

override Includes getBaseline() { any() }
}

class SucceededIncludeCount extends Metric {
string includeText;

SucceededIncludeCount() {
exists(Include i |
i.getIncludeText() = includeText and
exists(i.getFile().getRelativePath()) // Only report includes from the repo
) and
this = "Successfully included " + includeText
}

int getValue() { result = count(Include i | i.getIncludeText() = includeText) }

string getIncludeText() { result = includeText }
}

class MissingIncludeCount extends Metric {
string includeText;

MissingIncludeCount() {
exists(CannotOpenFileError e | e.getIncludedFile() = includeText) and
this = "Failed to include '" + includeText + "'"
}

int getValue() { result = count(CannotOpenFileError e | e.getIncludedFile() = includeText) }

string getIncludeText() { result = includeText }
}

class CompilerErrors extends ExtractionMetric {
CompilerErrors() { this = "compiler errors" }

override int getValue() { result = count(CompilerError e) }
}

class ErrorCount extends Metric {
ErrorCount() { exists(CompilerError e | e.getMessage() = this) }

int getValue() { result = count(CompilerError e | e.getMessage() = this) }
}

class SyntaxErrorCount extends ExtractionMetric {
SyntaxErrorCount() { this = "syntax errors" }

override int getValue() { result = count(SyntaxError e) }
}
}
13 changes: 13 additions & 0 deletions cpp/ql/src/Telemetry/MissingIncludes.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @name Failed to include header file
* @description A count of all failed includes, grouped by filename.
* @kind metric
* @tags summary telemetry
* @id cpp/telemetry/failed-includes
*/

import Metrics

from CppMetrics::MissingIncludeCount e
where RankMetric<CppMetrics::MissingIncludeCount>::getRank(e) <= 50
select e.getIncludeText(), e.getValue()
13 changes: 13 additions & 0 deletions cpp/ql/src/Telemetry/SucceededIncludes.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @name Successfully included header files
* @description A count of all succeeded includes, grouped by filename.
* @kind metric
* @tags summary telemetry
* @id cpp/telemetry/succeeded-includes
*/

import Metrics

from CppMetrics::SucceededIncludeCount m
where RankMetric<CppMetrics::SucceededIncludeCount>::getRank(m) <= 50
select m.getIncludeText(), m.getValue()
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
| 'this' may only be used inside a nonstatic member function | 1 |
| There was an error during this compilation | 1 |
| expected a ')' | 1 |
| expected a ';' | 1 |
| expected an expression | 1 |
| identifier 'no_such_function' is undefined | 1 |
| identifier 'nsf2' is undefined | 1 |
| identifier 'so_is_this' is undefined | 1 |
| identifier 'uint32_t' is undefined | 1 |
| too few arguments in function call | 1 |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Telemetry/CompilerErrors.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
| Percentage of calls with an explicit target | 50.0 |
| Percentage of compilations without errors | 50.0 |
| Percentage of expressions with a known type | 30.0 |
| Percentage of functions without errors | 75.0 |
| Percentage of lines of code without errors | 63.1578947368421 |
| Percentage of non-error expressions | 30.0 |
| Percentage of source/header files without errors | 66.66666666666667 |
| Percentage of successfully resolved #include directives | 100.0 |
| Percentage of variables with a known type | 90.0 |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Telemetry/DatabaseQuality.ql
Loading
Loading