~ubuntu-branches/ubuntu/wily/bandit/wily-proposed

« back to all changes in this revision

Viewing changes to bandit/core/utils.py

  • Committer: Package Import Robot
  • Author(s): Dave Walker (Daviey)
  • Date: 2015-07-22 09:01:39 UTC
  • Revision ID: package-import@ubuntu.com-20150722090139-fl0nluy0x8m9ctx4
Tags: upstream-0.12.0
ImportĀ upstreamĀ versionĀ 0.12.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding:utf-8 -*-
 
2
#
 
3
# Copyright 2014 Hewlett-Packard Development Company, L.P.
 
4
#
 
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 
6
# not use this file except in compliance with the License. You may obtain
 
7
# a copy of the License at
 
8
#
 
9
#      http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
11
# Unless required by applicable law or agreed to in writing, software
 
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
14
# License for the specific language governing permissions and limitations
 
15
# under the License.
 
16
 
 
17
import _ast
 
18
import ast
 
19
import os.path
 
20
import symtable
 
21
 
 
22
 
 
23
"""Various helper functions."""
 
24
 
 
25
 
 
26
def ast_args_to_str(args):
 
27
        res = ('\n\tArgument/s:\n\t\t%s' %
 
28
               '\n\t\t'.join([ast.dump(arg) for arg in args]))
 
29
        return res
 
30
 
 
31
 
 
32
def _get_attr_qual_name(node, aliases):
 
33
    '''Get a the full name for the attribute node.
 
34
 
 
35
    This will resolve a pseudo-qualified name for the attribute
 
36
    rooted at node as long as all the deeper nodes are Names or
 
37
    Attributes. This will give you how the code referenced the name but
 
38
    will not tell you what the name actually refers to. If we
 
39
    encounter a node without a static name we punt with an
 
40
    empty string. If this encounters something more comples, such as
 
41
    foo.mylist[0](a,b) we just return empty string.
 
42
 
 
43
    :param node: AST Name or Attribute node
 
44
    :param aliases: Import aliases dictionary
 
45
    :returns: Qualified name refered to by the attribute or name.
 
46
    '''
 
47
    if type(node) == _ast.Name:
 
48
        if node.id in aliases:
 
49
            return aliases[node.id]
 
50
        return node.id
 
51
    elif type(node) == _ast.Attribute:
 
52
        name = '%s.%s' % (_get_attr_qual_name(node.value, aliases), node.attr)
 
53
        if name in aliases:
 
54
            return aliases[name]
 
55
        return name
 
56
    else:
 
57
        return ""
 
58
 
 
59
 
 
60
def get_call_name(node, aliases):
 
61
    if type(node.func) == _ast.Name:
 
62
        if deepgetattr(node, 'func.id') in aliases:
 
63
            return aliases[deepgetattr(node, 'func.id')]
 
64
        return(deepgetattr(node, 'func.id'))
 
65
    elif type(node.func) == _ast.Attribute:
 
66
        return _get_attr_qual_name(node.func, aliases)
 
67
    else:
 
68
        return ""
 
69
 
 
70
 
 
71
def get_func_name(node):
 
72
    return node.name  # TODO(tkelsey): get that qualname using enclosing scope
 
73
 
 
74
 
 
75
def get_qual_attr(node, aliases):
 
76
    prefix = ""
 
77
    if type(node) == _ast.Attribute:
 
78
        try:
 
79
            val = deepgetattr(node, 'value.id')
 
80
            if val in aliases:
 
81
                prefix = aliases[val]
 
82
            else:
 
83
                prefix = deepgetattr(node, 'value.id')
 
84
        except Exception:
 
85
            # NOTE(tkelsey): degrade gracefully when we cant get the fully
 
86
            # qualified name for an attr, just return its base name.
 
87
            pass
 
88
 
 
89
        return("%s.%s" % (prefix, node.attr))
 
90
    else:
 
91
        return ""  # TODO(tkelsey): process other node types
 
92
 
 
93
 
 
94
def deepgetattr(obj, attr):
 
95
    """Recurses through an attribute chain to get the ultimate value."""
 
96
    for key in attr.split('.'):
 
97
        obj = getattr(obj, key)
 
98
    return obj
 
99
 
 
100
 
 
101
def describe_symbol(sym):
 
102
    assert type(sym) == symtable.Symbol
 
103
    print("Symbol:", sym.get_name())
 
104
 
 
105
    for prop in [
 
106
            'referenced', 'imported', 'parameter',
 
107
            'global', 'declared_global', 'local',
 
108
            'free', 'assigned', 'namespace']:
 
109
        if getattr(sym, 'is_' + prop)():
 
110
            print('    is', prop)
 
111
 
 
112
 
 
113
def lines_with_context(line_no, line_range, max_lines, file_len):
 
114
    '''Get affected lines, plus context
 
115
 
 
116
    This function takes a list of line numbers, adds one line
 
117
    before the specified range, and two lines after, to provide
 
118
    a bit more context. It then limits the number of lines to
 
119
    the specified max_lines value.
 
120
    :param line_no: The line of interest (trigger line)
 
121
    :param line_range: The lines that make up the whole statement
 
122
    :param max_lines: The maximum number of lines to output
 
123
    :return l_range: A list of line numbers to output
 
124
    '''
 
