Merge branch 'master' into range-analysis

This commit is contained in:
Max Schaefer
2018-11-30 09:36:40 +00:00
committed by GitHub
25 changed files with 235 additions and 1245 deletions

View 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

View File

@@ -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

View File

@@ -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, _))
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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))),

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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";
}
}
}

View File

@@ -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() |

View File

@@ -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()
}
}

View File

@@ -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`.

View File

@@ -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. |

View File

@@ -0,0 +1,2 @@
var React = x; // OK
(<e/>);

View File

@@ -0,0 +1,2 @@
var React = require("probably-react"); // OK
(<e/>);

View File

@@ -0,0 +1,2 @@
var { React } = { React: require("probably-react") }; // OK
(<e/>);

View File

@@ -0,0 +1,2 @@
var { React } = require("probably-react"); // OK
(<e/>);

View File

@@ -0,0 +1,6 @@
(function() {
var React = require("probably-react"); // NOT OK
})
(function() {
(<e/>);
})