eric6/Plugins/CheckerPlugins/CodeStyleChecker/Security/SecurityChecker.py

changeset 7612
ca1ce1e0fcff
child 7613
382f89c11e27
equal deleted inserted replaced
7611:d546c4e72f52 7612:ca1ce1e0fcff
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the security checker.
8 """
9
10 import sys
11 import ast
12 import collections
13
14 from . import Checks
15 from .SecurityNodeVisitor import SecurityNodeVisitor
16
17
18 class SecurityChecker(object):
19 """
20 Class implementing a checker for security issues.
21 """
22 def __init__(self, source, filename, select, ignore, expected, repeat,
23 args):
24 """
25 Constructor
26
27 @param source source code to be checked
28 @type list of str
29 @param filename name of the source file
30 @type str
31 @param select list of selected codes
32 @type list of str
33 @param ignore list of codes to be ignored
34 @type list of str
35 @param expected list of expected codes
36 @type list of str
37 @param repeat flag indicating to report each occurrence of a code
38 @type bool
39 @param args dictionary of arguments for the miscellaneous checks
40 @type dict
41 """
42 self.__select = tuple(select)
43 self.__ignore = ('',) if select else tuple(ignore)
44 self.__expected = expected[:]
45 self.__repeat = repeat
46 self.__filename = filename
47 self.__source = source[:]
48 self.__args = args
49
50 # statistics counters
51 self.counters = {}
52
53 # collection of detected errors
54 self.errors = []
55
56 checkersWithCodes = Checks.generateCheckersDict()
57
58 self.__checkers = collections.defaultdict(list)
59 for checkType, checkersList in checkersWithCodes.items():
60 for checker, codes in checkersList:
61 if any(not (code and self.__ignoreCode(code))
62 for code in codes):
63 self.__checkers[checkType].append(checker)
64
65 def __ignoreCode(self, code):
66 """
67 Private method to check if the message code should be ignored.
68
69 @param code message code to check for
70 @type str
71 @return flag indicating to ignore the given code
72 @rtype bool
73 """
74 return (code.startswith(self.__ignore) and
75 not code.startswith(self.__select))
76
77 def reportError(self, lineNumber, offset, code, severity, confidence,
78 *args):
79 """
80 Private method to record an issue.
81
82 @param lineNumber line number of the issue
83 @type int
84 @param offset position within line of the issue
85 @type int
86 @param code message code
87 @type str
88 @param severity severity code (H = high, M = medium, L = low,
89 U = undefined)
90 @type str
91 @param configence confidence code (H = high, M = medium, L = low,
92 U = undefined)
93 @type str
94 @param args arguments for the message
95 @type list
96 """
97 if self.__ignoreCode(code):
98 return
99
100 if code in self.counters:
101 self.counters[code] += 1
102 else:
103 self.counters[code] = 1
104
105 # Don't care about expected codes
106 if code in self.__expected:
107 return
108
109 if code and (self.counters[code] == 1 or self.__repeat):
110 # record the issue with one based line number
111 self.errors.append({
112 "file": self.__filename,
113 "line": lineNumber + 1,
114 "offset": offset,
115 "code": code,
116 "args": args,
117 "severity": severity,
118 "confidence": confidence,
119 })
120
121 def __reportInvalidSyntax(self):
122 """
123 Private method to report a syntax error.
124 """
125 exc_type, exc = sys.exc_info()[:2]
126 if len(exc.args) > 1:
127 offset = exc.args[1]
128 if len(offset) > 2:
129 offset = offset[1:3]
130 else:
131 offset = (1, 0)
132 self.__error(offset[0] - 1,
133 offset[1] or 0,
134 'S999',
135 "H",
136 "H",
137 exc_type.__name__, exc.args[0])
138
139 def __generateTree(self):
140 """
141 Private method to generate an AST for our source.
142
143 @return generated AST
144 @rtype ast.AST
145 """
146 source = "".join(self.__source)
147 # Check type for py2: if not str it's unicode
148 if sys.version_info[0] == 2:
149 try:
150 source = source.encode('utf-8')
151 except UnicodeError:
152 pass
153
154 return compile(source, self.__filename, 'exec', ast.PyCF_ONLY_AST)
155
156 def getConfig(self):
157 """
158 Public method to get the configuration dictionary.
159
160 @return dictionary containing the configuration
161 @rtype dict
162 """
163 return self.__args
164
165 def run(self):
166 """
167 Public method to check the given source against security related
168 conditions.
169 """
170 if not self.__filename:
171 # don't do anything, if essential data is missing
172 return
173
174 if not self.__checkers:
175 # don't do anything, if no codes were selected
176 return
177
178 try:
179 self.__tree = self.__generateTree()
180 except (SyntaxError, TypeError):
181 self.__reportInvalidSyntax()
182 return
183
184 securityNodeVisitor = SecurityNodeVisitor(
185 self, self.__checkers, self.__filename)
186 securityNodeVisitor.generic_visit(self.__tree)

eric ide

mercurial