42 ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
43 ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
43 ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
44 ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
44 ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
45 ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
45 ## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
46 ## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
46 ## IN THE SOFTWARE. |
47 ## IN THE SOFTWARE. |
47 ###################################################################### |
48 ############################################################################### |
48 # TODO: update to bandit v0.19.2 |
|
49 |
49 |
50 BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant) |
50 BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant) |
51 AST_CONST_TYPES = (ast.Constant, ast.NameConstant, ast.Str, ast.Num) |
51 AST_CONST_TYPES = (ast.Constant, ast.NameConstant, ast.Str, ast.Num) |
52 STR_TYPES = (ast.Constant, ast.Str) |
52 STR_TYPES = (ast.Constant, ast.Str) |
53 |
53 |
264 arg0Name = unparse(call.args[0]) |
281 arg0Name = unparse(call.args[0]) |
265 counter[arg0Name] += 1 |
282 counter[arg0Name] += 1 |
266 |
283 |
267 return [name for name, count in counter.items() if count > 1] |
284 return [name for name, count in counter.items() if count > 1] |
268 |
285 |
269 def __isConstantIncrease(self, expression): |
286 def __isConstantIncrease(self, expr): |
270 """ |
287 """ |
271 Private method check the given expression for being a constant |
288 Private method to check an expression for being a constant increase. |
272 increase. |
289 |
273 |
290 @param expr reference to the node to be checked |
274 @param expression reference to the expression node |
|
275 @type ast.AugAssign |
291 @type ast.AugAssign |
276 @return flag indicating a constant increase |
292 @return flag indicating a constant increase |
277 @rtype bool |
293 @rtype bool |
278 """ |
294 """ |
279 return isinstance(expression.op, ast.Add) and ( |
295 return isinstance(expr.op, ast.Add) and ( |
280 ( |
296 ( |
281 isinstance(expression.value, ast.Constant) |
297 isinstance(expr.value, ast.Constant) |
282 and isinstance(expression.value.value, int) |
298 and expr.value.value == 1 |
283 ) |
299 ) |
284 or isinstance(expression.value, ast.Num) |
300 or (isinstance(expr.value, ast.Num) and expr.value.n == 1) |
285 ) |
301 ) |
286 |
302 |
287 def __getIfBodyPairs(self, node): |
303 def __getIfBodyPairs(self, node): |
288 """ |
304 """ |
289 Private method to extract a list of pairs of test and body for an |
305 Private method to extract a list of pairs of test and body for an |
413 elif isinstance(op, ast.NotIn): |
429 elif isinstance(op, ast.NotIn): |
414 op = ast.In() |
430 op = ast.In() |
415 newNode.ops = [op] |
431 newNode.ops = [op] |
416 return newNode |
432 return newNode |
417 |
433 |
|
434 def __expressionUsesVariable(self, expr, var): |
|
435 """ |
|
436 Private method to check, if a variable is used by an expression. |
|
437 |
|
438 @param expr expression node to be checked |
|
439 @type ast.expr |
|
440 @param var variable name to be checked for |
|
441 @type str |
|
442 @return flag indicating the expression uses the variable |
|
443 @rtype bool |
|
444 """ |
|
445 return var in unparse(expr) |
|
446 # This is WAY too broad, but it's better to have false-negatives than |
|
447 # false-positives. |
|
448 |
|
449 def __bodyContainsContinue(self, stmts): |
|
450 """ |
|
451 Private method to check, if a list of statements contain a 'continue' statement. |
|
452 |
|
453 @param stmts list of statements |
|
454 @type list of ast.stmt |
|
455 @return flag indicating a continue statement |
|
456 @rtype bool |
|
457 """ |
|
458 return any( |
|
459 isinstance(stmt, ast.Continue) |
|
460 or (isinstance(stmt, ast.If) and self.__bodyContainsContinue(stmt.body)) |
|
461 for stmt in stmts |
|
462 ) |
|
463 |
418 ############################################################# |
464 ############################################################# |
419 ## Methods to check for possible code simplifications below |
465 ## Methods to check for possible code simplifications below |
420 ############################################################# |
466 ############################################################# |
421 |
467 |
422 def __check101(self, node): |
468 def __check101(self, node): |
643 and isinstance(node.body[0].targets[0], ast.Name) |
689 and isinstance(node.body[0].targets[0], ast.Name) |
644 and isinstance(node.orelse[0].targets[0], ast.Name) |
690 and isinstance(node.orelse[0].targets[0], ast.Name) |
645 and node.body[0].targets[0].id == node.orelse[0].targets[0].id |
691 and node.body[0].targets[0].id == node.orelse[0].targets[0].id |
646 and not isinstance(node.parent, ast.If) |
692 and not isinstance(node.parent, ast.If) |
647 ): |
693 ): |
648 assign = unparse(node.body[0].targets[0]) |
694 targetVar = node.body[0].targets[0] |
|
695 assign = unparse(targetVar) |
|
696 |
|
697 # It's part of a bigger if-elseif block: |
|
698 if isinstance(node.parent, ast.If): |
|
699 for n in node.parent.body: |
|
700 if ( |
|
701 isinstance(n, ast.Assign) |
|
702 and isinstance(n.targets[0], ast.Name) |
|
703 and n.targets[0].id == targetVar.id |
|
704 ): |
|
705 return |
|
706 |
649 body = unparse(node.body[0].value) |
707 body = unparse(node.body[0].value) |
650 cond = unparse(node.test) |
708 cond = unparse(node.test) |
651 orelse = unparse(node.orelse[0].value) |
709 orelse = unparse(node.orelse[0].value) |
652 |
710 |
653 self.__error( |
711 self.__error( |
718 and isinstance(node.body[0], ast.If) |
776 and isinstance(node.body[0], ast.If) |
719 and len(node.body[0].body) == 1 |
777 and len(node.body[0].body) == 1 |
720 and isinstance(node.body[0].body[0], ast.Return) |
778 and isinstance(node.body[0].body[0], ast.Return) |
721 and isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES) |
779 and isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES) |
722 and hasattr(node.body[0].body[0].value, "value") |
780 and hasattr(node.body[0].body[0].value, "value") |
|
781 and isinstance(node.next_sibling, ast.Return) |
723 ): |
782 ): |
724 check = unparse(node.body[0].test) |
783 check = unparse(node.body[0].test) |
725 target = unparse(node.target) |
784 target = unparse(node.target) |
726 iterable = unparse(node.iter) |
785 iterable = unparse(node.iter) |
727 if node.body[0].body[0].value.value is True: |
786 if node.body[0].body[0].value.value is True: |
728 self.__error( |
787 self.__error( |
729 node.lineno - 1, node.col_offset, "Y110", check, target, iterable |
788 node.lineno - 1, node.col_offset, "Y110", check, target, iterable |
730 ) |
789 ) |
731 elif node.body[0].body[0].value.value is False: |
790 elif node.body[0].body[0].value.value is False: |
732 check = "not " + check |
791 isCompoundExpression = " and " in check or " or " in check |
733 if check.startswith("not not "): |
792 |
734 check = check[len("not not ") :] |
793 if isCompoundExpression: |
|
794 check = f"not ({check})" |
|
795 else: |
|
796 if check.startswith("not "): |
|
797 check = check[len("not "):] |
|
798 else: |
|
799 check = f"not {check}" |
735 self.__error( |
800 self.__error( |
736 node.lineno - 1, node.col_offset, "Y111", check, target, iterable |
801 node.lineno - 1, node.col_offset, "Y111", check, target, iterable |
737 ) |
802 ) |
738 |
803 |
739 def __check112(self, node): |
804 def __check112(self, node): |
824 """ |
889 """ |
825 # idx = 0 |
890 # idx = 0 |
826 # for el in iterable: |
891 # for el in iterable: |
827 # ... |
892 # ... |
828 # idx += 1 |
893 # idx += 1 |
829 variableCandidates = [] |
894 if not self.__bodyContainsContinue(node.body): |
830 for expression in node.body: |
895 # Find variables that might just count the iteration of the current loop |
831 if ( |
896 variableCandidates = [] |
832 isinstance(expression, ast.AugAssign) |
897 for expression in node.body: |
833 and self.__isConstantIncrease(expression) |
898 if ( |
834 and isinstance(expression.target, ast.Name) |
899 isinstance(expression, ast.AugAssign) |
835 ): |
900 and self.__isConstantIncrease(expression) |
836 variableCandidates.append(expression.target) |
901 and isinstance(expression.target, ast.Name) |
837 |
902 ): |
838 for candidate in variableCandidates: |
903 variableCandidates.append(expression.target) |
839 self.__error( |
904 strCandidates = [unparse(x) for x in variableCandidates] |
840 candidate.lineno - 1, candidate.col_offset, "Y113", unparse(candidate) |
905 |
841 ) |
906 olderSiblings = [] |
|
907 for olderSibling in node.parent.body: |
|
908 if olderSibling is node: |
|
909 break |
|
910 olderSiblings.append(olderSibling) |
|
911 |
|
912 matches = [ |
|
913 n.targets[0] |
|
914 for n in olderSiblings |
|
915 if isinstance(n, ast.Assign) |
|
916 and len(n.targets) == 1 |
|
917 and isinstance(n.targets[0], ast.Name) |
|
918 and unparse(n.targets[0]) in strCandidates |
|
919 ] |
|
920 if len(matches) == 0: |
|
921 return |
|
922 |
|
923 sibling = node.previous_sibling |
|
924 while sibling is not None: |
|
925 sibling = sibling.previous_sibling |
|
926 |
|
927 for match in matches: |
|
928 variable = unparse(match) |
|
929 self.__error( |
|
930 match.lineno - 1, match.col_offset, "Y113", variable |
|
931 ) |
842 |
932 |
843 def __check114(self, node): |
933 def __check114(self, node): |
844 """ |
934 """ |
845 Private method to check for alternative if clauses with identical |
935 Private method to check for alternative if clauses with identical |
846 bodies. |
936 bodies. |
945 and isinstance(child.body[0], ast.Return) |
1041 and isinstance(child.body[0], ast.Return) |
946 and len(child.orelse) <= 1 |
1042 and len(child.orelse) <= 1 |
947 ): |
1043 ): |
948 return |
1044 return |
949 |
1045 |
|
1046 returnCall = child.body[0] |
|
1047 if isinstance(returnCall.value, ast.Call): |
|
1048 return |
|
1049 |
950 if isinstance(child.test.comparators[0], ast.Str): |
1050 if isinstance(child.test.comparators[0], ast.Str): |
951 key = child.test.comparators[0].s |
1051 key = child.test.comparators[0].s |
952 elif isinstance(child.test.comparators[0], ast.Num): |
1052 elif isinstance(child.test.comparators[0], ast.Num): |
953 key = child.test.comparators[0].n |
1053 key = child.test.comparators[0].n |
954 else: |
1054 else: |
955 key = child.test.comparators[0].value |
1055 key = child.test.comparators[0].value |
956 keyValuePairs[key] = unparse(child.body[0].value).strip("'") |
1056 |
|
1057 value = unparse(child.body[0].value) |
|
1058 if value[0] == '"' and value[-1] == '"': |
|
1059 value = value[1:-1] |
|
1060 keyValuePairs[key] = value |
|
1061 |
957 if len(child.orelse) == 1: |
1062 if len(child.orelse) == 1: |
958 if isinstance(child.orelse[0], ast.If): |
1063 if isinstance(child.orelse[0], ast.If): |
959 child = child.orelse[0] |
1064 child = child.orelse[0] |
960 elif isinstance(child.orelse[0], ast.Return): |
1065 elif isinstance(child.orelse[0], ast.Return): |
961 elseValue = unparse(child.orelse[0].value) |
1066 elseValue = unparse(child.orelse[0].value) |
1123 ): |
1228 ): |
1124 key = unparse(node.test.left) |
1229 key = unparse(node.test.left) |
1125 dictname = unparse(node.test.comparators[0]) |
1230 dictname = unparse(node.test.comparators[0]) |
1126 self.__error(node.lineno - 1, node.col_offset, "Y122", dictname, key) |
1231 self.__error(node.lineno - 1, node.col_offset, "Y122", dictname, key) |
1127 |
1232 |
|
1233 def __check123(self, node): |
|
1234 """ |
|
1235 Private method to check for complicated dictionary access with default value. |
|
1236 |
|
1237 @param node reference to the AST node to be checked |
|
1238 @type ast.If |
|
1239 """ |
|
1240 isPattern1 = ( |
|
1241 len(node.body) == 1 |
|
1242 and isinstance(node.body[0], ast.Assign) |
|
1243 and len(node.body[0].targets) == 1 |
|
1244 and isinstance(node.body[0].value, ast.Subscript) |
|
1245 and len(node.orelse) == 1 |
|
1246 and isinstance(node.orelse[0], ast.Assign) |
|
1247 and len(node.orelse[0].targets) == 1 |
|
1248 and isinstance(node.test, ast.Compare) |
|
1249 and len(node.test.ops) == 1 |
|
1250 and isinstance(node.test.ops[0], ast.In) |
|
1251 ) |
|
1252 |
|
1253 # just like pattern_1, but using NotIn and reversing if/else |
|
1254 isPattern2 = ( |
|
1255 len(node.body) == 1 |
|
1256 and isinstance(node.body[0], ast.Assign) |
|
1257 and len(node.orelse) == 1 |
|
1258 and isinstance(node.orelse[0], ast.Assign) |
|
1259 and isinstance(node.orelse[0].value, ast.Subscript) |
|
1260 and isinstance(node.test, ast.Compare) |
|
1261 and len(node.test.ops) == 1 |
|
1262 and isinstance(node.test.ops[0], ast.NotIn) |
|
1263 ) |
|
1264 |
|
1265 if isPattern1: |
|
1266 key = node.test.left |
|
1267 if unparse(key) != unparse(node.body[0].value.slice): |
|
1268 return |
|
1269 assignToIfBody = node.body[0].targets[0] |
|
1270 assignToElse = node.orelse[0].targets[0] |
|
1271 if unparse(assignToIfBody) != unparse(assignToElse): |
|
1272 return |
|
1273 dictName = node.test.comparators[0] |
|
1274 defaultValue = node.orelse[0].value |
|
1275 valueNode = node.body[0].targets[0] |
|
1276 keyStr = unparse(key) |
|
1277 dictStr = unparse(dictName) |
|
1278 defaultStr = unparse(defaultValue) |
|
1279 valueStr = unparse(valueNode) |
|
1280 elif isPattern2: |
|
1281 key = node.test.left |
|
1282 if unparse(key) != unparse(node.orelse[0].value.slice): |
|
1283 return |
|
1284 dictName = node.test.comparators[0] |
|
1285 defaultValue = node.body[0].value |
|
1286 valueNode = node.body[0].targets[0] |
|
1287 keyStr = unparse(key) |
|
1288 dictStr = unparse(dictName) |
|
1289 defaultStr = unparse(defaultValue) |
|
1290 valueStr = unparse(valueNode) |
|
1291 else: |
|
1292 return |
|
1293 self.__error(node.lineno - 1, node.col_offset, "Y123", valueStr, dictStr, |
|
1294 keyStr, defaultStr) |
|
1295 |
1128 def __check181(self, node): |
1296 def __check181(self, node): |
1129 """ |
1297 """ |
1130 Private method to check for assignments that could be converted into |
1298 Private method to check for assignments that could be converted into |
1131 an augmented assignment. |
1299 an augmented assignment. |
1132 |
1300 |
1548 ) |
1716 ) |
1549 |
1717 |
1550 if hasBareNumeric and not isException: |
1718 if hasBareNumeric and not isException: |
1551 self.__error(node.lineno - 1, node.col_offset, "Y402") |
1719 self.__error(node.lineno - 1, node.col_offset, "Y402") |
1552 |
1720 |
|
1721 def __check901(self, node): |
|
1722 """ |
|
1723 Private method to check for unnecessary bool conversion. |
|
1724 |
|
1725 @param node reference to the AST node to be checked |
|
1726 @type ast.Call |
|
1727 """ |
|
1728 if ( |
|
1729 isinstance(node.func, ast.Name) |
|
1730 and node.func.id == "bool" |
|
1731 and len(node.args) == 1 |
|
1732 and isinstance(node.args[0], ast.Compare) |
|
1733 ): |
|
1734 actual = unparse(node) |
|
1735 expected = unparse(node.args[0]) |
|
1736 self.__error(node.lineno - 1, node.col_offset, "Y901", expected, actual) |
|
1737 |
|
1738 def __check904(self, node): |
|
1739 """ |
|
1740 Private method to check for dictionary initialization. |
|
1741 |
|
1742 @param node reference to the AST node to be checked |
|
1743 @type ast.Assign |
|
1744 """ |
|
1745 # a = {}; a['b'] = 'c' |
|
1746 n2 = node.next_sibling |
|
1747 if ( |
|
1748 isinstance(node.value, ast.Dict) |
|
1749 and isinstance(n2, ast.Assign) |
|
1750 and len(n2.targets) == 1 |
|
1751 and len(node.targets) == 1 |
|
1752 and isinstance(n2.targets[0], ast.Subscript) |
|
1753 and isinstance(n2.targets[0].value, ast.Name) |
|
1754 and isinstance(node.targets[0], ast.Name) |
|
1755 and n2.targets[0].value.id == node.targets[0].id |
|
1756 ): |
|
1757 dictName = unparse(node.targets[0]) |
|
1758 if not self.__expressionUsesVariable(n2.value, dictName): |
|
1759 self.__error(node.lineno - 1, node.col_offset, "Y904", dictName) |
|
1760 |
|
1761 def __check905(self, node): |
|
1762 """ |
|
1763 Private method to check for list initialization by splitting a string. |
|
1764 |
|
1765 @param node reference to the AST node to be checked |
|
1766 @type ast.Call |
|
1767 """ |
|
1768 if ( |
|
1769 isinstance(node.func, ast.Attribute) |
|
1770 and node.func.attr == "split" |
|
1771 and isinstance(node.func.value, (ast.Str, ast.Constant)) |
|
1772 ): |
|
1773 if isinstance(node.func.value, ast.Constant): |
|
1774 value = node.func.value.value |
|
1775 else: |
|
1776 value = node.func.value.s |
|
1777 |
|
1778 expected = json.dumps(value.split()) |
|
1779 actual = unparse(node.func.value) + ".split()" |
|
1780 self.__error(node.lineno - 1, node.col_offset, "Y905", expected, actual) |
|
1781 |
|
1782 def __check906(self, node): |
|
1783 """ |
|
1784 Private method to check for unnecessary nesting of os.path.join(). |
|
1785 |
|
1786 @param node reference to the AST node to be checked |
|
1787 @type ast.Call |
|
1788 """ |
|
1789 # __IGNORE_WARNING_D234r__ |
|
1790 def getOsPathJoinArgs(node): |
|
1791 names = [] |
|
1792 for arg in node.args: |
|
1793 if ( |
|
1794 isinstance(arg, ast.Call) |
|
1795 and isinstance(arg.func, ast.Attribute) |
|
1796 and isinstance(arg.func.value, ast.Attribute) |
|
1797 and isinstance(arg.func.value.value, ast.Name) |
|
1798 and arg.func.value.value.id == "os" |
|
1799 and arg.func.value.attr == "path" |
|
1800 and arg.func.attr == "join" |
|
1801 ): |
|
1802 names += getOsPathJoinArgs(arg) |
|
1803 elif isinstance(arg, ast.Name): |
|
1804 names.append(arg.id) |
|
1805 elif isinstance(arg, ast.Str): |
|
1806 names.append(f"'{arg.s}'") |
|
1807 return names |
|
1808 |
|
1809 if ( |
|
1810 isinstance(node.func, ast.Attribute) |
|
1811 and isinstance(node.func.value, ast.Attribute) |
|
1812 and isinstance(node.func.value.value, ast.Name) |
|
1813 and node.func.value.value.id == "os" |
|
1814 and node.func.value.attr == "path" |
|
1815 and node.func.attr == "join" |
|
1816 and len(node.args) == 2 |
|
1817 and any( |
|
1818 ( |
|
1819 isinstance(arg, ast.Call) |
|
1820 and isinstance(arg.func, ast.Attribute) |
|
1821 and isinstance(arg.func.value, ast.Attribute) |
|
1822 and isinstance(arg.func.value.value, ast.Name) |
|
1823 and arg.func.value.value.id == "os" |
|
1824 and arg.func.value.attr == "path" |
|
1825 and arg.func.attr == "join" |
|
1826 ) |
|
1827 for arg in node.args |
|
1828 ) |
|
1829 ): |
|
1830 names = getOsPathJoinArgs(node) |
|
1831 |
|
1832 actual = unparse(node) |
|
1833 expected = "os.path.join({0})".format(', '.join(names)) |
|
1834 self.__error(node.lineno - 1, node.col_offset, "Y906", expected, actual) |
|
1835 |
|
1836 def __check907(self, node): |
|
1837 """ |
|
1838 Private method to check for Union type annotation with None. |
|
1839 |
|
1840 @param node reference to the AST node to be checked |
|
1841 @type ast.Subscript |
|
1842 """ |
|
1843 if (isinstance(node.value, ast.Name) and node.value.id == "Union"): |
|
1844 if ( |
|
1845 isinstance(node.slice, ast.Index) |
|
1846 and isinstance(node.slice.value, ast.Tuple) |
|
1847 ): |
|
1848 # Python 3.8 |
|
1849 tupleVar = node.slice.value |
|
1850 elif isinstance(node.slice, ast.Tuple): |
|
1851 # Python 3.9+ |
|
1852 tupleVar = node.slice |
|
1853 else: |
|
1854 return |
|
1855 |
|
1856 hasNone = False |
|
1857 others = [] |
|
1858 for elt in tupleVar.elts: |
|
1859 if isinstance(elt, BOOL_CONST_TYPES) and elt.value is None: |
|
1860 hasNone = True |
|
1861 else: |
|
1862 others.append(elt) |
|
1863 |
|
1864 if len(others) == 1 and hasNone: |
|
1865 type_ = unparse(others[0]) |
|
1866 self.__error( |
|
1867 node.lineno - 1, node.col_offset, "Y907", type_, unparse(node) |
|
1868 ) |
|
1869 |
|
1870 def __check909(self, node): |
|
1871 """ |
|
1872 Private method to check for reflexive assignments. |
|
1873 |
|
1874 @param node reference to the AST node to be checked |
|
1875 @type ast.Assign |
|
1876 """ |
|
1877 names = [] |
|
1878 if isinstance(node.value, (ast.Name, ast.Subscript, ast.Tuple)): |
|
1879 names.append(unparse(node.value)) |
|
1880 for target in node.targets: |
|
1881 names.append(unparse(target)) |
|
1882 |
|
1883 if len(names) != len(set(names)) and not isinstance(node.parent, ast.ClassDef): |
|
1884 srccode = unparse(node) |
|
1885 self.__error(node.lineno - 1, node.col_offset, "Y909", srccode) |
1553 |
1886 |
1554 # |
1887 # |
1555 # eflag: noqa = M891 |
1888 # eflag: noqa = M891 |