mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Merge branch 'master' into range-analysis
This commit is contained in:
19
change-notes/1.20/analysis-csharp.md
Normal file
19
change-notes/1.20/analysis-csharp.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Improvements to C# analysis
|
||||
|
||||
## General improvements
|
||||
|
||||
## New queries
|
||||
|
||||
| **Query** | **Tags** | **Purpose** |
|
||||
|-----------------------------|-----------|--------------------------------------------------------------------|
|
||||
|
||||
## Changes to existing queries
|
||||
|
||||
| *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* |
|
||||
| Off-by-one comparison against container length (cs/index-out-of-bounds) | Fewer false positives | Results have been removed when there are additional guards on the index. |
|
||||
|
||||
## Changes to code extraction
|
||||
|
||||
## Changes to QL libraries
|
||||
|
||||
## Changes to the autobuilder
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
## General improvements
|
||||
|
||||
* TODO
|
||||
|
||||
## New queries
|
||||
|
||||
| **Query** | **Tags** | **Purpose** |
|
||||
@@ -13,12 +11,9 @@
|
||||
|
||||
## Changes to existing queries
|
||||
|
||||
| **Query** | **Expected impact** | **Change** |
|
||||
|--------------------------------|----------------------------|----------------------------------------------|
|
||||
| todo | | |
|
||||
|
||||
| **Query** | **Expected impact** | **Change** |
|
||||
|--------------------------------------------|------------------------------|------------------------------------------------------------------------------|
|
||||
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements. |
|
||||
| | | |
|
||||
|
||||
## Changes to QL libraries
|
||||
|
||||
* TODO
|
||||
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
|
||||
import cpp
|
||||
import CPython.Extensions
|
||||
|
||||
/* A call to an argument parsing function */
|
||||
class PyArgParseTupleCall extends FunctionCall {
|
||||
|
||||
PyArgParseTupleCall() {
|
||||
this.getTarget().hasGlobalName("PyArg_Parse") or
|
||||
this.getTarget().hasGlobalName("PyArg_ParseTuple") or
|
||||
this.getTarget().hasGlobalName("PyArg_VaParse") or
|
||||
this.getTarget().hasGlobalName("PyArg_ParseTupleAndKeywords") or
|
||||
this.getTarget().hasGlobalName("PyArg_VaParseAndKeywords")
|
||||
}
|
||||
|
||||
private int getFormatIndex() {
|
||||
exists(Function f | f = this.getTarget() |
|
||||
(f.hasGlobalName("PyArg_Parse") or f.hasGlobalName("PyArg_ParseTuple") or f.hasGlobalName("PyArg_VaParse")) and result = 1
|
||||
or
|
||||
(f.hasGlobalName("PyArg_ParseTupleAndKeywords") or f.hasGlobalName("PyArg_VaParseAndKeywords")) and result = 2
|
||||
)
|
||||
}
|
||||
|
||||
private string getFormatString() {
|
||||
result = this.getArgument(this.getFormatIndex()).(StringLiteral).getValue()
|
||||
}
|
||||
|
||||
string getArgumentFormat() {
|
||||
exists(string fmt | fmt = this.getFormatString() |
|
||||
exists(int i | fmt.charAt(i) = ";" or fmt.charAt(i) = ":" | result = fmt.prefix(i))
|
||||
or
|
||||
not exists(int i | fmt.charAt(i) = ";" or fmt.charAt(i) = ":") and result = fmt
|
||||
)
|
||||
}
|
||||
|
||||
string getPyArgumentType(int index) {
|
||||
parse_format_string(this.getArgumentFormat(), index, _, result) and result != "typed"
|
||||
or
|
||||
exists(int cindex, PythonClass cls | parse_format_string(this.getArgumentFormat(), index, cindex, "typed") |
|
||||
cls.getAnAccess() = this.getArgument(this.getFormatIndex() * 2 + cindex).(AddressOfExpr).getOperand() and
|
||||
result = cls.getTpName()
|
||||
)
|
||||
or
|
||||
exists(int cindex | parse_format_string(this.getArgumentFormat(), index, cindex, "typed") and
|
||||
not exists(PythonClass cls | cls.getAnAccess() = this.getArgument(this.getFormatIndex() * 2 + cindex).(AddressOfExpr).getOperand())
|
||||
and result = "object"
|
||||
)
|
||||
}
|
||||
|
||||
predicate pyArgumentIsOptional(int index) {
|
||||
exists(string suffix | split_format_string(this.getArgumentFormat(), _, _, suffix, index, _) |
|
||||
suffix.charAt(0) = "|")
|
||||
}
|
||||
|
||||
predicate pyArgumentIsKwOnly(int index) {
|
||||
exists(string suffix | split_format_string(this.getArgumentFormat(), _, _, suffix, index, _) |
|
||||
suffix.charAt(0) = "$")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PyUnpackTupleCall extends FunctionCall {
|
||||
|
||||
PyUnpackTupleCall() {
|
||||
this.getTarget().hasGlobalName("PyArg_UnpackTuple")
|
||||
}
|
||||
|
||||
int getMinSize() {
|
||||
result = this.getArgument(2).getValue().toInt()
|
||||
}
|
||||
|
||||
int getMaxSize() {
|
||||
result = this.getArgument(3).getValue().toInt()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
predicate limiting_format(string text, string limit) {
|
||||
text = "t#" and limit = "read-only"
|
||||
or
|
||||
(text = "B" or text = "H" or text = "I" or text = "k" or text = "K") and limit = "non-negative"
|
||||
or
|
||||
(text = "c" or text = "C") and limit = "length-one"
|
||||
}
|
||||
|
||||
predicate format_string(string text, string type, int cargs) {
|
||||
tuple_format(text, type, cargs) or simple_format(text, type, cargs)
|
||||
}
|
||||
|
||||
private
|
||||
predicate simple_format(string text, string type, int cargs) {
|
||||
text = "s" and (type = "str" or type = "unicode") and cargs = 1
|
||||
or
|
||||
text = "s#" and (type = "str" or type = "unicode") and cargs = 2
|
||||
or
|
||||
text = "s*" and (type = "str" or type = "unicode") and cargs = 1
|
||||
or
|
||||
text = "z" and (type = "str" or type = "unicode" or type = "NoneType") and cargs = 1
|
||||
or
|
||||
text = "z#" and (type = "str" or type = "unicode" or type = "NoneType" or type = "buffer") and cargs = 2
|
||||
or
|
||||
text = "z*" and (type = "str" or type = "unicode" or type = "NoneType" or type = "buffer") and cargs = 1
|
||||
or
|
||||
text = "u" and type = "unicode" and cargs = 1
|
||||
or
|
||||
text = "u#" and type = "unicode" and cargs = 2
|
||||
or
|
||||
text = "O" and type = "object" and cargs = 1
|
||||
or
|
||||
text = "p" and type = "object" and cargs = 1
|
||||
or
|
||||
text = "O&" and type = "object" and cargs = 2
|
||||
or
|
||||
text = "O!" and type = "typed" and cargs = 2
|
||||
or
|
||||
(text = "b" or text = "h" or text = "i" or text = "l" or text = "L" or text = "n") and type = "int" and cargs = 1
|
||||
or
|
||||
(text = "B" or text = "H" or text = "I" or text = "k" or text = "K") and type = "int" and cargs = 1
|
||||
or
|
||||
text = "c" and (type = "bytes" or type = "bytearray") and cargs = 1
|
||||
or
|
||||
text = "C" and type = "unicode" and cargs = 1
|
||||
or
|
||||
text = "D" and type = "complex" and cargs = 1
|
||||
or
|
||||
(text = "f" or text = "d") and type = "float" and cargs = 1
|
||||
or
|
||||
text = "S" and type = "str" and cargs = 1
|
||||
or
|
||||
text = "U" and type = "unicode" and cargs = 1
|
||||
or
|
||||
text = "t#" and type = "buffer" and cargs = 2
|
||||
or
|
||||
text = "w" and type = "buffer" and cargs = 1
|
||||
or
|
||||
text = "w#" and type = "buffer" and cargs = 2
|
||||
or
|
||||
text = "w*" and type = "buffer" and cargs = 1
|
||||
or
|
||||
(text = "es" or text = "et") and (type = "str" or type = "unicode" or type = "buffer") and cargs = 2
|
||||
or
|
||||
(text = "es#" or text = "et#") and (type = "str" or type = "unicode" or type = "buffer") and cargs = 3
|
||||
or
|
||||
text = "y" and type = "bytes" and cargs = 1
|
||||
or
|
||||
text = "y*" and (type = "bytes" or type = "bytearray" or type = "buffer") and cargs = 1
|
||||
or
|
||||
text = "y#" and (type = "bytes" or type = "bytearray" or type = "buffer") and cargs = 2
|
||||
}
|
||||
|
||||
private
|
||||
predicate tuple_format(string text, string type, int cargs) {
|
||||
type = "tuple" and
|
||||
exists(PyArgParseTupleCall call | exists(call.getArgumentFormat().indexOf(text)))
|
||||
and
|
||||
exists(string body | text = "(" + body + ")" | tuple_body(body, _, cargs))
|
||||
}
|
||||
|
||||
private
|
||||
predicate tuple_body(string body, int pyargs, int cargs) {
|
||||
body = "" and cargs = 0 and pyargs = 0
|
||||
or
|
||||
(exists(PyArgParseTupleCall call | exists(call.getArgumentFormat().indexOf(body))) and
|
||||
exists(string p, int pargs, string s, int sargs, int pyargsm1 | pyargs = pyargsm1+1 and tuple_body(p, pyargsm1, pargs) and
|
||||
format_string(s, _, sargs) and body = p + s and cargs = pargs + sargs)
|
||||
)
|
||||
}
|
||||
|
||||
predicate format_token(string token, int delta, int cdelta) {
|
||||
format_string(token, _, cdelta) and delta = 1
|
||||
or
|
||||
token = "|" and delta = 0 and cdelta = 0
|
||||
or
|
||||
token = "$" and delta = 0 and cdelta = 0
|
||||
}
|
||||
|
||||
predicate split_format_string(string full, string prefix, string text, string suffix, int index, int cindex) {
|
||||
exists(PyArgParseTupleCall call | call.getArgumentFormat() = full) and
|
||||
full = prefix + text + suffix and
|
||||
(suffix = "" or exists(string s | suffix.prefix(s.length()) = s | format_token(s, _, _))) and
|
||||
format_token(text, _, _) and
|
||||
(prefix = "" and index = 0 and cindex = 0 and suffix = full.suffix(text.length())
|
||||
or
|
||||
exists(string prefixm1, string suffixm1, string textm1, int im1, int cim1, int prev, int cprev |
|
||||
full = prefixm1 + textm1 + suffixm1 and
|
||||
split_format_string(full, prefixm1, textm1, suffixm1, im1, cim1) and
|
||||
format_token(textm1, prev, cprev) and
|
||||
index = im1+prev and
|
||||
cindex = cim1+cprev and
|
||||
prefix = prefixm1 + textm1 and
|
||||
suffix = suffixm1.suffix(text.length()) and
|
||||
text = suffixm1.prefix(text.length())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate parse_format_string(string full, int index, int cindex, string type) {
|
||||
exists(string prefix, string text, string suffix | split_format_string(full, prefix, text, suffix, index, cindex) and format_string(text, type, _))
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @name Parameter type trap file generator
|
||||
* @description Generate trap files (in CSV form) describing CPython extension function parameter types.
|
||||
* @kind trap
|
||||
* @id cpp/c-python/argument-type-trap
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
import CPython.Extensions
|
||||
|
||||
from TypedPythonExtensionFunction func, int arg, PythonClass cls
|
||||
where func.getArgumentType(arg) = cls
|
||||
select "ext_argtype", func.getTrapID(), arg, cls.getTrapID()
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @name py_cobject_sources() trap file generator
|
||||
* @description Generate trap files (in CSV form) for CPython objects.
|
||||
* @kind trap
|
||||
* @id cpp/c-python/c-object-sources-trap
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
import CPython.Extensions
|
||||
|
||||
from CObject c
|
||||
select "py_cobject_sources", c.getTrapID(), 1
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @name py_cobject() trap file generator
|
||||
* @description Generate trap files (in CSV form) for CPython objects.
|
||||
* @kind trap
|
||||
* @id cpp/c-python/c-object-trap
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
import CPython.Extensions
|
||||
|
||||
|
||||
from CObject c
|
||||
select "py_cobjects", c.getTrapID()
|
||||
@@ -1,893 +0,0 @@
|
||||
import cpp
|
||||
import CPython.ArgParse
|
||||
|
||||
|
||||
/* Root class of all 'C' objects */
|
||||
abstract class CObject extends Element {
|
||||
|
||||
abstract string getTrapID();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
A Python class is an instance of PyTypeObject.
|
||||
*/
|
||||
class PythonClass extends Variable, CObject {
|
||||
|
||||
PythonClass() {
|
||||
getType().hasName("PyTypeObject")
|
||||
}
|
||||
|
||||
/** Gets the function table (if any) associated with the class. */
|
||||
PythonFunctionTable getFunctionTable() {
|
||||
exists(ClassAggregateLiteral l, TypedefType tt, Field f |
|
||||
l = getInitializer().getExpr()
|
||||
and tt.hasName("PyTypeObject")
|
||||
and f.hasName("tp_methods")
|
||||
and f.getDeclaringType() = tt.getBaseType()
|
||||
and result.getAnAccess() = l.getFieldExpr(f)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the getset table (if any) associated with the class. */
|
||||
PythonGetSetTable getGetSetTable() {
|
||||
exists(ClassAggregateLiteral l, TypedefType tt, Field f |
|
||||
l = getInitializer().getExpr()
|
||||
and tt.hasName("PyTypeObject")
|
||||
and f.hasName("tp_getset")
|
||||
and f.getDeclaringType() = tt.getBaseType()
|
||||
and result.getAnAccess() = l.getFieldExpr(f)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the Python module (if any) containing this class. */
|
||||
PythonModule getModule() {
|
||||
result = getFile()
|
||||
}
|
||||
|
||||
Expr getSlot(string name) {
|
||||
exists(ClassAggregateLiteral l, TypedefType tt, Field f |
|
||||
l = getInitializer().getExpr()
|
||||
and tt.hasName("PyTypeObject")
|
||||
and f.hasName(name)
|
||||
and f.getDeclaringType() = tt.getBaseType()
|
||||
and l.getFieldExpr(f) = result
|
||||
)
|
||||
}
|
||||
|
||||
string getTpName() {
|
||||
exists(StringLiteral lit |
|
||||
lit = this.getSlot("tp_name") |
|
||||
result = lit.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
Expr getSizeOf() {
|
||||
exists(ClassAggregateLiteral l, TypedefType tt, Field f |
|
||||
l = getInitializer().getExpr()
|
||||
and tt.hasName("PyTypeObject")
|
||||
and f.hasName("tp_basicsize")
|
||||
and f.getDeclaringType() = tt.getBaseType()
|
||||
and l.getFieldExpr(f) = result
|
||||
)
|
||||
}
|
||||
|
||||
override string getTrapID() {
|
||||
/* This needs to be kept in sync with extractor-python/semmle/passes/type.py */
|
||||
result = "C_type$" + this.getTpName()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A call to a Py_InitModule function. These functions register a Python module.
|
||||
*/
|
||||
class Py_InitModuleCall extends FunctionCall {
|
||||
Py_InitModuleCall() {
|
||||
// Py_InitModule itself is actually a macro, ultimately defined to be something like Py_InitModule4_64.
|
||||
getTarget().getName().matches("Py\\_Init%")
|
||||
}
|
||||
|
||||
/** Gets the name of the module being registered. */
|
||||
string getModuleName() {
|
||||
result = getArgument(0).(StringLiteral).getValue()
|
||||
}
|
||||
|
||||
/** Gets the function table associated with this Py_InitModule call. */
|
||||
PythonFunctionTable getFunctionTable() {
|
||||
exists(VariableAccess va |
|
||||
va = getArgument(1)
|
||||
and result = va.getTarget()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A Python module, represented by the file containing an initialising call for it.
|
||||
*/
|
||||
class PythonModule extends File {
|
||||
PythonModule() {
|
||||
exists(PythonModuleDefinition def | def.getFile() = this)
|
||||
or
|
||||
exists(FunctionCall c | c.getFile() = this |
|
||||
c.getTarget().getName().matches("Py\\_InitModule%")
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a Python class that is in this module. */
|
||||
PythonClass getAClass() {
|
||||
result.getFile() = this
|
||||
}
|
||||
|
||||
/** Gets the function table associated with the module. */
|
||||
PythonFunctionTable getFunctionTable() {
|
||||
result = this.getDefinition().getFunctionTable()
|
||||
or
|
||||
exists(FunctionCall c | c.getFile() = this |
|
||||
c.getTarget().getName().matches("Py\\_InitModule%") and
|
||||
c.getAnArgument() = result.getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the Py_InitModule call that was used to register the module. */
|
||||
//private
|
||||
PythonModuleDefinition getDefinition() {
|
||||
result.getFile() = this
|
||||
}
|
||||
|
||||
/** Gets the name of the module. */
|
||||
string getModuleName() {
|
||||
result = this.getDefinition().getModuleName()
|
||||
or
|
||||
exists(FunctionCall c |c.getFile() = this |
|
||||
c.getTarget().getName().matches("Py\\_InitModule%") and
|
||||
c.getArgument(0).getValue() = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The function table for a Python module.
|
||||
*/
|
||||
class PythonFunctionTable extends Variable {
|
||||
|
||||
PythonFunctionTable() {
|
||||
not(this instanceof Parameter)
|
||||
and exists(ArrayType at | at = getType().getUnspecifiedType() and at.getBaseType().hasName("PyMethodDef"))
|
||||
}
|
||||
|
||||
/** Gets an entry in the table. */
|
||||
PythonFunctionTableEntry getATableEntry() {
|
||||
result = getInitializer().getExpr().getAChild()
|
||||
and exists(result.getRegisteredFunctionName())
|
||||
}
|
||||
|
||||
/** Gets the class (if any) for which this is the function table. */
|
||||
PythonClass getClass() {
|
||||
result.getFunctionTable() = this
|
||||
or
|
||||
exists(FunctionAccess getattr, Call find_method |
|
||||
result.getSlot("tp_getattr") = getattr |
|
||||
find_method.getEnclosingFunction() = getattr.getTarget() and
|
||||
find_method.getArgument(0) = this.getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the module (if any) for which this is the function table. */
|
||||
PythonModule getModule() {
|
||||
result.getFunctionTable() = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The getset table for a Python module or type
|
||||
*/
|
||||
class PythonGetSetTable extends Variable {
|
||||
|
||||
PythonGetSetTable() {
|
||||
not(this instanceof Parameter) and
|
||||
exists(ArrayType at | at = getType() and at.getBaseType().hasName("PyGetSetDef"))
|
||||
}
|
||||
|
||||
/** Gets the class (if any) for which this is the function table. */
|
||||
PythonClass getClass() {
|
||||
result.getGetSetTable() = this
|
||||
}
|
||||
|
||||
/** Gets an entry in the table. */
|
||||
PythonGetSetTableEntry getATableEntry() {
|
||||
result = getInitializer().getExpr().getAChild()
|
||||
and exists(result.getRegisteredPropertyName())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PythonModuleDefinition extends Variable {
|
||||
|
||||
PythonModuleDefinition() {
|
||||
not(this instanceof Parameter)
|
||||
and exists(Type moddef_t | moddef_t = this.getType() and moddef_t.hasName("PyModuleDef"))
|
||||
}
|
||||
|
||||
/** Gets the function table (if any) associated with the class. */
|
||||
PythonFunctionTable getFunctionTable() {
|
||||
exists(ClassAggregateLiteral l, Type moddef_t, Field f |
|
||||
l = this.getInitializer().getExpr()
|
||||
and moddef_t.hasName("PyModuleDef")
|
||||
and f.hasName("m_methods")
|
||||
and f.getDeclaringType() = moddef_t
|
||||
and result.getAnAccess() = l.getFieldExpr(f)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the function table (if any) associated with the class. */
|
||||
string getModuleName() {
|
||||
exists(ClassAggregateLiteral l, Type moddef_t, Field f |
|
||||
l = this.getInitializer().getExpr()
|
||||
and moddef_t.hasName("PyModuleDef")
|
||||
and f.hasName("m_name")
|
||||
and f.getDeclaringType() = moddef_t
|
||||
and result = l.getFieldExpr(f).getValue()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A special (__xxx__) method implemented in C
|
||||
*/
|
||||
class PythonSpecialMethod extends Function {
|
||||
|
||||
PythonSpecialMethod() {
|
||||
class_special_methods(_, _, this)
|
||||
}
|
||||
|
||||
PythonClass getClass() {
|
||||
class_special_methods(result, _, this)
|
||||
}
|
||||
|
||||
string getPyName() {
|
||||
class_special_methods(_, result, this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
predicate class_special_methods(PythonClass cls, string name, Function method) {
|
||||
|
||||
exists(string slot, FunctionAccess fa |
|
||||
special_methods(name, slot) and cls.getSlot(slot) = fa and fa.getTarget() = method
|
||||
or
|
||||
number_methods(name, slot) and
|
||||
exists(ClassAggregateLiteral l, TypedefType tt, Field f |
|
||||
l = cls.getSlot("tp_as_number")
|
||||
and tt.hasName("PyNumberMethods")
|
||||
and f.hasName(slot)
|
||||
and f.getDeclaringType() = tt.getBaseType()
|
||||
and l.getFieldExpr(f) = fa
|
||||
and fa.getTarget() = method
|
||||
)
|
||||
or
|
||||
sequence_methods(name, slot) and
|
||||
exists(ClassAggregateLiteral l, TypedefType tt, Field f |
|
||||
l = cls.getSlot("tp_as_sequence")
|
||||
and tt.hasName("PySequenceMethods")
|
||||
and f.hasName(slot)
|
||||
and f.getDeclaringType() = tt.getBaseType()
|
||||
and l.getFieldExpr(f) = fa
|
||||
and fa.getTarget() = method
|
||||
)
|
||||
or
|
||||
mapping_methods(name, slot) and
|
||||
exists(ClassAggregateLiteral l, TypedefType tt, Field f |
|
||||
l = cls.getSlot("tp_as_mapping")
|
||||
and tt.hasName("PyMappingMethods")
|
||||
and f.hasName(slot)
|
||||
and f.getDeclaringType() = tt.getBaseType()
|
||||
and l.getFieldExpr(f) = fa
|
||||
and fa.getTarget() = method
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
predicate special_methods(string name, string slot_name) {
|
||||
name = "__getattr__" and slot_name = "tp_getattr"
|
||||
or
|
||||
name = "__hash__" and slot_name = "tp_hash"
|
||||
or
|
||||
name = "__call__" and slot_name = "tp_call"
|
||||
or
|
||||
name = "__str__" and slot_name = "tp_str"
|
||||
or
|
||||
name = "__getattribute__" and slot_name = "tp_getattro"
|
||||
or
|
||||
name = "__setattro__" and slot_name = "tp_setattro"
|
||||
or
|
||||
name = "__iter__" and slot_name = "tp_iter"
|
||||
or
|
||||
name = "__descr_get__" and slot_name = "tp_descr_get"
|
||||
or
|
||||
name = "__descr_set__" and slot_name = "tp_descr_set"
|
||||
}
|
||||
|
||||
predicate number_methods(string name, string slot_name) {
|
||||
name = "__add__" and slot_name = "nb_add"
|
||||
or
|
||||
name = "__sub__" and slot_name = "nb_subtract"
|
||||
or
|
||||
name = "__mul__" and slot_name = "nb_multiply"
|
||||
or
|
||||
name = "__mod__" and slot_name = "nb_remainder"
|
||||
or
|
||||
name = "__pow__" and slot_name = "nb_power"
|
||||
or
|
||||
name = "__neg__" and slot_name = "nb_negative"
|
||||
or
|
||||
name = "__pos__" and slot_name = "nb_positive"
|
||||
or
|
||||
name = "__abs__" and slot_name = "nb_absolute"
|
||||
or
|
||||
name = "__bool__" and slot_name = "nb_bool"
|
||||
or
|
||||
name = "__bool__" and slot_name = "nb_bool"
|
||||
or
|
||||
name = "__invert__" and slot_name = "nb_invert"
|
||||
or
|
||||
name = "__lshift__" and slot_name = "nb_lshift"
|
||||
or
|
||||
name = "__rshift__" and slot_name = "nb_rshift"
|
||||
or
|
||||
name = "__and__" and slot_name = "nb_and"
|
||||
or
|
||||
name = "__xor__" and slot_name = "nb_xor"
|
||||
or
|
||||
name = "__or__" and slot_name = "nb_or"
|
||||
or
|
||||
name = "__int__" and slot_name = "nb_int"
|
||||
or
|
||||
name = "__long__" and slot_name = "nb_long"
|
||||
or
|
||||
name = "__float__" and slot_name = "nb_float"
|
||||
or
|
||||
name = "__iadd__" and slot_name = "nb_inplace_add"
|
||||
or
|
||||
name = "__isub__" and slot_name = "nb_inplace_subtract"
|
||||
or
|
||||
name = "__imul__" and slot_name = "nb_inplace_multiply"
|
||||
or
|
||||
name = "__imod__" and slot_name = "nb_inplace_remainder"
|
||||
or
|
||||
name = "__ilshift__" and slot_name = "nb_inplace_lshift"
|
||||
or
|
||||
name = "__irshift__" and slot_name = "nb_inplace_rshift"
|
||||
or
|
||||
name = "__iand__" and slot_name = "nb_inplace_and"
|
||||
or
|
||||
name = "__ixor__" and slot_name = "nb_inplace_xor"
|
||||
or
|
||||
name = "__ior__" and slot_name = "nb_inplace_or"
|
||||
or
|
||||
name = "__index__" and slot_name = "nb_index"
|
||||
}
|
||||
|
||||
predicate sequence_methods(string name, string slot_name) {
|
||||
name = "__len__" and slot_name = "sq_length"
|
||||
or
|
||||
name = "__add__" and slot_name = "sq_concat"
|
||||
or
|
||||
name = "__mul__" and slot_name = "sq_repeat"
|
||||
or
|
||||
name = "__getitem__" and slot_name = "sq_item"
|
||||
or
|
||||
name = "__setitem__" and slot_name = "sq_ass_item"
|
||||
or
|
||||
name = "__contains__" and slot_name = "sq_contains"
|
||||
or
|
||||
name = "__iadd__" and slot_name = "sq_inplace_concat"
|
||||
or
|
||||
name = "__imul__" and slot_name = "sq_inplace_repeat"
|
||||
}
|
||||
|
||||
predicate mapping_methods(string name, string slot_name) {
|
||||
name = "__len__" and slot_name = "mp_length"
|
||||
or
|
||||
name = "__getitem__" and slot_name = "mp_subscript"
|
||||
or
|
||||
name = "__setitem__" and slot_name = "mp_ass_subscript"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
An entry in the getset table for a Python class.
|
||||
This is the C code item that corresponds 1-to-1 with the Python-level property
|
||||
*/
|
||||
class PythonGetSetTableEntry extends AggregateLiteral {
|
||||
|
||||
PythonGetSetTableEntry() {
|
||||
this.getUnderlyingType().hasName("PyGetSetDef")
|
||||
and
|
||||
this.getChild(0) instanceof StringLiteral
|
||||
}
|
||||
|
||||
Function getGetter() {
|
||||
exists(FunctionAccess fa | fa = getChild(1) and result = fa.getTarget())
|
||||
}
|
||||
|
||||
Function getSetter() {
|
||||
exists(FunctionAccess fa | fa = getChild(2) and result = fa.getTarget())
|
||||
}
|
||||
|
||||
StringLiteral getRegisteredPropertyName() {
|
||||
result = this.getChild(0)
|
||||
}
|
||||
|
||||
PythonGetSetTable getTable() {
|
||||
result.getATableEntry() = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
An entry in the function table for a Python class or module.
|
||||
This is the C code item that corresponds 1-to-1 with the Python-level function.
|
||||
*/
|
||||
class PythonFunctionTableEntry extends AggregateLiteral {
|
||||
|
||||
PythonFunctionTableEntry() {
|
||||
this.getUnderlyingType().hasName("PyMethodDef")
|
||||
and
|
||||
this.getChild(0) instanceof StringLiteral
|
||||
}
|
||||
|
||||
/** Gets the doc string to be associated with the function being registered. */
|
||||
string getDocString() {
|
||||
result = getChild(3).(StringLiteral).getValueText()
|
||||
}
|
||||
|
||||
/** Gets the flags for the function being registered. */
|
||||
int getFlags() {
|
||||
result = getChild(2).getValue().toInt()
|
||||
}
|
||||
|
||||
/** Gets the function being registered. */
|
||||
Function getFunction() {
|
||||
exists(FunctionAccess fa | fa = getChild(1) and result = fa.getTarget())
|
||||
}
|
||||
|
||||
/** Gets the module containing the function table. */
|
||||
PythonModule getModule() {
|
||||
result = getTable().getModule()
|
||||
}
|
||||
|
||||
/** Gets the name with which the function should be referenced from Python. */
|
||||
StringLiteral getRegisteredFunctionName() {
|
||||
result = this.getChild(0)
|
||||
}
|
||||
|
||||
/** Gets the function table containing this entry. */
|
||||
PythonFunctionTable getTable() {
|
||||
result.getATableEntry() = this
|
||||
}
|
||||
|
||||
/** Gets a flag associated with this function. */
|
||||
string getAFlag() {
|
||||
exists(int f | f = this.getFlags() |
|
||||
(f % 2 = 1 and result = "METH_VARARGS")
|
||||
or ((f / 2) % 2 = 1 and result = "METH_KEYWORDS")
|
||||
or ((f / 4) % 2 = 1 and result = "METH_NOARGS")
|
||||
or ((f / 8) % 2 = 1 and result = "METH_O")
|
||||
or ((f / 16) % 2 = 1 and result = "METH_CLASS")
|
||||
or ((f / 32) % 2 = 1 and result = "METH_STATIC")
|
||||
or ((f / 64) % 2 = 1 and result = "METH_COEXIST")
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
library class PythonBuildReturnCall extends FunctionCall {
|
||||
PythonBuildReturnCall() {
|
||||
exists(string name | name = getTarget().getName() |
|
||||
name = "Py_BuildValue"
|
||||
or name = "Py_VaBuildValue"
|
||||
)
|
||||
}
|
||||
|
||||
string getFormatString() {
|
||||
result = getArgument(0).(StringLiteral).getValue()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
An extension function for Python (written in C).
|
||||
*/
|
||||
class PythonExtensionFunction extends Function {
|
||||
PythonExtensionFunction() {
|
||||
exists(PythonFunctionTableEntry e | e.getFunction() = this)
|
||||
and exists(getAParameter())
|
||||
}
|
||||
|
||||
/** Gets a function table entry registering this function. */
|
||||
PythonFunctionTableEntry getATableEntry() {
|
||||
result.getFunction() = this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TypedPythonExtensionProperty extends PythonGetSetTableEntry, CObject {
|
||||
PythonClass getPropertyType() {
|
||||
result = py_return_type(this.getGetter())
|
||||
}
|
||||
|
||||
private string trapClass() {
|
||||
result = this.getClass().getTrapID()
|
||||
}
|
||||
|
||||
override string getTrapID() {
|
||||
result = this.trapClass() + "$" + this.getPyName()
|
||||
}
|
||||
|
||||
string getPyName() {
|
||||
result = this.getRegisteredPropertyName().getValue()
|
||||
}
|
||||
|
||||
/** Gets the class containing this function. */
|
||||
PythonClass getClass() {
|
||||
result = this.getTable().getClass()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* An extension function for Python (written in C); Python facing aspect */
|
||||
abstract class TypedPythonExtensionFunction extends PythonFunctionTableEntry, CObject {
|
||||
|
||||
override Location getLocation() {
|
||||
result = this.getRegisteredFunctionName().getLocation()
|
||||
}
|
||||
|
||||
override
|
||||
string toString() {
|
||||
result = "MethodDef " + this.getRegisteredFunctionName().getValue()
|
||||
}
|
||||
|
||||
abstract PythonClass getArgumentType(int index);
|
||||
|
||||
abstract predicate argumentIsOptional(int index);
|
||||
|
||||
abstract predicate argumentIsKwOnly(int index);
|
||||
|
||||
PythonExtensionFunction getCode() {
|
||||
result.getATableEntry() = this
|
||||
}
|
||||
|
||||
predicate isMethod() {
|
||||
exists(this.getTable().getClass()) and not this.getAFlag() = "METH_STATIC"
|
||||
}
|
||||
|
||||
int c_index(int index) {
|
||||
index in [0..20] and result in [-1..20]
|
||||
and
|
||||
(if this.isMethod() then
|
||||
result = index - 1
|
||||
else
|
||||
result = index
|
||||
)
|
||||
}
|
||||
|
||||
string getPyName() {
|
||||
result = this.getRegisteredFunctionName().getValue()
|
||||
}
|
||||
|
||||
PythonClass getReturnType() {
|
||||
result = py_return_type(this.getCode())
|
||||
}
|
||||
|
||||
|
||||
/** Gets the module containing this function. */
|
||||
override PythonModule getModule() {
|
||||
result = getTable().getModule()
|
||||
}
|
||||
|
||||
/** Gets the class containing this function. */
|
||||
PythonClass getClass() {
|
||||
result = getTable().getClass()
|
||||
}
|
||||
|
||||
//private
|
||||
string trapModule() {
|
||||
result = this.getModule().getModuleName()
|
||||
}
|
||||
|
||||
//private
|
||||
string trapClass() {
|
||||
result = this.getClass().getTrapID()
|
||||
}
|
||||
|
||||
/* A globally unique name for use in trap files.
|
||||
*/
|
||||
override string getTrapID() {
|
||||
result = "C_builtin_function_or_method$" + this.trapModule() + "." + this.getPyName()
|
||||
or
|
||||
result = this.trapClass() + "$" + this.getPyName()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
predicate src_dest_pair(Element src, ControlFlowNode dest) {
|
||||
exists(LocalScopeVariable v, ControlFlowNode def |
|
||||
definitionUsePair(v, def, dest) and
|
||||
exprDefinition(v, def, src) and
|
||||
not exists(AddressOfExpr addr | addr.getOperand() = v.getAnAccess())
|
||||
)
|
||||
or
|
||||
exists(Parameter p | dest = p.getAnAccess() and not definitionUsePair(_, _, dest) and src = p)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate local_flows_to(Element src, ControlFlowNode dest) {
|
||||
not unreachable(src) and not unreachable(dest) and
|
||||
(src = dest
|
||||
or
|
||||
src_dest_pair(src, dest)
|
||||
or
|
||||
exists(Element mid | local_flows_to(src, mid) and src_dest_pair(mid, dest))
|
||||
)
|
||||
}
|
||||
|
||||
PythonClass py_return_type(Function f) {
|
||||
exists(ReturnStmt ret |
|
||||
ret.getEnclosingFunction() = f and
|
||||
result = python_type(ret.getExpr()) and
|
||||
not ret.getExpr().getValue() = "0"
|
||||
)
|
||||
or
|
||||
exists(Macro m | m.getAnInvocation().getEnclosingFunction() = f and m.getName() = "Py_RETURN_NONE" and result.getTpName() = "NoneType")
|
||||
or
|
||||
exists(Macro m | m.getAnInvocation().getEnclosingFunction() = f and m.getName() = "Py_RETURN_TRUE" and result.getTpName() = "bool")
|
||||
or
|
||||
exists(Macro m | m.getAnInvocation().getEnclosingFunction() = f and m.getName() = "Py_RETURN_FALSE" and result.getTpName() = "bool")
|
||||
}
|
||||
|
||||
PythonClass python_type_from_size(Expr e) {
|
||||
exists(Type t, string name |
|
||||
t = e.getUnderlyingType().(PointerType).getBaseType() and name = t.getName() and name.matches("Py\\_%Object") |
|
||||
exists(PythonClass cls | cls.getSizeOf().getValueText() = "sizeof(" + t.getName() + ")" |
|
||||
result = cls and not result.getTpName() = "int" and not result.getTpName() = "bool"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate py_bool(Expr e) {
|
||||
exists(MacroInvocation mi, string name |
|
||||
mi.getExpr() = e and name = mi.getMacroName() |
|
||||
name = "Py_False" or name = "Py_True"
|
||||
)
|
||||
}
|
||||
|
||||
PythonClass python_type_from_name(Expr e) {
|
||||
exists(Type t, string name |
|
||||
t = e.getUnderlyingType().(PointerType).getBaseType() and name = t.getName() |
|
||||
name = "PyBytesObject" and result.getTpName() = "bytes"
|
||||
or
|
||||
name = "PyTupleObject" and result.getTpName() = "tuple"
|
||||
or
|
||||
name = "PyLongObject" and result.getTpName() = "int" and not py_bool(e)
|
||||
or
|
||||
name = "PyIntObject" and result.getTpName() = "int" and not py_bool(e)
|
||||
or
|
||||
name = "PyStringObject" and result.getTpName() = "str" and cpython_major_version() = 2
|
||||
or
|
||||
name = "PyStringObject" and result.getTpName() = "bytes" and cpython_major_version() = 3
|
||||
or
|
||||
name = "PyUnicodeObject" and result.getTpName() = "unicode" and cpython_major_version() = 2
|
||||
or
|
||||
name = "PyUnicodeObject" and result.getTpName() = "str" and cpython_major_version() = 3
|
||||
)
|
||||
}
|
||||
|
||||
PythonClass python_type(Expr e) {
|
||||
result = python_type_from_size(e)
|
||||
or
|
||||
result = python_type_from_name(e)
|
||||
or
|
||||
call_to_new(e, result)
|
||||
or
|
||||
exists(Element src | result = python_type(src) and local_flows_to(src, e))
|
||||
or
|
||||
result = type_from_build_value(e)
|
||||
or
|
||||
result = type_from_call(e)
|
||||
or
|
||||
py_bool(e) and result.getTpName() = "bool"
|
||||
or
|
||||
call_to_type(e, result)
|
||||
or
|
||||
exists(MacroInvocation mi |
|
||||
mi.getExpr() = e and mi.getMacroName() = "Py_None" |
|
||||
result.getTpName() = "NoneType"
|
||||
)
|
||||
}
|
||||
|
||||
predicate build_value_function(Function f) {
|
||||
f.getName().regexpMatch("_?Py_(Va)?BuildValue(_SizeT)?")
|
||||
}
|
||||
|
||||
PythonClass type_from_build_value(Expr e) {
|
||||
exists(FunctionCall c |
|
||||
c = e |
|
||||
build_value_function(c.getTarget()) and
|
||||
result = type_from_build_value_code(c.getArgument(0).getValue())
|
||||
)
|
||||
}
|
||||
|
||||
PythonClass type_from_call(Expr e) {
|
||||
exists(Function f |
|
||||
not build_value_function(f) and
|
||||
/* Do not type to infer return type of the interpreter */
|
||||
not f.getName().matches("PyEval%") and
|
||||
f = e.(FunctionCall).getTarget() and
|
||||
result = py_return_type(f)
|
||||
)
|
||||
or
|
||||
exists(Function f |
|
||||
f = e.(FunctionCall).getTarget() and
|
||||
result.getTpName() = "dict"
|
||||
|
|
||||
f.hasName("PyEval_GetBuiltins") or
|
||||
f.hasName("PyEval_GetGlobals") or
|
||||
f.hasName("PyEval_GetLocals")
|
||||
)
|
||||
}
|
||||
|
||||
PythonClass type_from_build_value_code(string s) {
|
||||
exists(FunctionCall c | c.getArgument(0).getValue() = s)
|
||||
and
|
||||
(result = type_from_simple_code(s)
|
||||
or
|
||||
result.getTpName() = "dict" and s.charAt(0) = "{"
|
||||
or
|
||||
result.getTpName() = "tuple" and not exists(type_from_simple_code(s)) and not s.charAt(0) = "{"
|
||||
)
|
||||
}
|
||||
|
||||
private PythonClass theBytesClass() {
|
||||
cpython_major_version() = 2 and result.getTpName() = "str"
|
||||
or
|
||||
cpython_major_version() = 3 and result.getTpName() = "bytes"
|
||||
}
|
||||
|
||||
private PythonClass theUnicodeClass() {
|
||||
cpython_major_version() = 2 and result.getTpName() = "unicode"
|
||||
or
|
||||
cpython_major_version() = 3 and result.getTpName() = "str"
|
||||
}
|
||||
|
||||
PythonClass type_from_simple_code(string s) {
|
||||
(s = "s" or s = "s#" or s = "z" or s = "z#") and result.getTpName() = "str"
|
||||
or
|
||||
(s = "u" or s = "u#" or s = "U" or s = "U#" or s = "C") and result = theUnicodeClass()
|
||||
or
|
||||
(s = "y" or s = "y#" or s = "c") and result = theBytesClass()
|
||||
or
|
||||
(s = "i" or s = "I" or s = "b" or s = "B" or s = "h" or s = "H" or
|
||||
s = "k" or s = "K" or s = "l" or s = "L" or s = "n"
|
||||
) and result.getTpName() = "int"
|
||||
or
|
||||
(s = "f" or s = "d") and result.getTpName() = "float"
|
||||
or
|
||||
s = "D" and result.getTpName() = "complex"
|
||||
or
|
||||
(s = "O" or s = "O&" or s = "N") and result.getTpName() = "object"
|
||||
}
|
||||
|
||||
predicate call_to_new(FunctionCall call, PythonClass cls) {
|
||||
exists(string name |
|
||||
name = call.getTarget().getName() |
|
||||
name = "_PyObject_New" or
|
||||
name = "_PyObject_GC_New" or
|
||||
name = "_PyObject_NewVar" or
|
||||
name = "_PyObject_GC_NewVar"
|
||||
) and
|
||||
exists(AddressOfExpr addr |
|
||||
addr = call.getArgument(0) |
|
||||
addr.getAddressable() = cls
|
||||
)
|
||||
}
|
||||
|
||||
predicate call_to_type(FunctionCall call, PythonClass cls) {
|
||||
exists(AddressOfExpr aoe |
|
||||
call.getTarget().getName().matches("PyObject\\_Call%") and
|
||||
call.getArgument(0) = aoe and
|
||||
aoe.getAddressable() = cls
|
||||
)
|
||||
}
|
||||
|
||||
predicate pyargs_function(PythonFunctionTableEntry func, PyArgParseTupleCall call) {
|
||||
func.getFunction().getParameter(1).getAnAccess() = call.getArgument(0)
|
||||
}
|
||||
|
||||
|
||||
class PyArgsFunction extends TypedPythonExtensionFunction {
|
||||
|
||||
PyArgsFunction() {
|
||||
this.getAFlag() = "METH_VARARGS"
|
||||
}
|
||||
|
||||
PyArgParseTupleCall getParseArgsCall() {
|
||||
strictcount(PyArgParseTupleCall other | this.getCode().getParameter(1).getAnAccess() = other.getArgument(0)) = 1 and
|
||||
pyargs_function(this, result)
|
||||
}
|
||||
|
||||
override PythonClass getArgumentType(int index) {
|
||||
this.isMethod() and index = 0 and result = this.getTable().getClass()
|
||||
or
|
||||
result.getTpName() = this.getParseArgsCall().getPyArgumentType(this.c_index(index))
|
||||
}
|
||||
|
||||
override predicate argumentIsOptional(int index) {
|
||||
this.getParseArgsCall().pyArgumentIsOptional(this.c_index(index))
|
||||
}
|
||||
|
||||
override predicate argumentIsKwOnly(int index) {
|
||||
this.getParseArgsCall().pyArgumentIsKwOnly(this.c_index(index))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PyOFunction extends TypedPythonExtensionFunction {
|
||||
|
||||
PyOFunction() {
|
||||
this.getAFlag() = "METH_O"
|
||||
}
|
||||
|
||||
override PythonClass getArgumentType(int index) {
|
||||
this.isMethod() and index = 0 and result = this.getTable().getClass()
|
||||
or
|
||||
/* TO DO -- Better analysis */
|
||||
this.c_index(index) = 0 and result.getTpName() = "object"
|
||||
}
|
||||
|
||||
override predicate argumentIsOptional(int index) {
|
||||
none()
|
||||
}
|
||||
|
||||
override predicate argumentIsKwOnly(int index) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
class PyNoArgFunction extends TypedPythonExtensionFunction {
|
||||
|
||||
PyNoArgFunction() {
|
||||
this.getAFlag() = "METH_NOARGS"
|
||||
}
|
||||
|
||||
override PythonClass getArgumentType(int index) {
|
||||
this.isMethod() and index = 0 and result = this.getTable().getClass()
|
||||
}
|
||||
|
||||
override predicate argumentIsOptional(int index) {
|
||||
none()
|
||||
}
|
||||
|
||||
override predicate argumentIsKwOnly(int index) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
int cpython_major_version() {
|
||||
exists(MacroInvocation mi |
|
||||
mi.getMacroName() = "PY_MAJOR_VERSION" |
|
||||
result = mi.getExpr().getValue().toInt()
|
||||
)
|
||||
}
|
||||
|
||||
int cpython_minor_version() {
|
||||
exists(MacroInvocation mi |
|
||||
mi.getMacroName() = "PY_MINOR_VERSION" |
|
||||
result = mi.getExpr().getValue().toInt()
|
||||
)
|
||||
}
|
||||
|
||||
string cpython_version() {
|
||||
result = cpython_major_version().toString() + "." + cpython_minor_version().toString()
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @name Parameter return trap file generator
|
||||
* @description Generate trap files (in CSV form) describing CPython extension functions return one of their parameters.
|
||||
* @kind trap
|
||||
* @id cpp/c-python/parameter-return-trap
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import CPython.Extensions
|
||||
|
||||
predicate argument_flows_to_return(Function func, int arg) {
|
||||
exists(Parameter p | p = func.getParameter(arg) |
|
||||
forall(ReturnStmt ret | ret.getEnclosingFunction() = func |
|
||||
local_flows_to(p, ret.getExpr()))
|
||||
)
|
||||
}
|
||||
|
||||
from TypedPythonExtensionFunction func, PythonExtensionFunction code, int arg
|
||||
where func.getCode() = code and argument_flows_to_return(code, arg)
|
||||
select "ext_argreturn", func.getTrapID(), arg
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @name Property type trap file generator
|
||||
* @description Generate trap files (in CSV form) describing CPython extension property types.
|
||||
* @kind trap
|
||||
* @id cpp/c-python/property-type-trap
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
import CPython.Extensions
|
||||
|
||||
|
||||
from TypedPythonExtensionProperty p, PythonClass cls
|
||||
where cls = p.getPropertyType()
|
||||
select "ext_proptype", p.getTrapID(), cls.getTrapID()
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @name Return type trap file generator
|
||||
* @description Generate trap files (in CSV form) describing CPython extension function return types.
|
||||
* @kind trap
|
||||
* @id cpp/c-python/return-type-trap
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
import CPython.Extensions
|
||||
|
||||
|
||||
from TypedPythonExtensionFunction func, PythonClass cls
|
||||
where cls = func.getReturnType()
|
||||
select "ext_rettype", func.getTrapID(), cls.getTrapID()
|
||||
@@ -3,7 +3,11 @@
|
||||
* @description Query to help investigate mysterious results with ReturnStackAllocatedObject
|
||||
* @kind table
|
||||
* @id cpp/points-to/debug
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
// This query is not suitable for production use and has been deprecated.
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.pointsto.PointsTo
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
* @description Query to force evaluation of staged points-to predicates
|
||||
* @kind table
|
||||
* @id cpp/points-to/prepared-staged-points-to
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
// This query is not suitable for production use and has been deprecated.
|
||||
|
||||
import semmle.code.cpp.pointsto.PointsTo
|
||||
|
||||
select count(int set, Element location | setlocations(set, unresolveElement(location))),
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
* @description Count the number points-to sets with 0 or 1 incoming flow edges, and the total number of points-to sets
|
||||
* @kind table
|
||||
* @id cpp/points-to/stats
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
// This query is not suitable for production use and has been deprecated.
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.pointsto.PointsTo
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
* @name Taint test
|
||||
* @kind table
|
||||
* @id cpp/points-to/tainted-format-strings
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
// This query is not suitable for production use and has been deprecated.
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.pointsto.PointsTo
|
||||
import semmle.code.cpp.pointsto.CallGraph
|
||||
|
||||
@@ -14,16 +14,53 @@
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.controlflow.Guards
|
||||
import semmle.code.csharp.commons.ComparisonTest
|
||||
|
||||
from RelationalOperation cmp, Variable array, Variable index, ElementAccess ea, VariableAccess indexAccess
|
||||
// Look for `index <= array.Length` or `array.Length >= index`
|
||||
where (cmp instanceof GEExpr or cmp instanceof LEExpr)
|
||||
and cmp.getGreaterOperand() = any(PropertyAccess pa | pa.getQualifier() = array.getAnAccess() and pa.getTarget().hasName("Length"))
|
||||
and cmp.getLesserOperand() = index.getAnAccess()
|
||||
/** A comparison of an index variable with the length of an array. */
|
||||
class IndexGuard extends ComparisonTest {
|
||||
VariableAccess indexAccess;
|
||||
Variable array;
|
||||
|
||||
IndexGuard() {
|
||||
this.getFirstArgument() = indexAccess and
|
||||
this.getSecondArgument() = any(PropertyAccess lengthAccess |
|
||||
lengthAccess.getQualifier() = array.getAnAccess() and
|
||||
lengthAccess.getTarget().hasName("Length")
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this comparison applies to array `arr` and index `ind`. */
|
||||
predicate controls(Variable arr, Variable ind) {
|
||||
arr = array and
|
||||
ind.getAnAccess() = indexAccess
|
||||
}
|
||||
|
||||
/** Holds if this comparison guards `expr`. */
|
||||
predicate guards(GuardedExpr expr, boolean condition) {
|
||||
expr.isGuardedBy(this.getExpr(), indexAccess, condition)
|
||||
}
|
||||
|
||||
/** Holds if this comparison is an incorrect `<=` or equivalent. */
|
||||
predicate isIncorrect() {
|
||||
this.getComparisonKind().isLessThanEquals()
|
||||
}
|
||||
}
|
||||
|
||||
from IndexGuard incorrectGuard, Variable array, Variable index, ElementAccess ea, GuardedExpr indexAccess
|
||||
where
|
||||
// Look for `index <= array.Length` or `array.Length >= index`
|
||||
incorrectGuard.controls(array, index) and
|
||||
incorrectGuard.isIncorrect() and
|
||||
// Look for `array[index]`
|
||||
and ea.getQualifier() = array.getAnAccess()
|
||||
and ea.getIndex(0) = indexAccess
|
||||
and indexAccess = index.getAnAccess()
|
||||
ea.getQualifier() = array.getAnAccess() and
|
||||
ea.getIndex(0) = indexAccess and
|
||||
indexAccess = index.getAnAccess() and
|
||||
// Where the index access is guarded by the comparison
|
||||
and indexAccess.(GuardedExpr).isGuardedBy(cmp, index.getAnAccess(), true)
|
||||
select cmp, "Off-by-one index comparison against length leads to possible out of bounds $@.", ea, ea.toString()
|
||||
incorrectGuard.guards(indexAccess, true) and
|
||||
// And there are no other guards
|
||||
not exists(IndexGuard validGuard |
|
||||
not validGuard.isIncorrect() and
|
||||
validGuard.controls(array, index) and
|
||||
validGuard.guards(indexAccess, _)
|
||||
)
|
||||
select incorrectGuard, "Off-by-one index comparison against length leads to possible out of bounds $@.", ea, ea.toString()
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
|
||||
class Test
|
||||
{
|
||||
static void Main(string[] args)
|
||||
void Test1(string[] args)
|
||||
{
|
||||
// BAD: Loop upper bound is off-by-one
|
||||
for (int i = 0; i <= args.Length; i++)
|
||||
@@ -34,6 +34,11 @@ class Test
|
||||
{
|
||||
Console.WriteLine(args[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void Test2(string[] args)
|
||||
{
|
||||
int j = 0;
|
||||
|
||||
// GOOD: Correct terminating value
|
||||
if (args.Length > j)
|
||||
@@ -41,4 +46,54 @@ class Test
|
||||
Console.WriteLine(args[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void Test3(string[] args)
|
||||
{
|
||||
// GOOD: Guarded by ternary operator.
|
||||
for (int i = 0; i <= args.Length; i++)
|
||||
{
|
||||
string s = i < args.Length ? args[i] : "";
|
||||
}
|
||||
}
|
||||
|
||||
void Test4(string[] args)
|
||||
{
|
||||
int j = 0;
|
||||
|
||||
// GOOD: Guarded by ternary operator.
|
||||
if( j <= args.Length )
|
||||
{
|
||||
string s = j < args.Length ? args[j] : "";
|
||||
}
|
||||
}
|
||||
|
||||
void Test5(string[] args)
|
||||
{
|
||||
// GOOD: A valid test of Length.
|
||||
for (int i = 0; i != args.Length; i++)
|
||||
{
|
||||
Console.WriteLine(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Test6(string[] args)
|
||||
{
|
||||
int j = 0;
|
||||
|
||||
// GOOD: There is another correct test.
|
||||
if( j <= args.Length )
|
||||
{
|
||||
if (j == args.Length) return;
|
||||
Console.WriteLine(args[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void Test7(string[] args)
|
||||
{
|
||||
// GOOD: Guarded by ||.
|
||||
for (int i = 0; i <= args.Length; i++)
|
||||
{
|
||||
bool b = i == args.Length || args[i] == "x";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,24 +49,29 @@ predicate isPropertyFilter(UnusedLocal v) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasJsxInScope(UnusedLocal v) {
|
||||
any(JSXNode n).getParent+() = v.getScope().getScopeElement()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v` is an import of React, and there is a JSX element that implicitly
|
||||
* references it.
|
||||
*/
|
||||
predicate isReactImportForJSX(UnusedLocal v) {
|
||||
exists (ImportSpecifier is |
|
||||
is.getLocal() = v.getADeclaration() and
|
||||
exists (JSXNode jsx | jsx.getTopLevel() = is.getTopLevel())
|
||||
|
|
||||
* Holds if `v` is a "React" variable that is implicitly used by a JSX element.
|
||||
*/
|
||||
predicate isReactForJSX(UnusedLocal v) {
|
||||
hasJsxInScope(v) and
|
||||
(
|
||||
v.getName() = "React"
|
||||
or
|
||||
// legacy `@jsx` pragmas
|
||||
exists (JSXPragma p | p.getTopLevel() = is.getTopLevel() | p.getDOMName() = v.getName())
|
||||
or
|
||||
// JSX pragma from a .babelrc file
|
||||
exists (Babel::TransformReactJsxConfig plugin |
|
||||
plugin.appliesTo(is.getTopLevel()) and
|
||||
plugin.getJsxFactoryVariableName() = v.getName())
|
||||
exists(TopLevel tl |
|
||||
tl = v.getADeclaration().getTopLevel() |
|
||||
// legacy `@jsx` pragmas
|
||||
exists(JSXPragma p | p.getTopLevel() = tl | p.getDOMName() = v.getName())
|
||||
or
|
||||
// JSX pragma from a .babelrc file
|
||||
exists(Babel::TransformReactJsxConfig plugin |
|
||||
plugin.appliesTo(tl) and
|
||||
plugin.getJsxFactoryVariableName() = v.getName()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,7 +155,7 @@ predicate whitelisted(UnusedLocal v) {
|
||||
// exclude variables used to filter out unwanted properties
|
||||
isPropertyFilter(v) or
|
||||
// exclude imports of React that are implicitly referenced by JSX
|
||||
isReactImportForJSX(v) or
|
||||
isReactForJSX(v) or
|
||||
// exclude names that are used as types
|
||||
exists (VarDecl vd |
|
||||
v = vd.getVariable() |
|
||||
|
||||
@@ -254,22 +254,18 @@ class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
|
||||
* Gets a return value for a call to this function.
|
||||
*/
|
||||
AbstractValue getAReturnValue() {
|
||||
if astNode.isGenerator() or astNode.isAsync() then
|
||||
result = TAbstractOtherObject()
|
||||
else (
|
||||
// explicit return value
|
||||
result = astNode.getAReturnedExpr().analyze().getALocalValue()
|
||||
// explicit return value
|
||||
result = astNode.getAReturnedExpr().analyze().getALocalValue()
|
||||
or
|
||||
// implicit return value
|
||||
(
|
||||
// either because execution of the function may terminate normally
|
||||
mayReturnImplicitly()
|
||||
or
|
||||
// implicit return value
|
||||
(
|
||||
// either because execution of the function may terminate normally
|
||||
mayReturnImplicitly()
|
||||
or
|
||||
// or because there is a bare `return;` statement
|
||||
exists (ReturnStmt ret | ret = astNode.getAReturnStmt() | not exists(ret.getExpr()))
|
||||
) and
|
||||
result = TAbstractUndefined()
|
||||
)
|
||||
// or because there is a bare `return;` statement
|
||||
exists (ReturnStmt ret | ret = astNode.getAReturnStmt() | not exists(ret.getExpr()))
|
||||
) and
|
||||
result = TAbstractUndefined()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,5 +284,26 @@ class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
|
||||
not final instanceof ThrowStmt
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow analysis for generator functions.
|
||||
*/
|
||||
private class AnalyzedGeneratorFunction extends AnalyzedFunction {
|
||||
AnalyzedGeneratorFunction() { astNode.isGenerator() }
|
||||
|
||||
override AbstractValue getAReturnValue() {
|
||||
result = TAbstractOtherObject()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow analysis for async functions.
|
||||
*/
|
||||
private class AnalyzedAsyncFunction extends AnalyzedFunction {
|
||||
AnalyzedAsyncFunction() { astNode.isAsync() }
|
||||
|
||||
override AbstractValue getAReturnValue() {
|
||||
result = TAbstractOtherObject()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,16 +39,7 @@ AbstractValue getAnInitialPropertyValue(DefiniteAbstractValue baseVal, string pr
|
||||
)
|
||||
or
|
||||
// class members
|
||||
exists (ClassDefinition c, DataFlow::AnalyzedNode init, MemberDefinition m |
|
||||
m = c.getMember(propertyName) and
|
||||
not m instanceof AccessorMethodDefinition and
|
||||
init = m.getInit().analyze() and
|
||||
result = init.getALocalValue() |
|
||||
if m.isStatic() then
|
||||
baseVal = TAbstractClass(c)
|
||||
else
|
||||
baseVal = AbstractInstance::of(c)
|
||||
)
|
||||
result = getAnInitialMemberValue(getMember(baseVal, propertyName))
|
||||
or
|
||||
// object properties
|
||||
exists (ValueProperty p |
|
||||
@@ -63,6 +54,30 @@ AbstractValue getAnInitialPropertyValue(DefiniteAbstractValue baseVal, string pr
|
||||
result = TAbstractInstance(baseVal)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a class member definition that we abstractly represent as a property of `baseVal`
|
||||
* with the given `name`.
|
||||
*/
|
||||
private MemberDefinition getMember(DefiniteAbstractValue baseVal, string name) {
|
||||
exists (ClassDefinition c | result = c.getMember(name) |
|
||||
if result.isStatic() then
|
||||
baseVal = TAbstractClass(c)
|
||||
else
|
||||
baseVal = AbstractInstance::of(c)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an abstract representation of the initial value of member definition `m`.
|
||||
*
|
||||
* For (non-accessor) methods, this is the abstract function corresponding to the
|
||||
* method. For fields, it is an abstract representation of their initial value(s).
|
||||
*/
|
||||
private AbstractValue getAnInitialMemberValue(MemberDefinition m) {
|
||||
not m instanceof AccessorMethodDefinition and
|
||||
result = m.getInit().analyze().getALocalValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `baseVal` is an abstract value whose properties we track for the purposes
|
||||
* of `getALocalValue`.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
| importWithoutPragma.jsx:1:1:1:27 | import ... react'; | Unused import h. |
|
||||
| multi-imports.js:1:1:1:29 | import ... om 'x'; | Unused imports a, b, d. |
|
||||
| multi-imports.js:2:1:2:42 | import ... om 'x'; | Unused imports alphabetically, ordered. |
|
||||
| require-react-in-other-scope.js:2:9:2:13 | React | Unused variable React. |
|
||||
| typeoftype.ts:9:7:9:7 | y | Unused variable y. |
|
||||
| unusedShadowed.ts:1:1:1:26 | import ... where'; | Unused import T. |
|
||||
| unusedShadowed.ts:2:1:2:31 | import ... where'; | Unused import object. |
|
||||
|
||||
2
javascript/ql/test/query-tests/Declarations/UnusedVariable/react-jsx.js
vendored
Normal file
2
javascript/ql/test/query-tests/Declarations/UnusedVariable/react-jsx.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
var React = x; // OK
|
||||
(<e/>);
|
||||
@@ -0,0 +1,2 @@
|
||||
var React = require("probably-react"); // OK
|
||||
(<e/>);
|
||||
@@ -0,0 +1,2 @@
|
||||
var { React } = { React: require("probably-react") }; // OK
|
||||
(<e/>);
|
||||
@@ -0,0 +1,2 @@
|
||||
var { React } = require("probably-react"); // OK
|
||||
(<e/>);
|
||||
@@ -0,0 +1,6 @@
|
||||
(function() {
|
||||
var React = require("probably-react"); // NOT OK
|
||||
})
|
||||
(function() {
|
||||
(<e/>);
|
||||
})
|
||||
Reference in New Issue
Block a user