Skip to content

Commit 8d15680

Browse files
authored
Merge pull request #5506 from tausbn/python-allow-absolute-imports-from-source-directory
Python: Allow absolute imports in directories with scripts
2 parents 63831cc + 47686a6 commit 8d15680

26 files changed

+108
-1
lines changed

python/ql/src/semmle/python/Files.qll

+30
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,33 @@ class File extends Container {
7272
* are specified to be extracted.
7373
*/
7474
string getContents() { file_contents(this, result) }
75+
76+
/** Holds if this file is likely to get executed directly, and thus act as an entry point for execution. */
77+
predicate maybeExecutedDirectly() {
78+
// Only consider files in the source code, and not things like the standard library
79+
exists(this.getRelativePath()) and
80+
(
81+
// The file doesn't have the extension `.py` but still contains Python statements
82+
not this.getExtension().matches("py%") and
83+
exists(Stmt s | s.getLocation().getFile() = this)
84+
or
85+
// The file contains the usual `if __name__ == '__main__':` construction
86+
exists(If i, Name name, StrConst main, Cmpop op |
87+
i.getScope().(Module).getFile() = this and
88+
op instanceof Eq and
89+
i.getTest().(Compare).compares(name, op, main) and
90+
name.getId() = "__name__" and
91+
main.getText() = "__main__"
92+
)
93+
or
94+
// The file contains a `#!` line referencing the python interpreter
95+
exists(Comment c |
96+
c.getLocation().getFile() = this and
97+
c.getLocation().getStartLine() = 1 and
98+
c.getText().regexpMatch("^#! */.*python(2|3)?[ \\\\t]*$")
99+
)
100+
)
101+
}
75102
}
76103

77104
private predicate occupied_line(File f, int n) {
@@ -121,6 +148,9 @@ class Folder extends Container {
121148
this.getBaseName().regexpMatch("[^\\d\\W]\\w*") and
122149
result = this.getParent().getImportRoot(n)
123150
}
151+
152+
/** Holds if execution may start in a file in this directory. */
153+
predicate mayContainEntryPoint() { any(File f | f.getParent() = this).maybeExecutedDirectly() }
124154
}
125155

126156
/**

python/ql/src/semmle/python/Module.qll

+6-1
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,13 @@ private string moduleNameFromBase(Container file) {
204204
string moduleNameFromFile(Container file) {
205205
exists(string basename |
206206
basename = moduleNameFromBase(file) and
207-
legalShortName(basename) and
207+
legalShortName(basename)
208+
|
208209
result = moduleNameFromFile(file.getParent()) + "." + basename
210+
or
211+
// If execution can start in the folder containing this module, then we will assume `file` can
212+
// be imported as an absolute import, and hence return `basename` as a possible name.
213+
file.getParent().(Folder).mayContainEntryPoint() and result = basename
209214
)
210215
or
211216
isPotentialSourcePackage(file) and
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#! /usr/bin/python3
2+
print(__file__)
3+
import module
4+
import package
5+
import namespace_package
6+
import namespace_package.namespace_package_main
7+
print(module.message)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
message = "Hello world!"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
import namespace_package.namespace_package_module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print(__file__.split("entry_point")[1])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
from . import package_main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
from . import package_module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print(__file__.split("entry_point")[1])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
| main | hash_bang/main.py:0:0:0:0 | Script main |
2+
| main | name_main/main.py:0:0:0:0 | Module main |
3+
| module | hash_bang/module.py:0:0:0:0 | Module module |
4+
| module | name_main/module.py:0:0:0:0 | Module module |
5+
| package | hash_bang/package:0:0:0:0 | Package package |
6+
| package | name_main/package:0:0:0:0 | Package package |
7+
| package | no_py_extension/package:0:0:0:0 | Package package |
8+
| package.__init__ | hash_bang/package/__init__.py:0:0:0:0 | Module package.__init__ |
9+
| package.__init__ | name_main/package/__init__.py:0:0:0:0 | Module package.__init__ |
10+
| package.__init__ | no_py_extension/package/__init__.py:0:0:0:0 | Module package.__init__ |
11+
| package.package_main | hash_bang/package/package_main.py:0:0:0:0 | Module package.package_main |
12+
| package.package_main | name_main/package/package_main.py:0:0:0:0 | Module package.package_main |
13+
| package.package_main | no_py_extension/package/package_main.py:0:0:0:0 | Module package.package_main |
14+
| package.package_module | hash_bang/package/package_module.py:0:0:0:0 | Module package.package_module |
15+
| package.package_module | name_main/package/package_module.py:0:0:0:0 | Module package.package_module |
16+
| package.package_module | no_py_extension/package/package_module.py:0:0:0:0 | Module package.package_module |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import python
2+
3+
from Module m
4+
select m.getName(), m
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
print(__file__)
2+
import module
3+
import package
4+
import namespace_package
5+
import namespace_package.namespace_package_main
6+
7+
if __name__ == '__main__':
8+
print(module.message)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
message = "Hello world!"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
import namespace_package.namespace_package_module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print(__file__.split("entry_point")[1])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
from . import package_main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
from . import package_module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print(__file__.split("entry_point")[1])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
print(__file__)
2+
import module
3+
import package
4+
import namespace_package
5+
import namespace_package.namespace_package_main
6+
print(module.message)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
message = "Hello world!"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
import namespace_package.namespace_package_module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print(__file__.split("entry_point")[1])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
from . import package_main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print(__file__.split("entry_point")[1])
2+
from . import package_module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print(__file__.split("entry_point")[1])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
semmle-extractor-options: --lang=3 --path bogus -R . --filter=include:**/*.secretpy

0 commit comments

Comments
 (0)