Files
codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFile.qll
2022-10-14 15:08:41 -04:00

299 lines
11 KiB
Plaintext

import cpp
/**
* A wrapper that provides metrics for a C/C++ file.
*/
class MetricFile extends File {
/** Gets the number of functions defined in this file. */
int getNumberOfTopLevelFunctions() {
result = count(Function f | f.isTopLevel() and f.getFile() = this)
}
/** Gets the number of classes defined in this file. */
int getNumberOfClasses() { result = count(Class c | c.getFile() = this) }
/** Gets the number of user-defined types defined in this file. */
int getNumberOfUserTypes() { result = count(UserType t | t.getFile() = this) }
/** Gets the number of lines in this file. */
int getNumberOfLines() { numlines(underlyingElement(this), result, _, _) }
/** Gets the number of lines of code in this file. */
int getNumberOfLinesOfCode() { numlines(underlyingElement(this), _, result, _) }
/** Gets the number of lines of comments in this file. */
int getNumberOfLinesOfComments() { numlines(underlyingElement(this), _, _, result) }
/** Gets the number of incoming file dependencies. */
int getAfferentCoupling() { result = count(MetricFile that | that.getAFileDependency() = this) }
/** Gets the number of outgoing file dependencies. */
int getEfferentCoupling() { result = count(MetricFile that | this.getAFileDependency() = that) }
/*
* HALSTEAD METRICS
*/
/**
* Gets the Halstead "N1" metric for this file. This is the total number of
* operators in the file. Operators are taken to be all operators in
* expressions (`+`, `*`, `&`, `->`, `=`, ...) as well as most statements.
*/
int getHalsteadN1() {
result =
sum(MetricFunction mf, int toSum | mf.getFile() = this and toSum = mf.getHalsteadN1() | toSum)
+
// Each class counts once as an operator
count(Class c | c.getFile() = this) +
// Each variable declaration that is not in a function counts once as an operator
count(GlobalVariable gv | gv.getFile() = this) +
count(MemberVariable mv | mv.getFile() = this) +
// Type declarations - to count the definition tokens
count(TypeDeclarationEntry decl | decl.getFile() = this) +
// Friend declarations
count(FriendDecl f | f.getFile() = this)
}
/**
* Gets the Halstead "N2" metric for this file: this is the total number of operands.
* An operand is either a variable, constant, type name, class name, or function name.
*/
int getHalsteadN2() {
result =
sum(MetricFunction mf, int toSum | mf.getFile() = this and toSum = mf.getHalsteadN2() | toSum)
+
// Each class counts once as an operand
count(Class c | c.getFile() = this) +
// Each variable declaration that is not in a function counts once as an operand
count(GlobalVariable gv | gv.getFile() = this) +
count(MemberVariable mv | mv.getFile() = this) +
// Type declarations - to count the type names
count(TypeDeclarationEntry decl | decl.getFile() = this) +
// Enum constant declarations to count the name
count(EnumConstant ec | ec.getFile() = this)
}
private string getAUsedHalsteadN1Operator() {
exists(CommaExpr e | e.getFile() = this) and result = "comma"
or
exists(ReferenceToExpr e | e.getFile() = this) and result = "refTo"
or
exists(PointerDereferenceExpr e | e.getFile() = this) and result = "dereference"
or
exists(CStyleCast e | e.getFile() = this) and result = "cCast"
or
exists(StaticCast e | e.getFile() = this) and result = "staticCast"
or
exists(ConstCast e | e.getFile() = this) and result = "constCast"
or
exists(ReinterpretCast e | e.getFile() = this) and result = "reinterpretCast"
or
exists(DynamicCast e | e.getFile() = this) and result = "dynamicCast"
or
exists(SizeofExprOperator e | e.getFile() = this) and result = "sizeofExpr"
or
exists(SizeofTypeOperator e | e.getFile() = this) and result = "sizeofType"
or
exists(IfStmt e | e.getFile() = this) and result = "ifVal"
or
exists(SwitchStmt e | e.getFile() = this) and result = "switchVal"
or
exists(ForStmt e | e.getFile() = this) and result = "forVal"
or
exists(DoStmt e | e.getFile() = this) and result = "doVal"
or
exists(WhileStmt e | e.getFile() = this) and result = "whileVal"
or
exists(GotoStmt e | e.getFile() = this) and result = "gotoVal"
or
exists(ContinueStmt e | e.getFile() = this) and result = "continueVal"
or
exists(BreakStmt e | e.getFile() = this) and result = "breakVal"
or
exists(ReturnStmt e | e.getFile() = this) and result = "returnVal"
or
exists(SwitchCase e | e.getFile() = this) and result = "caseVal"
or
exists(IfStmt s | s.getFile() = this and s.hasElse()) and
result = "elseVal"
or
exists(Function f | f.getFile() = this) and result = "function"
or
exists(Class c | c.getFile() = this) and result = "classDef"
or
exists(TypeDeclarationEntry e | e.getFile() = this) and result = "typeDecl"
or
exists(FriendDecl e | e.getFile() = this) and result = "friendDecl"
}
/**
* Gets the Halstead "n1" metric: this is the total number of distinct operators
* in this file. Operators are defined as in the "N1" metric (`getHalsteadN1`).
*/
int getHalsteadN1Distinct() {
result =
// avoid 0 values
1 + count(string s | exists(Operation op | op.getFile() = this and s = op.getOperator())) +
count(string s | s = this.getAUsedHalsteadN1Operator())
}
/**
* Gets the Halstead "n2" metric: this is the number of distinct operands in this
* file. An operand is either a variable, constant, type name, or function name.
*/
int getHalsteadN2Distinct() {
result =
// avoid 0 values
1 + count(string s | exists(Access a | a.getFile() = this and s = a.getTarget().getName())) +
count(Function f | exists(FunctionCall fc | fc.getFile() = this and f = fc.getTarget())) +
// Approximate: count declarations once more to account for the type name
count(Declaration d | d.getFile() = this)
}
/**
* Gets the Halstead length of this file. This is the sum of the N1 and N2 Halstead metrics.
*/
int getHalsteadLength() { result = this.getHalsteadN1() + this.getHalsteadN2() }
/**
* Gets the Halstead vocabulary size of this file. This is the sum of the n1 and n2 Halstead metrics.
*/
int getHalsteadVocabulary() {
result = this.getHalsteadN1Distinct() + this.getHalsteadN2Distinct()
}
/**
* Gets the Halstead volume of this file. This is the Halstead size multiplied by the log of the
* Halstead vocabulary. It represents the information content of the file.
*/
float getHalsteadVolume() {
result = this.getHalsteadLength().(float) * this.getHalsteadVocabulary().log2()
}
/**
* Gets the Halstead difficulty value of this file. This is proportional to the number of unique
* operators, and further proportional to the ratio of total operands to unique operands.
*/
float getHalsteadDifficulty() {
result =
(this.getHalsteadN1Distinct() * this.getHalsteadN2()).(float) /
(2 * this.getHalsteadN2Distinct()).(float)
}
/**
* Gets the Halstead level of this file. This is the inverse of the difficulty of the file.
*/
float getHalsteadLevel() {
exists(float difficulty |
difficulty = this.getHalsteadDifficulty() and
if difficulty != 0.0 then result = 1.0 / difficulty else result = 0.0
)
}
/**
* Gets the Halstead implementation effort for this file. This is the product of the volume and difficulty.
*/
float getHalsteadEffort() { result = this.getHalsteadVolume() * this.getHalsteadDifficulty() }
/**
* Gets the Halstead 'delivered bugs' metric for this file. This metric correlates with the complexity of
* the software, but is known to be an underestimate of bug counts.
*/
float getHalsteadDeliveredBugs() { result = this.getHalsteadEffort().pow(2.0 / 3.0) / 3000.0 }
/** Gets a file dependency of this file. */
File getAFileDependency() { dependsOnFileSimple(this, result.getMetrics()) }
}
private predicate aClassFile(Class c, File file) { c.getDefinitionLocation().getFile() = file }
pragma[noopt]
private predicate dependsOnFileSimple(MetricFile source, MetricFile dest) {
// class derives from another class
exists(Class fromClass, Class toClass |
aClassFile(fromClass, source) and
fromClass.derivesFrom(toClass) and
aClassFile(toClass, dest)
)
or
// class nested in another class
exists(Class fromClass, Class toClass |
aClassFile(fromClass, source) and
fromClass.getDeclaringType() = toClass and
toClass.getFile() = dest
)
or
// class has friend class
exists(Class fromClass, Class toClass, FriendDecl fd |
aClassFile(fromClass, source) and
fromClass.getAFriendDecl() = fd and
fd.getFriend() = toClass and
toClass instanceof Class and
dest = toClass.getFile()
)
or
exists(FunctionCall ca, Function f |
ca instanceof FunctionCall and
ca.getFile() = source and
ca.getTarget() = f and
f.getFile() = dest and
not f.isMultiplyDefined() and
not exists(Function ef | ef = ca.getEnclosingFunction() and ef.isMultiplyDefined())
)
or
exists(Access a, Declaration d |
a instanceof Access and
a.getFile() = source and
a.getTarget() = d and
d.getFile() = dest and
not exists(Function ef | ef = a.getEnclosingFunction() and ef.isMultiplyDefined())
)
or
exists(Variable v, VariableDeclarationEntry e, Type vt, UserType t |
e instanceof VariableDeclarationEntry and
e.getFile() = source and
v.getADeclarationEntry() = e and
vt = v.getType() and
vt.refersTo(t) and
t instanceof UserType and
t.getFile() = dest
)
or
exists(Function f, FunctionDeclarationEntry e, Type ft, UserType t |
e instanceof FunctionDeclarationEntry and
e.getFile() = source and
f.getADeclarationEntry() = e and
ft = f.getType() and
ft.refersTo(t) and
t instanceof UserType and
t.getFile() = dest
)
or
exists(MacroInvocation mi, Macro m |
mi instanceof MacroInvocation and
mi.getFile() = source and
mi.getMacro() = m and
m.getFile() = dest
)
or
exists(TypedefType t, TypeDeclarationEntry e, Type bt, UserType u |
e instanceof TypeDeclarationEntry and
e.getFile() = source and
t.getADeclarationEntry() = e and
bt = t.getBaseType() and
bt.refersTo(u) and
u instanceof UserType and
u.getFile() = dest
)
or
exists(Cast c, Type t, UserType u |
c instanceof Cast and
c.getFile() = source and
c.getType() = t and
t.refersTo(u) and
u instanceof UserType and
u.getFile() = dest and
not exists(Function ef | ef = c.getEnclosingFunction() and ef.isMultiplyDefined())
)
}