Merge branch 'master' into python-objectapi-to-valueapi-signatureoverriddenmethod

This commit is contained in:
Rebecca Valentine
2020-03-10 08:06:45 -07:00
56 changed files with 1160 additions and 1401 deletions

View File

@@ -13,6 +13,17 @@ private int varargs_length_objectapi(Call call) {
result = count(call.getStarargs().(List).getAnElt())
}
private int varargs_length(Call call) {
not exists(call.getStarargs()) and result = 0
or
exists(TupleValue t |
call.getStarargs().pointsTo(t) |
result = t.length()
)
or
result = count(call.getStarargs().(List).getAnElt())
}
/** Gets a keyword argument that is not a keyword-only parameter. */
private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) {
func.getACall().getNode() = call and
@@ -20,13 +31,20 @@ private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) {
not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg()
}
/** Gets a keyword argument that is not a keyword-only parameter. */
private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
func.getACall().getNode() = call and
result = call.getAKeyword() and
not func.getScope().getAKeywordOnlyArg().getId() = result.getArg()
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/
private int positional_arg_count_objectapi_for_call_objectapi(Call call, Object callable) {
private int positional_arg_count_for_call_objectapi(Call call, Object callable) {
call = get_a_call_objectapi(callable).getNode() and
exists(int positional_keywords |
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
@@ -40,10 +58,34 @@ private int positional_arg_count_objectapi_for_call_objectapi(Call call, Object
)
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/
private int positional_arg_count_for_call(Call call, Value callable) {
call = get_a_call(callable).getNode() and
exists(int positional_keywords |
exists(FunctionValue func | func = get_function_or_initializer(callable) |
not func.getScope().hasKwArg() and
positional_keywords = count(not_keyword_only_arg(call, func))
or
func.getScope().hasKwArg() and positional_keywords = 0
)
|
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
)
}
int arg_count_objectapi(Call call) {
result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword())
}
int arg_count(Call call) {
result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword())
}
/* Gets a call corresponding to the given class or function*/
private ControlFlowNode get_a_call_objectapi(Object callable) {
result = callable.(ClassObject).getACall()
@@ -51,6 +93,13 @@ private ControlFlowNode get_a_call_objectapi(Object callable) {
result = callable.(FunctionObject).getACall()
}
/* Gets a call corresponding to the given class or function*/
private ControlFlowNode get_a_call(Value callable) {
result = callable.(ClassValue).getACall()
or
result = callable.(FunctionValue).getACall()
}
/* Gets the function object corresponding to the given class or function*/
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
result = func_or_cls.(FunctionObject)
@@ -58,6 +107,13 @@ FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
}
/* Gets the function object corresponding to the given class or function*/
FunctionValue get_function_or_initializer(Value func_or_cls) {
result = func_or_cls.(FunctionValue)
or
result = func_or_cls.(ClassValue).declaredAttribute("__init__")
}
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */
predicate illegally_named_parameter_objectapi(Call call, Object func, string name) {
@@ -67,6 +123,14 @@ predicate illegally_named_parameter_objectapi(Call call, Object func, string nam
not get_function_or_initializer_objectapi(func).isLegalArgumentName(name)
}
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */
predicate illegally_named_parameter(Call call, Value func, string name) {
not func.isBuiltin() and
name = call.getANamedArgumentName() and
call.getAFlowNode() = get_a_call(func) and
not get_function_or_initializer(func).isLegalArgumentName(name)
}
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
predicate too_few_args_objectapi(Call call, Object callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
@@ -88,6 +152,27 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) {
)
}
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
predicate too_few_args(Call call, Value callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and
not exists(call.getStarargs()) and not exists(call.getKwargs()) and
arg_count(call) < limit and
exists(FunctionValue func | func = get_function_or_initializer(callable) |
call = func.getACall().getNode() and limit = func.minParameters() and
/* The combination of misuse of `mox.Mox().StubOutWithMock()`
* and a bug in mox's implementation of methods results in having to
* pass 1 too few arguments to the mocked function.
*/
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or
call = func.getACall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and limit = func.minParameters() - 1
)
}
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
predicate too_many_args_objectapi(Call call, Object callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
@@ -103,7 +188,25 @@ predicate too_many_args_objectapi(Call call, Object callable, int limit) {
callable instanceof ClassObject and
call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.maxParameters() - 1
) and
positional_arg_count_objectapi_for_call_objectapi(call, callable) > limit
positional_arg_count_for_call_objectapi(call, callable) > limit
}
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
predicate too_many_args(Call call, Value callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and
exists(FunctionValue func |
func = get_function_or_initializer(callable) and
not func.getScope().hasVarArg() and limit >= 0
|
call = func.getACall().getNode() and limit = func.maxParameters()
or
call = func.getACall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and limit = func.maxParameters() - 1
) and
positional_arg_count_for_call(call, callable) > limit
}
/** Holds if `call` has too many or too few arguments for `func` */
@@ -113,6 +216,13 @@ predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string
too_many_args_objectapi(call, func, limit) and too = "too many"
}
/** Holds if `call` has too many or too few arguments for `func` */
predicate wrong_args(Call call, FunctionValue func, int limit, string too) {
too_few_args(call, func, limit) and too = "too few"
or
too_many_args(call, func, limit) and too = "too many"
}
/** Holds if `call` has correct number of arguments for `func`.
* Implies nothing about whether `call` could call `func`.
*/
@@ -123,8 +233,25 @@ predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject f
arg_count_objectapi(call) < func.maxParameters()
}
/** Holds if `call` has correct number of arguments for `func`.
* Implies nothing about whether `call` could call `func`.
*/
bindingset[call, func]
predicate correct_args_if_called_as_method(Call call, FunctionValue func) {
arg_count(call)+1 >= func.minParameters()
and
arg_count(call) < func.maxParameters()
}
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) {
overriding.overrides(func) and
overriding.getACall().getNode() = call
}
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) {
overriding.overrides(func) and
overriding.getACall().getNode() = call
}

