mirror of
https://github.com/github/codeql.git
synced 2025-12-24 12:46:34 +01:00
Quoting the style guide: "14. _Always_ qualify _calls_ to predicates of the same class with `this`."
286 lines
9.1 KiB
Plaintext
286 lines
9.1 KiB
Plaintext
import python
|
|
private import semmle.python.objects.ObjectAPI
|
|
private import semmle.python.objects.Modules
|
|
|
|
/**
|
|
* A module. This is the top level element in an AST, corresponding to a source file.
|
|
* It is also a Scope; the scope of global variables.
|
|
*/
|
|
class Module extends Module_, Scope, AstNode {
|
|
override string toString() {
|
|
result = this.getKind() + " " + this.getName()
|
|
or
|
|
/* No name is defined, which means that this module is not on an import path. So it must be a script */
|
|
not exists(this.getName()) and
|
|
not this.isPackage() and
|
|
result = "Script " + this.getFile().getShortName()
|
|
or
|
|
/* Package missing name, so just use the path instead */
|
|
not exists(this.getName()) and
|
|
this.isPackage() and
|
|
result = "Package at " + this.getPath().getAbsolutePath()
|
|
}
|
|
|
|
/**
|
|
* This method will be deprecated in the next release. Please use `getEnclosingScope()` instead.
|
|
* The enclosing scope of this module (always none)
|
|
*/
|
|
override Scope getScope() { none() }
|
|
|
|
/** The enclosing scope of this module (always none) */
|
|
override Scope getEnclosingScope() { none() }
|
|
|
|
/** Gets the statements forming the body of this module */
|
|
override StmtList getBody() { result = Module_.super.getBody() }
|
|
|
|
/** Gets the nth statement of this module */
|
|
override Stmt getStmt(int n) { result = Module_.super.getStmt(n) }
|
|
|
|
/** Gets a top-level statement in this module */
|
|
override Stmt getAStmt() { result = Module_.super.getAStmt() }
|
|
|
|
/** Gets the name of this module */
|
|
override string getName() {
|
|
result = Module_.super.getName() and legalDottedName(result)
|
|
or
|
|
not exists(Module_.super.getName()) and
|
|
result = moduleNameFromFile(this.getPath())
|
|
}
|
|
|
|
/** Gets the short name of the module. For example the short name of module x.y.z is 'z' */
|
|
string getShortName() {
|
|
result = this.getName().suffix(this.getPackage().getName().length() + 1)
|
|
or
|
|
result = this.getName() and not exists(this.getPackage())
|
|
}
|
|
|
|
/** Gets this module */
|
|
override Module getEnclosingModule() { result = this }
|
|
|
|
/** Gets the __init__ module of this module if the module is a package and it has an __init__ module */
|
|
Module getInitModule() {
|
|
/* this.isPackage() and */ result.getName() = this.getName() + ".__init__"
|
|
}
|
|
|
|
/** Whether this module is a package initializer */
|
|
predicate isPackageInit() { this.getName().matches("%\\_\\_init\\_\\_") and not this.isPackage() }
|
|
|
|
/** Gets a name exported by this module, that is the names that will be added to a namespace by 'from this-module import *' */
|
|
string getAnExport() {
|
|
py_exports(this, result)
|
|
or
|
|
exists(ModuleObjectInternal mod | mod.getSource() = this.getEntryNode() |
|
|
mod.(ModuleValue).exports(result)
|
|
)
|
|
}
|
|
|
|
/** Gets the source file for this module */
|
|
File getFile() { py_module_path(this, result) }
|
|
|
|
/** Gets the source file or folder for this module or package */
|
|
Container getPath() { py_module_path(this, result) }
|
|
|
|
/** Whether this is a package */
|
|
predicate isPackage() { this.getPath() instanceof Folder }
|
|
|
|
/** Gets the package containing this module (or parent package if this is a package) */
|
|
Module getPackage() {
|
|
this.getName().matches("%.%") and
|
|
result.getName() = this.getName().regexpReplaceAll("\\.[^.]*$", "")
|
|
}
|
|
|
|
/** Gets the name of the package containing this module */
|
|
string getPackageName() {
|
|
this.getName().matches("%.%") and
|
|
result = this.getName().regexpReplaceAll("\\.[^.]*$", "")
|
|
}
|
|
|
|
/** Gets the metrics for this module */
|
|
ModuleMetrics getMetrics() { result = this }
|
|
|
|
/**
|
|
* Use ModuleObject.getAnImportedModule() instead.
|
|
* Gets a module imported by this module
|
|
*/
|
|
deprecated Module getAnImportedModule() { result.getName() = this.getAnImportedModuleName() }
|
|
|
|
string getAnImportedModuleName() {
|
|
exists(Import i | i.getEnclosingModule() = this | result = i.getAnImportedModuleName())
|
|
or
|
|
exists(ImportStar i | i.getEnclosingModule() = this | result = i.getImportedModuleName())
|
|
}
|
|
|
|
override Location getLocation() {
|
|
py_scope_location(result, this)
|
|
or
|
|
not py_scope_location(_, this) and
|
|
locations_ast(result, this, 0, 0, 0, 0)
|
|
}
|
|
|
|
/** Gets a child module or package of this package */
|
|
Module getSubModule(string name) {
|
|
result.getPackage() = this and
|
|
name = result.getName().regexpReplaceAll(".*\\.", "")
|
|
}
|
|
|
|
/** Whether name is declared in the __all__ list of this module */
|
|
predicate declaredInAll(string name) {
|
|
exists(AssignStmt a, GlobalVariable all |
|
|
a.defines(all) and
|
|
a.getScope() = this and
|
|
all.getId() = "__all__" and
|
|
(
|
|
a.getValue().(List).getAnElt().(StrConst).getText() = name
|
|
or
|
|
a.getValue().(Tuple).getAnElt().(StrConst).getText() = name
|
|
)
|
|
)
|
|
}
|
|
|
|
override AstNode getAChildNode() { result = this.getAStmt() }
|
|
|
|
predicate hasFromFuture(string attr) {
|
|
exists(Import i, ImportMember im, ImportExpr ie, Alias a, Name name |
|
|
im.getModule() = ie and
|
|
ie.getName() = "__future__" and
|
|
a.getAsname() = name and
|
|
name.getId() = attr and
|
|
i.getASubExpression() = im and
|
|
i.getAName() = a and
|
|
i.getEnclosingModule() = this
|
|
)
|
|
}
|
|
|
|
/** Gets the path element from which this module was loaded. */
|
|
Container getLoadPath() { result = this.getPath().getImportRoot() }
|
|
|
|
/** Holds if this module is in the standard library for version `major.minor` */
|
|
predicate inStdLib(int major, int minor) { this.getLoadPath().isStdLibRoot(major, minor) }
|
|
|
|
/** Holds if this module is in the standard library */
|
|
predicate inStdLib() { this.getLoadPath().isStdLibRoot() }
|
|
|
|
override predicate containsInScope(AstNode inner) { Scope.super.containsInScope(inner) }
|
|
|
|
override predicate contains(AstNode inner) { Scope.super.contains(inner) }
|
|
|
|
/** Gets the kind of this module. */
|
|
override string getKind() {
|
|
if this.isPackage()
|
|
then result = "Package"
|
|
else (
|
|
not exists(Module_.super.getKind()) and result = "Module"
|
|
or
|
|
result = Module_.super.getKind()
|
|
)
|
|
}
|
|
}
|
|
|
|
bindingset[name]
|
|
private predicate legalDottedName(string name) {
|
|
name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*(\\.(\\p{L}|_)(\\p{L}|\\d|_)*)*")
|
|
}
|
|
|
|
bindingset[name]
|
|
private predicate legalShortName(string name) { name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*") }
|
|
|
|
/**
|
|
* Holds if `f` is potentially a source package.
|
|
* Does it have an __init__.py file (or --respect-init=False for Python 2) and is it within the source archive?
|
|
*/
|
|
private predicate isPotentialSourcePackage(Folder f) {
|
|
f.getRelativePath() != "" and
|
|
isPotentialPackage(f)
|
|
}
|
|
|
|
private predicate isPotentialPackage(Folder f) {
|
|
exists(f.getFile("__init__.py"))
|
|
or
|
|
py_flags_versioned("options.respect_init", "False", _) and major_version() = 2
|
|
}
|
|
|
|
private string moduleNameFromBase(Container file) {
|
|
isPotentialPackage(file) and result = file.getBaseName()
|
|
or
|
|
file instanceof File and result = file.getStem()
|
|
}
|
|
|
|
/**
|
|
* Holds if `file` may be transitively imported from a file that may serve as the entry point of
|
|
* the execution.
|
|
*/
|
|
private predicate transitively_imported_from_entry_point(File file) {
|
|
file.getExtension().matches("%py%") and
|
|
exists(File importer |
|
|
// Only consider files that are in the source archive
|
|
exists(importer.getRelativePath()) and
|
|
importer.getParent() = file.getParent() and
|
|
exists(ImportExpr i |
|
|
i.getLocation().getFile() = importer and
|
|
i.getName() = file.getStem() and
|
|
// Disregard relative imports
|
|
i.getLevel() = 0
|
|
)
|
|
|
|
|
importer.isPossibleEntryPoint() or transitively_imported_from_entry_point(importer)
|
|
)
|
|
}
|
|
|
|
string moduleNameFromFile(Container file) {
|
|
exists(string basename |
|
|
basename = moduleNameFromBase(file) and
|
|
legalShortName(basename)
|
|
|
|
|
result = moduleNameFromFile(file.getParent()) + "." + basename
|
|
or
|
|
// If `file` is a transitive import of a file that's executed directly, we allow references
|
|
// to it by its `basename`.
|
|
transitively_imported_from_entry_point(file) and
|
|
result = basename
|
|
)
|
|
or
|
|
isPotentialSourcePackage(file) and
|
|
result = file.getStem() and
|
|
(
|
|
not isPotentialSourcePackage(file.getParent()) or
|
|
not legalShortName(file.getParent().getBaseName())
|
|
)
|
|
or
|
|
result = file.getStem() and file.getParent() = file.getImportRoot()
|
|
or
|
|
result = file.getStem() and isStubRoot(file.getParent())
|
|
}
|
|
|
|
private predicate isStubRoot(Folder f) {
|
|
not f.getParent*().isImportRoot() and
|
|
f.getAbsolutePath().matches("%/data/python/stubs")
|
|
}
|
|
|
|
/**
|
|
* Holds if the Container `c` should be the preferred file or folder for
|
|
* the given name when performing imports.
|
|
* Trivially true for any container if it is the only one with its name.
|
|
* However, if there are several modules with the same name, then
|
|
* this is the module most likely to be imported under that name.
|
|
*/
|
|
predicate isPreferredModuleForName(Container c, string name) {
|
|
exists(int p |
|
|
p = min(int x | x = priorityForName(_, name)) and
|
|
p = priorityForName(c, name)
|
|
)
|
|
}
|
|
|
|
private int priorityForName(Container c, string name) {
|
|
name = moduleNameFromFile(c) and
|
|
(
|
|
// In the source
|
|
exists(c.getRelativePath()) and result = -1
|
|
or
|
|
// On an import path
|
|
exists(c.getImportRoot(result))
|
|
or
|
|
// Otherwise
|
|
result = 10000
|
|
)
|
|
}
|