Files
codeql/java/ql/lib/semmle/code/Location.qll
2025-07-15 10:10:32 +01:00

238 lines
7.6 KiB
Plaintext

/**
* Provides classes and predicates for working with locations.
*
* Locations represent parts of files and are used to map elements to their source location.
*/
overlay[local?]
module;
import FileSystem
import semmle.code.java.Element
private import semmle.code.java.Overlay
private import semmle.code.SMAP
/** Holds if element `e` has name `name`. */
predicate hasName(Element e, string name) {
classes_or_interfaces(e, name, _, _)
or
primitives(e, name)
or
constrs(e, name, _, _, _, _)
or
methods(e, name, _, _, _, _)
or
fields(e, name, _, _)
or
packages(e, name)
or
name = e.(File).getStem()
or
paramName(e, name)
or
exists(int pos |
params(e, _, pos, _, _) and
not paramName(e, _) and
name = "p" + pos
)
or
localvars(e, name, _, _)
or
typeVars(e, name, _, _)
or
wildcards(e, name, _)
or
arrays(e, name, _, _, _)
or
modifiers(e, name)
or
kt_type_alias(e, name, _)
or
ktProperties(e, name)
or
e instanceof ErrorType and name = "<CodeQL error type>"
}
/**
* Top is the root of the QL type hierarchy; it defines some default
* methods for obtaining locations and a standard `toString()` method.
*/
class Top extends @top {
/** Gets the source location for this element. */
Location getLocation() { fixedHasLocation(this, 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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.hasLocationInfoAux(filepath, startline, startcolumn, endline, endcolumn)
or
exists(string outFilepath, int outStartline, int outEndline |
this.hasLocationInfoAux(outFilepath, outStartline, _, outEndline, _) and
hasSmapLocationInfo(filepath, startline, startcolumn, endline, endcolumn, outFilepath,
outStartline, outEndline)
)
}
private predicate hasLocationInfoAux(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(File f, Location l | fixedHasLocation(this, l, f) |
locations_default(l, f, startline, startcolumn, endline, endcolumn) and
filepath = f.getAbsolutePath()
)
}
/** Gets the file associated with this element. */
File getFile() { fixedHasLocation(this, _, result) }
/**
* Gets the total number of lines that this element ranges over,
* including lines of code, comment and whitespace-only lines.
*/
int getTotalNumberOfLines() { numlines(this, result, _, _) }
/** Gets the number of lines of code that this element ranges over. */
int getNumberOfLinesOfCode() { numlines(this, _, result, _) }
/** Gets the number of comment lines that this element ranges over. */
int getNumberOfCommentLines() { numlines(this, _, _, result) }
/** Gets a textual representation of this element. */
cached
string toString() { hasName(this, result) }
/**
* Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs.
*/
final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
/**
* Gets the name of a primary CodeQL class to which this element belongs.
*
* For most elements, this is simply the most precise syntactic category to
* which they belong; for example, `AddExpr` is a primary class, but
* `BinaryExpr` is not.
*
* This predicate always has a result. If no primary class can be
* determined, the result is `"???"`. If multiple primary classes match,
* this predicate can have multiple results.
*/
string getAPrimaryQlClass() { result = "???" }
}
/** A location maps language elements to positions in source files. */
class Location extends @location {
/** Gets the 1-based line number (inclusive) where this location starts. */
int getStartLine() { locations_default(this, _, result, _, _, _) }
/** Gets the 1-based column number (inclusive) where this location starts. */
int getStartColumn() { locations_default(this, _, _, result, _, _) }
/** Gets the 1-based line number (inclusive) where this location ends. */
int getEndLine() { locations_default(this, _, _, _, result, _) }
/** Gets the 1-based column number (inclusive) where this location ends. */
int getEndColumn() { locations_default(this, _, _, _, _, result) }
/**
* Gets the total number of lines that this location ranges over,
* including lines of code, comment and whitespace-only lines.
*/
int getNumberOfLines() {
exists(@sourceline s | hasLocation(s, this) |
numlines(s, result, _, _)
or
not numlines(s, _, _, _) and result = 0
)
}
/** Gets the number of lines of code that this location ranges over. */
int getNumberOfLinesOfCode() {
exists(@sourceline s | hasLocation(s, this) |
numlines(s, _, result, _)
or
not numlines(s, _, _, _) and result = 0
)
}
/** Gets the number of comment lines that this location ranges over. */
int getNumberOfCommentLines() {
exists(@sourceline s | hasLocation(s, this) |
numlines(s, _, _, result)
or
not numlines(s, _, _, _) and result = 0
)
}
/** Gets the file containing this location. */
File getFile() { locations_default(this, result, _, _, _, _) }
/** Gets a string representation containing the file and range for this location. */
string toString() {
exists(string filepath, int startLine, int startCol, int endLine, int endCol |
this.hasLocationInfo(filepath, startLine, startCol, endLine, endCol)
|
toUrl(filepath, startLine, startCol, endLine, endCol, 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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(File f | locations_default(this, f, startline, startcolumn, endline, endcolumn) |
filepath = f.getAbsolutePath()
)
}
}
private predicate hasSourceLocation(Top l, Location loc, File f) {
hasLocation(l, loc) and f = loc.getFile() and f.isSourceFile()
}
cached
private predicate fixedHasLocation(Top l, Location loc, File f) {
hasSourceLocation(l, loc, f)
or
// When an entity has more than one location, as it might due to
// e.g. a parameterized generic being seen and extracted in several
// different directories or JAR files, select an arbitrary representative
// location to avoid needlessly duplicating alerts.
//
// Don't do this when the relevant location is in a source file, because
// that is much more unusual and we would rather notice the bug than mask it here.
loc =
min(Location candidateLoc |
hasLocation(l, candidateLoc)
|
candidateLoc order by candidateLoc.getFile().getAbsolutePath()
) and
not hasSourceLocation(l, _, _) and
locations_default(loc, f, _, _, _, _)
}
overlay[local]
private predicate discardableLocation(string file, @location l) {
not isOverlay() and
file = getRawFileForLoc(l) and
not exists(@file f | hasLocation(f, l))
}
/** Discard base locations in files fully extracted in the overlay. */
overlay[discard_entity]
private predicate discardLocation(@location l) {
exists(string file | discardableLocation(file, l) and overlayChangedFiles(file))
}