Files
codeql/javascript/ql/src/external/CodeDuplication.qll
Jonas Jensen 6fc9e1d84c C++/JavaScript: Improve CodeDuplication.qll QLDoc
I took most of the docs from the corresponding predicates in
JavaScript's `CodeDuplication.qll`. Where JavaScript had a corresponding
predicate but didn't have QLDoc, I added new QLDoc to both.
2020-05-25 18:59:48 +02:00

368 lines
13 KiB
Plaintext

/** Provides classes for detecting duplicate or similar code. */
import semmle.javascript.Files
/** Gets the relative path of `file`, with backslashes replaced by forward slashes. */
private string relativePath(File file) { result = file.getRelativePath().replaceAll("\\", "/") }
/**
* Holds if the `index`-th token of block `copy` is in file `file`, spanning
* column `sc` of line `sl` to column `ec` of line `el`.
*
* For more information, see [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
pragma[noinline]
private predicate tokenLocation(File file, int sl, int sc, int ec, int el, Copy copy, int index) {
file = copy.sourceFile() and
tokens(copy, index, sl, sc, ec, el)
}
/** A token block used for detection of duplicate and similar code. */
class Copy extends @duplication_or_similarity {
/** Gets the index of the last token in this block. */
private int lastToken() { result = max(int i | tokens(this, i, _, _, _, _) | i) }
/** Gets the index of the token in this block starting at the location `loc`, if any. */
int tokenStartingAt(Location loc) {
tokenLocation(loc.getFile(), loc.getStartLine(), loc.getStartColumn(), _, _, this, result)
}
/** Gets the index of the token in this block ending at the location `loc`, if any. */
int tokenEndingAt(Location loc) {
tokenLocation(loc.getFile(), _, _, loc.getEndLine(), loc.getEndColumn(), this, result)
}
/** Gets the line on which the first token in this block starts. */
int sourceStartLine() { tokens(this, 0, result, _, _, _) }
/** Gets the column on which the first token in this block starts. */
int sourceStartColumn() { tokens(this, 0, _, result, _, _) }
/** Gets the line on which the last token in this block ends. */
int sourceEndLine() { tokens(this, this.lastToken(), _, _, result, _) }
/** Gets the column on which the last token in this block ends. */
int sourceEndColumn() { tokens(this, this.lastToken(), _, _, _, result) }
/** Gets the number of lines containing at least (part of) one token in this block. */
int sourceLines() { result = this.sourceEndLine() + 1 - this.sourceStartLine() }
/** Gets an opaque identifier for the equivalence class of this block. */
int getEquivalenceClass() { duplicateCode(this, _, result) or similarCode(this, _, result) }
/** Gets the source file in which this block appears. */
File sourceFile() {
exists(string name | duplicateCode(this, name, _) or similarCode(this, name, _) |
name.replaceAll("\\", "/") = relativePath(result)
)
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
sourceFile().getAbsolutePath() = filepath and
startline = sourceStartLine() and
startcolumn = sourceStartColumn() and
endline = sourceEndLine() and
endcolumn = sourceEndColumn()
}
/** Gets a textual representation of this element. */
string toString() { none() }
/**
* Gets a block that extends this one, that is, its first token is also
* covered by this block, but they are not the same block.
*/
Copy extendingBlock() {
exists(File file, int sl, int sc, int ec, int el |
tokenLocation(file, sl, sc, ec, el, this, _) and
tokenLocation(file, sl, sc, ec, el, result, 0)
) and
this != result
}
}
/**
* Holds if there is a sequence of `SimilarBlock`s `start1, ..., end1` and another sequence
* `start2, ..., end2` such that each block extends the previous one and corresponding blocks
* have the same equivalence class, with `start` being the equivalence class of `start1` and
* `start2`, and `end` the equivalence class of `end1` and `end2`.
*/
predicate similar_extension(
SimilarBlock start1, SimilarBlock start2, SimilarBlock end1, SimilarBlock end2, int start, int end
) {
start1.getEquivalenceClass() = start and
start2.getEquivalenceClass() = start and
end1.getEquivalenceClass() = end and
end2.getEquivalenceClass() = end and
start1 != start2 and
(
end1 = start1 and end2 = start2
or
similar_extension(start1.extendingBlock(), start2.extendingBlock(), end1, end2, _, end)
)
}
/**
* Holds if there is a sequence of `DuplicateBlock`s `start1, ..., end1` and another sequence
* `start2, ..., end2` such that each block extends the previous one and corresponding blocks
* have the same equivalence class, with `start` being the equivalence class of `start1` and
* `start2`, and `end` the equivalence class of `end1` and `end2`.
*/
predicate duplicate_extension(
DuplicateBlock start1, DuplicateBlock start2, DuplicateBlock end1, DuplicateBlock end2, int start,
int end
) {
start1.getEquivalenceClass() = start and
start2.getEquivalenceClass() = start and
end1.getEquivalenceClass() = end and
end2.getEquivalenceClass() = end and
start1 != start2 and
(
end1 = start1 and end2 = start2
or
duplicate_extension(start1.extendingBlock(), start2.extendingBlock(), end1, end2, _, end)
)
}
/** A block of duplicated code. */
class DuplicateBlock extends Copy, @duplication {
override string toString() { result = "Duplicate code: " + sourceLines() + " duplicated lines." }
}
/** A block of similar code. */
class SimilarBlock extends Copy, @similarity {
override string toString() {
result = "Similar code: " + sourceLines() + " almost duplicated lines."
}
}
/** Holds if statement `s` is in function or toplevel `sc`. */
private predicate stmtInContainer(Stmt s, StmtContainer sc) {
s.getContainer() = sc and
not s instanceof BlockStmt
}
/**
* Holds if `stmt1` and `stmt2` are duplicate statements in function or toplevel `sc1` and `sc2`,
* respectively, where `sc1` and `sc2` are not the same.
*/
predicate duplicateStatement(StmtContainer sc1, StmtContainer sc2, Stmt stmt1, Stmt stmt2) {
exists(int equivstart, int equivend, int first, int last |
stmtInContainer(stmt1, sc1) and
stmtInContainer(stmt2, sc2) and
duplicateCoversStatement(equivstart, equivend, first, last, stmt1) and
duplicateCoversStatement(equivstart, equivend, first, last, stmt2) and
stmt1 != stmt2 and
sc1 != sc2
)
}
/**
* Holds if statement `stmt` is covered by a sequence of `DuplicateBlock`s, where `first`
* is the index of the token in the first block that starts at the beginning of `stmt`,
* while `last` is the index of the token in the last block that ends at the end of `stmt`,
* and `equivstart` and `equivend` are the equivalence classes of the first and the last
* block, respectively.
*/
private predicate duplicateCoversStatement(
int equivstart, int equivend, int first, int last, Stmt stmt
) {
exists(DuplicateBlock b1, DuplicateBlock b2, Location loc |
stmt.getLocation() = loc and
first = b1.tokenStartingAt(loc) and
last = b2.tokenEndingAt(loc) and
b1.getEquivalenceClass() = equivstart and
b2.getEquivalenceClass() = equivend and
duplicate_extension(b1, _, b2, _, equivstart, equivend)
)
}
/**
* Holds if `sc1` is a function or toplevel with `total` lines, and `sc2` is a function or
* toplevel that has `duplicate` lines in common with `sc1`.
*/
private predicate duplicateStatements(StmtContainer sc1, StmtContainer sc2, int duplicate, int total) {
duplicate = strictcount(Stmt stmt | duplicateStatement(sc1, sc2, stmt, _)) and
total = strictcount(Stmt stmt | stmtInContainer(stmt, sc1))
}
/**
* Holds if `sc` and `other` are functions or toplevels where `percent` is the percentage
* of lines they have in common, which is greater than 90%.
*/
predicate duplicateContainers(StmtContainer sc, StmtContainer other, float percent) {
exists(int total, int duplicate | duplicateStatements(sc, other, duplicate, total) |
percent = 100.0 * duplicate / total and
percent > 90.0
)
}
/**
* Holds if `stmt1` and `stmt2` are similar statements in function or toplevel `sc1` and `sc2`,
* respectively, where `sc1` and `sc2` are not the same.
*/
private predicate similarStatement(StmtContainer sc1, StmtContainer sc2, Stmt stmt1, Stmt stmt2) {
exists(int start, int end, int first, int last |
stmtInContainer(stmt1, sc1) and
stmtInContainer(stmt2, sc2) and
similarCoversStatement(start, end, first, last, stmt1) and
similarCoversStatement(start, end, first, last, stmt2) and
stmt1 != stmt2 and
sc1 != sc2
)
}
/**
* Holds if statement `stmt` is covered by a sequence of `SimilarBlock`s, where `first`
* is the index of the token in the first block that starts at the beginning of `stmt`,
* while `last` is the index of the token in the last block that ends at the end of `stmt`,
* and `equivstart` and `equivend` are the equivalence classes of the first and the last
* block, respectively.
*/
private predicate similarCoversStatement(
int equivstart, int equivend, int first, int last, Stmt stmt
) {
exists(SimilarBlock b1, SimilarBlock b2, Location loc |
stmt.getLocation() = loc and
first = b1.tokenStartingAt(loc) and
last = b2.tokenEndingAt(loc) and
b1.getEquivalenceClass() = equivstart and
b2.getEquivalenceClass() = equivend and
similar_extension(b1, _, b2, _, equivstart, equivend)
)
}
/**
* Holds if `sc1` is a function or toplevel with `total` lines, and `sc2` is a function or
* toplevel that has `similar` similar lines to `sc1`.
*/
private predicate similarStatements(StmtContainer sc1, StmtContainer sc2, int similar, int total) {
similar = strictcount(Stmt stmt | similarStatement(sc1, sc2, stmt, _)) and
total = strictcount(Stmt stmt | stmtInContainer(stmt, sc1))
}
/**
* Holds if `sc` and `other` are functions or toplevels where `percent` is the percentage
* of similar lines between the two, which is greater than 90%.
*/
predicate similarContainers(StmtContainer sc, StmtContainer other, float percent) {
exists(int total, int similar | similarStatements(sc, other, similar, total) |
percent = 100.0 * similar / total and
percent > 90.0
)
}
/**
* INTERNAL: do not use.
*
* Holds if `line` in `f` is similar to a line somewhere else.
*/
predicate similarLines(File f, int line) {
exists(SimilarBlock b | b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()])
}
private predicate similarLinesPerEquivalenceClass(int equivClass, int lines, File f) {
lines =
strictsum(SimilarBlock b, int toSum |
(b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and
toSum = b.sourceLines()
|
toSum
)
}
/** Holds if `coveredLines` lines of `f` are similar to lines in `otherFile`. */
pragma[noopt]
private predicate similarLinesCovered(File f, int coveredLines, File otherFile) {
exists(int numLines | numLines = f.getNumberOfLines() |
exists(int coveredApprox |
coveredApprox =
strictsum(int num |
exists(int equivClass |
similarLinesPerEquivalenceClass(equivClass, num, f) and
similarLinesPerEquivalenceClass(equivClass, num, otherFile) and
f != otherFile
)
) and
exists(int n, int product | product = coveredApprox * 100 and n = product / numLines | n > 75)
) and
exists(int notCovered |
notCovered = count(int j | j in [1 .. numLines] and not similarLines(f, j)) and
coveredLines = numLines - notCovered
)
)
}
/**
* INTERNAL: do not use.
*
* Holds if `line` in `f` is duplicated by a line somewhere else.
*/
predicate duplicateLines(File f, int line) {
exists(DuplicateBlock b |
b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()]
)
}
private predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, File f) {
lines =
strictsum(DuplicateBlock b, int toSum |
(b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and
toSum = b.sourceLines()
|
toSum
)
}
/** Holds if `coveredLines` lines of `f` are duplicates of lines in `otherFile`. */
pragma[noopt]
private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) {
exists(int numLines | numLines = f.getNumberOfLines() |
exists(int coveredApprox |
coveredApprox =
strictsum(int num |
exists(int equivClass |
duplicateLinesPerEquivalenceClass(equivClass, num, f) and
duplicateLinesPerEquivalenceClass(equivClass, num, otherFile) and
f != otherFile
)
) and
exists(int n, int product | product = coveredApprox * 100 and n = product / numLines | n > 75)
) and
exists(int notCovered |
notCovered = count(int j | j in [1 .. numLines] and not duplicateLines(f, j)) and
coveredLines = numLines - notCovered
)
)
}
/** Holds if most of `f` (`percent`%) is similar to `other`. */
predicate similarFiles(File f, File other, int percent) {
exists(int covered, int total |
similarLinesCovered(f, covered, other) and
total = f.getNumberOfLines() and
covered * 100 / total = percent and
percent > 80
) and
not duplicateFiles(f, other, _)
}
/** Holds if most of `f` (`percent`%) is duplicated by `other`. */
predicate duplicateFiles(File f, File other, int percent) {
exists(int covered, int total |
duplicateLinesCovered(f, covered, other) and
total = f.getNumberOfLines() and
covered * 100 / total = percent and
percent > 70
)
}