125
 
 
126
    # Catch a 0 or negative max lines, don't display any code
 
127
    if max_lines == 0:
 
128
        return []
 
129
 
 
130
    l_range = sorted(line_range)
 
131
 
 
132
    # add one line before before and after, to make sure we don't miss
 
133
    # any context.
 
134
    l_range.append(l_range[-1] + 1)
 
135
    l_range.append(l_range[0] - 1)
 
136
 
 
137
    l_range = sorted(l_range)
 
138
 
 
139
    if max_lines < 0:
 
140
        return l_range
 
141
 
 
142
    # limit scope to max_lines
 
143
    if len(l_range) > max_lines:
 
144
        # figure out a sane distribution of scope (extra lines after)
 
145
        after = (max_lines - 1) / 2
 
146
        before = max_lines - (after + 1)
 
147
        target = l_range.index(line_no)
 
148
 
 
149
        # skew things if the code is at the start or end of the statement
 
150
 
 
151
        if before > target:
 
152
            extra = before - target
 
153
            before = target
 
154
            after += extra
 
155
 
 
156
        gap = file_len - (target + 1)
 
157
        if gap < after:
 
158
            extra = after - gap
 
159
            after = gap
 
160
            before += extra
 
161
 
 
162
        # find start
 
163
        if before >= target:
 
164
            start = 0
 
165
        else:
 
166
            start = target - before
 
167
 
 
168
        # find end
 
169
        if target + after > len(l_range) - 1:
 
170
            end = len(l_range) - 1
 
171
        else:
 
172
            end = target + after
 
173
 
 
174
        # slice line array
 
175
        l_range = l_range[start:end + 1]
 
176
 
 
177
    return l_range
 
178
 
 
179
 
 
180
class InvalidModulePath(Exception):
 
181
    pass
 
182
 
 
183
 
 
184
def get_module_qualname_from_path(path):
 
185
    '''Get the module's qualified name by analysis of the path.
 
186
 
 
187
    Resolve the absolute pathname and eliminate symlinks. This could result in
 
188
    an incorrect name if symlinks are used to restructure the python lib
 
189
    directory.
 
190
 
 
191
    Starting from the right-most directory component look for __init__.py in
 
192
    the directory component. If it exists then the directory name is part of
 
193
    the module name. Move left to the subsequent directory components until a
 
194
    directory is found without __init__.py.
 
195
 
 
196
    :param: Path to module file. Relative paths will be resolved relative to
 
197
            current working directory.
 
198
    :return: fully qualified module name
 
199
    '''
 
200
 
 
201
    (head, tail) = os.path.split(path)
 
202
    if head == '' or tail == '':
 
203
        raise InvalidModulePath('Invalid python file path: "%s"'
 
204
                                ' Missing path or file name' % (path))
 
205
 
 
206
    qname = [os.path.splitext(tail)[0]]
 
207
    while head != '/':
 
208
        if os.path.isfile(os.path.join(head, '__init__.py')):
 
209
            (head, tail) = os.path.split(head)
 
210
            qname.insert(0, tail)
 
211
        else:
 
212
            break
 
213
 
 
214
    qualname = '.'.join(qname)
 
215
    return qualname
 
216
 
 
217
 
 
218
def namespace_path_join(base, name):
 
219
    '''Extend the current namespace path with an additional name
 
220
 
 
221
    Take a namespace path (i.e., package.module.class) and extends it
 
222
    with an additional name (i.e., package.module.class.subclass).
 
223
    This is similar to how os.path.join works.
 
224
 
 
225
    :param base: (String) The base namespace path.
 
226
    :param name: (String) The new name to append to the base path.
 
227
    :returns: (String) A new namespace path resulting from combination of
 
228
              base and name.
 
229
    '''
 
230
    return '%s.%s' % (base, name)
 
231
 
 
232
 
 
233
def namespace_path_split(path):
 
234
    '''Split the namespace path into a pair (head, tail).
 
235
 
 
236
    Tail will be the last namespace path component and head will
 
237
    be everything leading up to that in the path. This is similar to
 
238
    os.path.split.
 
239
 
 
240
    :param path: (String) A namespace path.
 
241
    :returns: (String, String) A tuple where the first component is the base
 
242
              path and the second is the last path component.
 
243
    '''
 
244
    return tuple(path.rsplit('.', 1))
 
245
 
 
246
 
 
247
def safe_unicode(obj, *args):
 
248
    '''return the unicode representation of obj.'''
 
249
    try:
 
250
        return unicode(obj, *args)
 
251
    except UnicodeDecodeError:
 
252
        # obj is byte string
 
253
        ascii_text = str(obj).encode('string_escape')
 
254
        return unicode(ascii_text)
 
255
 
 
256
 
 
257
def safe_str(obj):
 
258
    '''return the byte string representation of obj.'''
 
259
    try:
 
260
        return str(obj)
 
261
    except UnicodeEncodeError:
 
262
        # obj is unicode
 
263
        return unicode(obj).encode('unicode_escape')