View File

@@ -15,29 +15,30 @@
import python
import semmle.python.strings
predicate string_format(BinaryExpr operation, StrConst str, Object args, AstNode origin) {
exists(Object fmt, Context ctx | operation.getOp() instanceof Mod |
operation.getLeft().refersTo(ctx, fmt, _, str) and
operation.getRight().refersTo(ctx, args, _, origin)
predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) {
operation.getOp() instanceof Mod and
exists(Value fmt, Context ctx |
operation.getLeft().pointsTo(ctx, fmt, str) and
operation.getRight().pointsTo(ctx, args, origin)
)
}
int sequence_length(Object args) {
int sequence_length(Value args) {
/* Guess length of sequence */
exists(Tuple seq |
seq = args.getOrigin() |
exists(Tuple seq, AstNode origin |
seq.pointsTo(args,origin) |
result = strictcount(seq.getAnElt()) and
not seq.getAnElt() instanceof Starred
)
or
exists(ImmutableLiteral i |
i.getLiteralObject() = args |
i.getLiteralValue() = args |
result = 1
)
}
from BinaryExpr operation, StrConst fmt, Object args, int slen, int alen, AstNode origin, string provided
from BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin, string provided
where string_format(operation, fmt, args, origin) and slen = sequence_length(args) and alen = format_items(fmt) and slen != alen and
(if slen = 1 then provided = " is provided." else provided = " are provided.")
select operation, "Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " + slen.toString() + provided,

View File

@@ -112,7 +112,7 @@ predicate is_quad_op(string name) {
name = "__setslice__" or name = "__exit__"
}
int argument_count(PyFunctionObject f, string name, ClassObject cls) {
int argument_count(PythonFunctionValue f, string name, ClassValue cls) {
cls.declaredAttribute(name) = f and
(
is_unary_op(name) and result = 1
@@ -125,7 +125,7 @@ int argument_count(PyFunctionObject f, string name, ClassObject cls) {
)
}
predicate incorrect_special_method_defn(PyFunctionObject func, string message, boolean show_counts, string name, ClassObject owner) {
predicate incorrect_special_method_defn(PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner) {
exists(int required |
required = argument_count(func, name, owner) |
/* actual_non_default <= actual */
@@ -133,14 +133,14 @@ predicate incorrect_special_method_defn(PyFunctionObject func, string message, b
(message = "Too few parameters" and show_counts = true)
else if required < func.minParameters() then
(message = "Too many parameters" and show_counts = true)
else if (func.minParameters() < required and not func.getFunction().hasVarArg()) then
else if (func.minParameters() < required and not func.getScope().hasVarArg()) then
(message = (required -func.minParameters()) + " default values(s) will never be used" and show_counts = false)
else
none()
)
}
predicate incorrect_pow(FunctionObject func, string message, boolean show_counts, ClassObject owner) {
predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
owner.declaredAttribute("__pow__") = func and
(
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
@@ -153,19 +153,19 @@ predicate incorrect_pow(FunctionObject func, string message, boolean show_counts
)
}
predicate incorrect_get(FunctionObject func, string message, boolean show_counts, ClassObject owner) {
predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
owner.declaredAttribute("__get__") = func and
(
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
or
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
or
func.minParameters() < 2 and not func.getFunction().hasVarArg() and
func.minParameters() < 2 and not func.getScope().hasVarArg() and
message = (2 - func.minParameters()) + " default value(s) will never be used" and show_counts = false
)
}
string should_have_parameters(PyFunctionObject f, string name, ClassObject owner) {
string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) {
exists(int i | i = argument_count(f, name, owner) |
result = i.toString()
)
@@ -173,7 +173,7 @@ string should_have_parameters(PyFunctionObject f, string name, ClassObject owner
owner.declaredAttribute(name) = f and (name = "__get__" or name = "__pow__") and result = "2 or 3"
}
string has_parameters(PyFunctionObject f) {
string has_parameters(PythonFunctionValue f) {
exists(int i | i = f.minParameters() |
i = 0 and result = "no parameters"
or
@@ -183,7 +183,7 @@ string has_parameters(PyFunctionObject f) {
)
}
from PyFunctionObject f, string message, string sizes, boolean show_counts, string name, ClassObject owner
from PythonFunctionValue f, string message, string sizes, boolean show_counts, string name, ClassValue owner
where
(
incorrect_special_method_defn(f, message, show_counts, name, owner)

View File

@@ -23,12 +23,12 @@ predicate is_used(Call c) {
)
}
from Call c, FunctionObject func
from Call c, FunctionValue func
where
/* Call result is used, but callee is a procedure */
is_used(c) and c.getFunc().refersTo(func) and func.getFunction().isProcedure() and
is_used(c) and c.getFunc().pointsTo(func) and func.getScope().isProcedure() and
/* All callees are procedures */
forall(FunctionObject callee | c.getFunc().refersTo(callee) | callee.getFunction().isProcedure()) and
forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and
/* Mox return objects have an `AndReturn` method */
not useOfMoxInModule(c.getEnclosingModule())
select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName()

View File

@@ -5,7 +5,7 @@ predicate monkey_patched_builtin(string name) {
subscr.isStore() and
subscr.getIndex().getNode() = s and
s.getText() = name and
subscr.getValue() = attr and
subscr.getObject() = attr and
attr.getObject("__dict__").pointsTo(Module::builtinModule())
)
or

View File

@@ -286,14 +286,14 @@ module DictKind {
pragma[noinline]
private predicate subscript_index(ControlFlowNode obj, SubscriptNode sub) {
sub.isLoad() and
sub.getValue() = obj and
sub.getObject() = obj and
not sub.getNode().getIndex() instanceof Slice
}
pragma[noinline]
private predicate subscript_slice(ControlFlowNode obj, SubscriptNode sub) {
sub.isLoad() and
sub.getValue() = obj and
sub.getObject() = obj and
sub.getNode().getIndex() instanceof Slice
}

View File

@@ -514,6 +514,28 @@ abstract class FunctionValue extends CallableValue {
predicate isOverriddenMethod() {
exists(Value f | f.overrides(this))
}
/** Whether `name` is a legal argument name for this function */
bindingset[name]
predicate isLegalArgumentName(string name) {
this.getScope().getAnArg().asName().getId() = name
or
this.getScope().getAKeywordOnlyArg().getId() = name
or
this.getScope().hasKwArg()
}
/** Whether this is a "normal" method, that is, it is exists as a class attribute
* which is not a lambda and not the __new__ method. */
predicate isNormalMethod() {
exists(ClassValue cls, string name |
cls.declaredAttribute(name) = this and
name != "__new__" and
exists(Expr expr, AstNode origin | expr.pointsTo(this, origin) |
not origin instanceof Lambda
)
)
}
}
/** Class representing Python functions */

View File

@@ -12,25 +12,27 @@ private predicate re_module_function(string name, int flags) {
name = "subn" and flags = 4
}
/**
* Holds if `s` is used as a regex with the `re` module, with the regex-mode `mode` (if known).
* If regex mode is not known, `mode` will be `"None"`.
*/
predicate used_as_regex(Expr s, string mode) {
(s instanceof Bytes or s instanceof Unicode)
and
exists(ModuleValue re | re.getName() = "re" |
/* Call to re.xxx(regex, ... [mode]) */
exists(CallNode call, string name |
call.getArg(0).refersTo(_, _, s.getAFlowNode()) and
call.getFunction().pointsTo(re.attr(name)) |
mode = "None"
or
exists(Value obj |
mode = mode_from_mode_object(obj) |
exists(int flags_arg |
re_module_function(name, flags_arg) and
call.getArg(flags_arg).pointsTo(obj)
)
or
call.getArgByName("flags").pointsTo(obj)
/* Call to re.xxx(regex, ... [mode]) */
exists(CallNode call, string name |
call.getArg(0).refersTo(_, _, s.getAFlowNode()) and
call.getFunction().pointsTo(Module::named("re").attr(name)) |
mode = "None"
or
exists(Value obj |
mode = mode_from_mode_object(obj) |
exists(int flags_arg |
re_module_function(name, flags_arg) and
call.getArg(flags_arg).pointsTo(obj)
)
or
call.getArgByName("flags").pointsTo(obj)
)
)
}

View File

@@ -104,7 +104,7 @@ private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) {
exists(Slice all |
all = tonode.getIndex().getNode() and
not exists(all.getStart()) and not exists(all.getStop()) and
tonode.getValue() = fromnode
tonode.getObject() = fromnode
)
}

View File

@@ -134,7 +134,7 @@ private predicate json_subscript_taint(
SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key
) {
sub.isLoad() and
sub.getValue() = obj and
sub.getObject() = obj and
key = seq.getValue()
}

View File

@@ -82,7 +82,7 @@ private predicate tracking_step(ControlFlowNode src, ControlFlowNode dest) {
or
src = dest.(AttrNode).getObject()
or
src = dest.(SubscriptNode).getValue()
src = dest.(SubscriptNode).getObject()
or
tracked_call_step(src, dest)
or

View File

@@ -26,7 +26,7 @@ class WsgiEnvironment extends TaintKind {
tonode.(CallNode).getFunction().(AttrNode).getObject("get") = fromnode and
tonode.(CallNode).getArg(0).pointsTo(key)
or
tonode.(SubscriptNode).getValue() = fromnode and tonode.isLoad() and
tonode.(SubscriptNode).getObject() = fromnode and tonode.isLoad() and
tonode.(SubscriptNode).getIndex().pointsTo(key)
|
key = Value::forString(text) and result instanceof ExternalStringKind and
@@ -66,7 +66,7 @@ class UntrustedCookie extends TaintKind {
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
tonode.(SubscriptNode).getValue() = fromnode and
tonode.(SubscriptNode).getObject() = fromnode and
result instanceof UntrustedMorsel
}

View File

@@ -21,7 +21,7 @@ class DjangoRequest extends TaintKind {
/* Helper for getTaintForStep() */
pragma[noinline]
private predicate subscript_taint(SubscriptNode sub, ControlFlowNode obj, TaintKind kind) {
sub.getValue() = obj and
sub.getObject() = obj and
kind instanceof ExternalStringKind
}

View File

@@ -0,0 +1,3 @@
| test.py:5:7:5:9 | ControlFlowNode for foo | int 42 |
| test.py:11:11:11:13 | ControlFlowNode for foo | int 1 |
| test.py:17:11:17:13 | ControlFlowNode for foo | <MISSING pointsTo()> |

View File

@@ -0,0 +1,10 @@
import python
from NameNode name, CallNode call, string debug
where
call.getAnArg() = name and
call.getFunction().(NameNode).getId() = "check" and
if exists(name.pointsTo())
then debug = name.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select name, debug

View File

@@ -0,0 +1 @@
semmle-extractor-options: --max-import-depth=1 --lang=3

View File

@@ -0,0 +1,17 @@
# Only a problem in Python 3
from urllib.parse import urlsplit
foo = 42
check(foo)
def func(url):
parts = urlsplit(url)
foo = 1
check(foo)
if parts.path: # using `urlsplit(url).path` here is equivalent
return # using `pass` here instead makes points-to work
foo = 2
check(foo) # no points-to information

View File

@@ -0,0 +1 @@
| test.py:5:7:5:13 | ControlFlowNode for PATTERN | <MISSING pointsTo()> |

View File

@@ -0,0 +1,10 @@
import python
from NameNode name, CallNode call, string debug
where
call.getAnArg() = name and
call.getFunction().(NameNode).getId() = "check" and
if exists(name.pointsTo())
then debug = name.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select name, debug

View File

@@ -0,0 +1,5 @@
import re
PATTERN = re.compile("a|b")
check(PATTERN)

View File

@@ -1,9 +1,9 @@
| om_test.py:59:5:59:28 | Function __div__ | Too many parameters for special method __div__, which has 3 parameters, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:62:5:62:22 | Function __mul__ | Too few parameters for special method __mul__, which has 1 parameter, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:65:5:65:29 | Function __neg__ | Too many parameters for special method __neg__, which has 2 parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:68:5:68:35 | Function __exit__ | Too few parameters for special method __exit__, which has 3 parameters, but should have 4, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:71:5:71:19 | Function __repr__ | Too few parameters for special method __repr__, which has no parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:74:5:74:46 | Function __add__ | 1 default values(s) will never be used for special method __add__, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:97:15:97:34 | Function lambda | Too few parameters for special method __sub__, which has 1 parameter, but should have 2, in class $@. | om_test.py:95:1:95:28 | class NotOKSpecials | NotOKSpecials |
| om_test.py:59:5:59:28 | Function WrongSpecials.__div__ | Too many parameters for special method __div__, which has 3 parameters, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:62:5:62:22 | Function WrongSpecials.__mul__ | Too few parameters for special method __mul__, which has 1 parameter, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:65:5:65:29 | Function WrongSpecials.__neg__ | Too many parameters for special method __neg__, which has 2 parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:68:5:68:35 | Function WrongSpecials.__exit__ | Too few parameters for special method __exit__, which has 3 parameters, but should have 4, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:71:5:71:19 | Function WrongSpecials.__repr__ | Too few parameters for special method __repr__, which has no parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:74:5:74:46 | Function WrongSpecials.__add__ | 1 default values(s) will never be used for special method __add__, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials |
| om_test.py:97:15:97:34 | Function NotOKSpecials.lambda | Too few parameters for special method __sub__, which has 1 parameter, but should have 2, in class $@. | om_test.py:95:1:95:28 | class NotOKSpecials | NotOKSpecials |
| protocols.py:104:1:104:12 | Function f | Too few parameters for special method __add__, which has 1 parameter, but should have 2, in class $@. | protocols.py:107:1:107:29 | class MissingMethods | MissingMethods |
| protocols.py:104:1:104:12 | Function f | Too few parameters for special method __set__, which has 1 parameter, but should have 3, in class $@. | protocols.py:107:1:107:29 | class MissingMethods | MissingMethods |