/usr/share/pyshared/pychecker2/FormatStringChecks.py is in pychecker 0.8.19-4.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | from pychecker2.Check import Check
from pychecker2.util import BaseVisitor
from pychecker2.Warning import Warning
from compiler import ast, walk
from types import *
import re
class UnknownError(Exception): pass
def _compute_node(node, recurse):
if isinstance(node, ast.Add):
return recurse(node.left) + recurse(node.right)
elif isinstance(node, ast.Mul):
return recurse(node.left) * recurse(node.right)
elif isinstance(node, ast.Sub):
return recurse(node.left) - recurse(node.right)
elif isinstance(node, ast.Div):
return recurse(node.left) / recurse(node.right)
raise UnknownError
def _compute_constant(node):
"Compute a simple forms of constant strings from an expression node"
if isinstance(node, ast.Const):
return node.value
return _compute_node(node, _compute_constant)
def _compute_tuple_size(node):
"Compute the length of simple forms of tuples from an expression node"
if isinstance(node, ast.Tuple):
return (None,) * len(node.nodes)
if isinstance(node, ast.Const):
return node.value
if isinstance(node, ast.Backquote):
return ''
return _compute_node(node, _compute_tuple_size)
# for details: http://www.python.org/doc/current/lib/typesseq-strings.html
_MOD_AND_TYPE = '([hlL])?([diouxXeEfFgGcrs%])'
_TUP_FORMAT_REGEX = re.compile('%(())?[ #+-]*'
'([0-9]*|[*])(|[.](|[*]|[0-9]*))' +
_MOD_AND_TYPE)
_DICT_FORMAT_REGEX = re.compile('%([(]([a-zA-Z_]+)[)])?[ #+-]*'
'([0-9]*)(|[.](|[0-9]*))' + _MOD_AND_TYPE)
class FormatError(Exception):
def __init__(self, position):
Exception.__init__(self)
self.position = position
def _check_format(s):
pos = 0
specs = []
while 1:
pos = s.find('%', pos)
if pos < 0:
return specs
match = _TUP_FORMAT_REGEX.search(s, pos)
if not match or match.start(0) != pos:
match = _DICT_FORMAT_REGEX.search(s, pos)
if not match or match.start(0) != pos:
raise FormatError(pos)
if match.group(7) != '%': # ignore "%%"
specs.append( (match.group(2), match.group(3), match.group(5),
match.group(6)) )
pos = match.end(0)
return specs
class _GetMod(BaseVisitor):
def __init__(self):
self.mods = []
def visitMod(self, node):
self.mods.append(node)
self.visitChildren(node)
# don't descend into other scopes
def visitFunction(self, node): pass
visitClass = visitFunction
visitLambda = visitFunction
def get_mods(node):
try:
return walk(node.code, _GetMod()).mods
except AttributeError:
return walk(node.node, _GetMod()).mods
_BAD_FORMAT_MAX = 10
def _bad_format_str(s, pos):
result = s[pos : pos + _BAD_FORMAT_MAX]
return result + (len(s) > pos + _BAD_FORMAT_MAX and "..." or "")
class FormatStringCheck(Check):
"Look for warnings in format strings"
badFormat = \
Warning('Report illegal format specifications in format strings',
'Bad format specifier at position %d (%s)')
uselessModifier = \
Warning('Report unused modifiers for format strings (l, h, L)',
'Modifier %s is not necessary')
mixedFormat = \
Warning('Report format strings which use both positional '
'and named formats',
'Cannot mix positional and named formats (%%%s)')
formatCount = \
Warning('Report positional format string with the wrong '
'number of arguments',
'Wrong number of arguments supplied for format: '
'%d given %d required')
unknownFormatName = \
Warning('Report unknown names if locals() or globals() '
'are used for format strings',
'The name "%s" is not defined in %s')
badConstant = \
Warning('Report bad constant expressions for format strings',
'Error computing constant: %s')
def check(self, file, unused_checker):
if not file.parseTree:
return
for scope in file.scopes.values():
for mod in get_mods(scope.node):
formats = []
try:
s = _compute_constant(mod.left)
formats = _check_format(s)
except FormatError, detail:
file.warning(mod, self.badFormat, detail.position,
_bad_format_str(s, detail.position))
except TypeError, detail:
file.warning(mod, self.badConstant, str(detail))
except UnknownError:
pass
if not formats:
continue
count = len(formats)
for name, width, precision, lmodifier in formats:
if lmodifier:
file.warning(mod, self.uselessModifier, lmodifier)
if width == '*':
count += 1
if precision == '*':
count += 1
names = [f[0] for f in formats if f[0]]
if len(names) == 0: # tuple
try:
t = _compute_tuple_size(mod.right)
n = 1
if type(t) == TupleType:
n = len(t)
if n != count:
file.warning(mod, self.formatCount, n, count)
except UnknownError:
pass
except TypeError, detail:
file.warning(mod, self.badConstant, str(detail))
elif len(names) == len(formats): # dictionary
defines = None
if isinstance(mod.right, ast.CallFunc) and \
isinstance(mod.right.node, ast.Name):
if mod.right.node.name in ['locals', 'vars']:
defines = scope.defs
uses = scope.uses
if mod.right.node.name == 'globals':
defines = file.root_scope.defs
uses = file.root_scope.uses
if defines is not None:
for n in names:
if not defines.has_key(n):
file.warning(mod, self.unknownFormatName,
n, mod.right.node.name)
else:
uses[n] = uses.get(n, mod)
else:
file.warning(mod, self.mixedFormat, "(%s)" % names[0])
|