pychecker-0.8.19/0000775000076400007640000000000011512046402013143 5ustar thomasthomaspychecker-0.8.19/TODO0000644000076400007640000002270011512036115013632 0ustar thomasthomasGoals for next minor release: ----------------------------- * Run pychecker on itself and clean up * Add warning for duplicated elif conditions * Check for "if s.find(str):" s/b "if s.find(str) >= 0:" where s is a string * Add warning for: x % x is always 0 * Add warning when using listcomp variable (x) outside of [... for x in ...] * Add warning for import overriding builtin (from os import *) * Add warning if defining a module name that's also in the stdlib * Add decorator so you can do: @pychecker(maxlocals=42) def myfunc(): * Support function attributes in addition to __pychecker__ * Add warning when iterating over a dict (keys, values, items) and mutating it * Add warning for __special_ or __special or _special__ etc. * Add warning about redefining a function/method with different signatures * Add warning when base class has similar name and same signature: class Base: def foobar(): class Sub(Base): def foobaz(): # damn, I wanted to override foobar * If unknown name, suggest similar names * Check for return from finally (disables exception, default off?) * Add another boolean warning for: if bool(xxx): * Need way to configure broken (and non-broken) versions of evil C extensions * Add estimated difficulty to research/fix error * Shouldn't warn about using return value for generators * Suppress argument (self) name warnings if func has no impl? * Add warning for a variable that is constant ie, initialized and used, but never changed * Add warning for catching AttributeError when there is no attr access ZeroDivisionError w/no division? KeyError UnboundLocalError IndexError IndentationError ImportError * Add warning for a import b & b import a (import loops) * Check that classes with __slots__ don't get/set any other attributes * Expectations of __methods__, constness (don't modify in __nonzero__) * Add warning if dict has duplicate keys when creating from literals * __new__ should use cls, not self??? * If arg has default value of None, check that arg is assigned some value * check if returning self from __str__() * check for str1 in str2 == otherValue * Fix dependancy problem w/checkReturnValues & checkImplicitReturns * check for use of self.attr before setting self.attr in __init__ method * Handle sys.exit() as non-returnable condition * Warn about non-escaping backslashes??? * vars() should use locals, like locals(), hmmm, it should already ... Goals for next major release (0.9): ----------------------------------- * Improve handling when importing packages (ie, don't import modules) - Make a base class to handle stack operations - Override base class with classes to handle module scope, functions, etc. - Process module and create Module/Class/Function objects w/o dir(), etc. - Finish tests: 44 * Add a config option to ignore certain cantankerous modules * Add warning for global statement inside a conditional * Fix spurious warnings for unreachable code checks * Fix most of the FIXMEs * Add check for import module (from within package p-should be import p.module) - don't use relative imports, should always use absolute paths * If there is a warnings for unused varargs parameter (varArgumentsUsed), why not for kwargs ? Longer Term goals: ------------------ * Add check for magic #s/consts (need dict of consts, count, max count, consts to ignore) * Add check for unsupported operand type(s) for * + / etc * Store types of globals, class attributes, function & method return values * Output stats about pychecker's job, good, bad, otherwise... annotate code for problem areas, well checked areas, etc. * Add original line warnings for those that refer to another place (e.g., overridden method doesn't match signature) * Add capability to check features from a specified version of python which is different from runtime interpreter * Security checks for known issues. Adrian Likins @redhat recommends looking at http://www.securesw.com/rats/ * Make check for 'self' a list instead of a string * Check function return values - partially complete * Add check that private functions are used in module/class * Add check for using object members outside of class * Add portability checks for win32/unix * Add check for except: pass * Spell check doc strings # need to read source code for these: * Add warning for unnecessary global stmts * Add check for implicit string concatentation * Add check for creating tuple when don't want a tuple, like: 1, or not creating tuple when want one, like: (1) * Warn when using () with statements (assert, print, del, if, while) * Add check for <>, should use != * Find lines that end w/semi-colons (;) * inconsistent camel-case Fixing testsuite: ----------------- Tested on 2010-12-18, after 2.7 fixes: On Fedora 14, system Python 2.7: 7 TESTS FAILED: test13 test34 test44 test70 test71 test77 test85 On Fedora 14, source Python 2.6: 6 TESTS FAILED: test13 test34 test44 test70 test71 test77 On Fedora 14, source Python 2.5: 6 TESTS FAILED: test13 test34 test44 test70 test71 test77 On Fedora 14, source Python 2.4: 4 TESTS FAILED: test13 test44 test70 test77 On Fedora 14, source Python 2.3: 8 TESTS FAILED: test103 test13 test44 test68 test70 test77 test78 test85 On Fedora 14, source Python 2.2: 11 TESTS FAILED: test101 test103 test13 test1 test34 test3 test44 test58 test77 test78 test85 Tested on 2009-06-27: On Fedora 9, system Python 2.5: TESTS FAILED: test103 test13 test22 test44 test48 test53 test70 test71 test77 test78 test87 On Fedora 9, Python 2.4 built by hand: TESTS FAILED: test13 test44 test70 test78 test88 On Fedora 9, Python 2.3 built by hand: TESTS FAILED: test103 test13 test44 test68 test70 test77 test78 test85 On Fedora 9, Python 2.2 built by hand: all tests fail due to a bug in this_mod.__filename__ test76: when adding the patch on 2011-01-07 to process nested functions in order, line numbers for warnings changed from 6 to 7. Not clear which should be correct; it seems like a should be 6 and d should be 7 ? test103: a generator statement run twice should not generate a warning, but does in python 2.3 and 2.5, but not 2.4 test22: two constant warnings went away since python 2.5 test26: fails in 2.6 test34: fails in 2.5, 2.6 locale.py added a currency method which uses digits as a local var; this shadows global defined in the test. Not sure if this is a valid warning that should be flagged (due to from ... import *) I would argue no, since locale.py being good or bad shouldn't change based on where it's imported from. Formatter was added to string.py, without docstring, so looks like a valid new warning. test36: fails in 2.6 test44: test44.py imports * from import44, which has a class Ccc named the same but whose init takes an extra argument. test48: line 57 (the single character '5') no longer triggers statement has no effect warning since python 2.5 test53: the operator ~~ stopped generating a warning since python 2.5 test59: fails in 2.6 test68: only fails in python 2.3 test70: 5 conditional warnings went away since python 2.5; but test fails in all test71: unreachable code moved from test a to test c; since 2.5 In the pre-2.5 warnings, the line numbers don't point at the unreachable code, but the statement before. In 2.5 and later, the right blocks don't even get found; it looks like this is because the implicit return None is gone from the byte code; compare: 2.6, py-dis test_input.test71.a ... 9 25 LOAD_FAST 0 (x) 28 PRINT_ITEM 29 PRINT_NEWLINE 2.4, py-dis test_input.test71.a 9 25 LOAD_FAST 0 (x) 28 PRINT_ITEM 29 PRINT_NEWLINE 30 LOAD_CONST 0 (None) 33 RETURN_VALUE So the implied return None is not found as a terminal Aditionally, b disassembles differently too: 2.6, py-dis test_intput.test71.b ... 13 13 LOAD_FAST 0 (x) 16 RETURN_VALUE >> 17 POP_TOP 2.4, py-dis test_intput.test71.b 13 13 LOAD_FAST 0 (x) 16 RETURN_VALUE 17 JUMP_FORWARD 1 (to 21) >> 20 POP_TOP index 9 is different test77: doesn't get error formatting exception value; since 2.5 gets linenumber wrong (698 and not 704) in 2.3 test78: generated output has, at offset 0x35, [?1034h.Warnings; expected output does not have this; since 2.3 test85: 9: Using a conditional statement with a constant value (true) fails in 2.3, works in 2.4/2.5/2.6 test87: the expected output is able to clearly identify the line number of a broken up statement; the current output is not and blames the first line of the broken up statement; since python 2.5 test88: DeprecationWarning for whrandom pointing to the prefix path instead of expected /usr/lib Test questions -------------- test3: why does 'dict shadows builtin' trigger in 2.3 but not 2.4/2.5 ? Bugs ---- figure out why running pychecker on test/input/test_dict.py fails in 2.6 while it doesn't when you copy it to ./test.py and run it on that (Probably because there is also test/__init__.py and it is trying to import that instead ?) pychecker-0.8.19/test/0000775000076400007640000000000011512046402014122 5ustar thomasthomaspychecker-0.8.19/test/test_module.py0000664000076400007640000000070611043316754017034 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to modules. ''' import unittest import common class SameModuleNameTestCase(common.TestCase): ''' Test that modules with the same name do not shadow eachother. ''' def test_getmodule(self): self.checkMultiple('test_getmodule', [ 'getmodule/A/C.py', 'getmodule/B/C.py', ]) if __name__ == '__main__': unittest.main() pychecker-0.8.19/test/test_stdlib.py0000644000076400007640000000117211221437500017013 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to the -q/--stdlib option. ''' import unittest import common class ZopeTestCase(common.TestCase): ''' test that -q properly ignores errors for modules in the arch-specific directory. See http://sourceforge.net/tracker/index.php?func=detail&aid=1564614&group_id=24686&atid=382217 ''' def test_zope_interface(self): if not common.canImport('zope.interface'): self.skip = True # FIXME: interpret this return self.check('test_zope_interface', '-q') if __name__ == '__main__': unittest.main() pychecker-0.8.19/test/test_global.py0000664000076400007640000000063311506576421017011 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to dicts. ''' import unittest import common class KeysTestCase(common.TestCase): ''' test that globals are only warned once without -g. ''' def test_global(self): self.check('test_global') def test_global_g(self): self.check('test_global', args='-g') if __name__ == '__main__': unittest.main() pychecker-0.8.19/test/test_dict.py0000664000076400007640000000052711221233025016456 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to dicts. ''' import unittest import common class KeysTestCase(common.TestCase): ''' test that modules with the same name do not shadow eachother. ''' def test_dict(self): self.check('test_dict') if __name__ == '__main__': unittest.main() pychecker-0.8.19/test/test_pychecker_CodeChecks.py0000644000076400007640000000170611511156326021573 0ustar thomasthomas# -*- Mode: Python; test-case-name: test.test_pychecker_CodeChecks -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to pychecker.CodeChecks ''' import unittest import common from pychecker import CodeChecks class DispatchTestCase(common.TestCase): ''' Test that we have all opcodes in the DISPATCH array. ''' def testDispatch(self): if not common.canImport('dis'): # FIXME: no skip support return res = [] import dis for key, value in dis.opmap.items(): if not CodeChecks.DISPATCH[value]: res.append("opcode %d: %s not in CodeChecks.DISPATCH" % ( value, key)) self.failIf(res, "\n".join(res)) class OpcodesTestCase(common.TestCase): def test_STORE_SLICE_PLUS_0(self): self.check('test_STORE_SLICE_PLUS_0') def test_DUP_TOPX(self): self.check('test_DUP_TOPX') if __name__ == '__main__': unittest.main() pychecker-0.8.19/test/test_suppressions.py0000664000076400007640000000061311511655030020312 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to suppressions. ''' import unittest import common class NestedTestCase(common.TestCase): ''' Test that suppressions inside nested code stay inside their context. ''' def test_getmodule(self): self.check('test_nestedsuppression', '--objattrs') if __name__ == '__main__': unittest.main() pychecker-0.8.19/test/test_pychecker_function.py0000644000076400007640000000254311511444550021424 0ustar thomasthomas# -*- Mode: Python; test-case-name: test.test_pychecker_function -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to pychecker.function ''' import unittest import common from pychecker import function, utils class GeneratorTestCase(common.TestCase): ''' Test that a generator function works as expected. ''' def testGenerator(self): def returner(): return (str(x) for x in range(10)) # get the generator code object genCode = returner.func_code.co_consts[1] # FIXME: this is what co_varnames looks like, but I don't understand why # possible clue in Python, Lib/compiler/ast.py, class GenExpr if utils.pythonVersion() < utils.PYTHON_2_5: self.assertEquals(genCode.co_varnames, ('[outmost-iterable]', 'x')) else: self.assertEquals(genCode.co_varnames, ('.0', 'x')) # wrap it into a Funtion so we can look at it f = function.Function( function.FakeFunction(genCode.co_name, genCode)) self.failIf(f.isMethod) self.assertEquals(f.minArgs, 1) self.assertEquals(f.maxArgs, 1) if utils.pythonVersion() < utils.PYTHON_2_5: self.assertEquals(f.arguments(), ('[outmost-iterable]', )) else: self.assertEquals(f.arguments(), ('.0', )) if __name__ == '__main__': unittest.main() pychecker-0.8.19/test/test_slice.py0000664000076400007640000000046611504140636016645 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 ''' Tests related to slices. ''' import unittest import common class SliceTestCase:#(common.TestCase): ''' test that slices work. ''' def test_slice(self): self.check('test_slice') if __name__ == '__main__': unittest.main() pychecker-0.8.19/setup.py0000644000076400007640000002724511512044217014667 0ustar thomasthomas#!/usr/bin/env python # -*- coding: iso-8859-1 -*- """ Python distutils setup script for pychecker. This code was originally contributed by Nicolas Chauvat, and has been rewritten to generalize it so it works properly with --prefix and other distutils options. This install script needs to customize two distutils behaviors: it needs to install the documentation to the configured package install directory, and it needs to create a pychecker (or pychecker.bat) script containing the correct path to the installed checker.py module and the Python interpreter. Nicolas' original attempt at this worked fine in the normal case (i.e. a root user install). However, because it assumed that the package directory was always within sysconfig.get_python_lib(), it broke when users wanted to specify a prefix or a home directory install to, etc. After some research, I've decided that the best way to make this work is to customize (override) some of the distutils action classes. This way, we get access to the distutils configuration and can "do the right thing" when options are specified. @author: Kenneth J. Pronovici , after Nicolas Chauvat. """ ################### # Imported modules ################### import sys import os from distutils import core from distutils.util import execute from distutils.command.bdist import bdist from distutils.command.bdist_dumb import bdist_dumb from distutils.command.bdist_rpm import bdist_rpm from distutils.command.bdist_wininst import bdist_wininst from distutils.command.install_data import install_data from distutils.command.install_scripts import install_scripts from distutils.command.build_scripts import build_scripts ############################### # Overridden distutils actions ############################### using_bdist = 0 # assume we're not using bdist class my_bdist(bdist): """Customized action which flags that a binary distribution is being produced.""" def run(self): global using_bdist using_bdist = 1 bdist.run(self) class my_bdist_dumb(bdist_dumb): """Customized action which flags that a binary distribution is being produced.""" def run(self): global using_bdist using_bdist = 1 bdist_dumb.run(self) class my_bdist_rpm(bdist_rpm): """Customized action which flags that a binary distribution is being produced.""" def run(self): global using_bdist using_bdist = 1 bdist_rpm.run(self) class my_bdist_wininst(bdist_wininst): """Customized action which flags that a binary distribution is being produced.""" def run(self): global using_bdist using_bdist = 1 bdist_wininst.run(self) class my_install_data(install_data): """ Customized install_data distutils action. This customized action forces all data files to all be installed relative to the normal library install directory (within site-packages) rather than in the standard location. This action does not obey any --install-data flag that the user specifies. Distutils apparently does not provide a way to tell whether the install data directory on the Distribution class is "standard" or has already been overrwritten. This means that we don't have a way to say "change this only if it hasn't been changed already". All we can do is override the location all of the time. Note: If you want your files to go in the "pychecker" package directory, make sure that you specify "pychecker" as the prefix in the setup target. """ def finalize_options(self): self.set_undefined_options('install', ('install_lib', 'install_dir')) install_data.finalize_options(self) # invoke "standard" action class my_install_scripts(install_scripts): """ Customized install_scripts distutils action. This customized action fills in the pychecker script (either Windows or shell style, depending on platform) using the proper path to the checker.py file and Python interpreter. This all is done through the execute() method, so that the action obeys the --dry-run flag, etc. The pychecker script must be built here, in the install action, because the proper path to checker.py cannot be known before this point. If this action is invoked directly from the command-line, it won't have access to certain configuration, such as the list of scripts. Instead of trying to work around all that (it's a pain), I've just disallowed the action if it doesn't have the information it needs. We have some special behavior around binary distributions. If we're building a binary distibution, then we don't try to figure out where checker.py will be installed "for real". We just assume it will be put in the standard site-packages location. """ def run(self): global using_bdist if not using_bdist: install_lib = self.distribution.get_command_obj("install").install_lib else: install_lib = get_site_packages_path() scripts = self.distribution.get_command_obj("build_scripts").scripts if install_lib is None or scripts is None or self.build_dir is None: print "note: install_scripts can only be invoked by install" else: script_path = get_script_path(self.build_dir) if script_path in scripts: package_path = os.path.join(install_lib, "pychecker") # if a staging root is used, we don't want that path to end up # in the install_lib path root = self.distribution.get_command_obj("install").root if root: package_path = package_path[len(root):] self.execute(func=create_script, args=[script_path, package_path], msg="filling in script %s" % script_path) install_scripts.run(self) # invoke "standard" action class my_build_scripts(build_scripts): """ Customized build_scripts distutils action. This action looks through the configured scripts list, and if "pychecker" is in the list, replaces that entry with the real name of the script to be created within the build directory (including the .bat extension if needed). Then, it creates an empty script file at that path to make distutils happy. This is all done through the execute() method, so that the action obeys the --dry-run flag, etc. It might be surprising that we don't fill in the pychecker script here. This is because in order to fill in the script, we need to know the installed location of checker.py. This information isn't available until the install action is executed. This action completely ignores any scripts other than "pychecker" which are listed in the setup configuration, and it only does anything if "pychecker" is listed in the first place. This way, new scripts with constant contents (if any) can be added to the setup configuration without writing any new code. It also helps OS package maintainers (like for the Debian package) because they can still avoid installing scripts just by commenting out the scripts line in configuration, just like usual. """ def run(self): if self.scripts is not None and "pychecker" in self.scripts: script_path = get_script_path(self.build_dir) self.scripts.remove("pychecker") self.scripts.append(script_path) self.mkpath(self.build_dir) self.execute(func=open, args=[script_path, "w"], msg="creating empty script %s" % script_path) build_scripts.run(self) # invoke "standard" action def get_script_path(build_dir): """ Returns the platform-specific path to the pychecker script within the build dir. """ if sys.platform == "win32": return os.path.join(build_dir, "pychecker.bat") else: return os.path.join(build_dir, "pychecker") def get_site_packages_path(): """ Returns the platform-specific location of the site-packages directory. This directory is usually something like /usr/share/python2.3/site-packages on UNIX platforms and /Python23/Lib/site-packages on Windows platforms. """ if sys.platform.lower().startswith('win'): return os.path.join(sys.prefix, "Lib", "site-packages") else: return os.path.join(sys.prefix, "lib", "python%s" % sys.version[:3], "site-packages") def create_script(script_path, package_path): """ Creates the pychecker script at the indicated path. The pychecker script will be created to point to checker.py in the package directory, using the Python executable specified in sys.executable. If the platform is Windows, a batch-style script will be created. Otherwise, a Bourne-shell script will be created. Note that we don't worry about what permissions mode the created file will have because the distutils install process takes care of that for us. @param script_path: Path to the script to be created @param package_path: Path to the package that checker.py can be found within @raise Exception: If script cannot be created on disk. """ try: checker_path = os.path.join(package_path, "checker.py") if sys.platform == "win32": script_str = "%s %s %%*\n" % (sys.executable, checker_path) else: script_str = '#! /bin/sh\n\n%s %s "$@"\n' % (sys.executable, checker_path) open(script_path, "w").write(script_str) except Exception, e: print "ERROR: Unable to create %s: %s" % (script_path, e) raise e ###################### # Setup configuration ###################### CUSTOMIZED_ACTIONS = { 'build_scripts' : my_build_scripts, 'install_scripts': my_install_scripts, 'install_data' : my_install_data, 'bdist' : my_bdist, 'bdist_dumb' : my_bdist_dumb, 'bdist_rpm' : my_bdist_rpm, 'bdist_wininst' : my_bdist_wininst, } DATA_FILES = [ 'COPYRIGHT', 'README', 'VERSION', 'ChangeLog', 'NEWS', 'KNOWN_BUGS', 'MAINTAINERS', 'TODO', 'pychecker.doap', ] LONG_DESCRIPTION = """ PyChecker is a tool for finding bugs in python source code. It finds problems that are typically caught by a compiler for less dynamic languages, like C and C++. Because of the dynamic nature of Python, some warnings may be incorrect; however, spurious warnings should be fairly infrequent. """ kw = { 'name' : "pychecker", 'version' : "0.8.19", 'license' : "BSD-like", 'description' : "Python source code checking tool", 'author' : "Neal Norwitz", 'author_email' : "nnorwitz@gmail.com", 'url' : "http://pychecker.sourceforge.net/", 'packages' : [ 'pychecker', ], 'scripts' : [ "pychecker" ], # note: will be replaced by customized action 'data_files' : [ ( "pychecker", DATA_FILES, ) ], 'long_description' : LONG_DESCRIPTION, 'cmdclass' : CUSTOMIZED_ACTIONS, } if hasattr(core, 'setup_keywords') and 'classifiers' in core.setup_keywords: kw['classifiers'] = [ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Software Development :: Debuggers', 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', ] if __name__ == '__main__' : core.setup(**kw) pychecker-0.8.19/KNOWN_BUGS0000664000076400007640000000442507420334046014616 0ustar thomasthomas Crash when running on code that does: from wxPython.wx import * The problem is in wxGTK-2.2.6 and earlier versions. Below is a patch to wxGTK 2.2.6 that fixes the problem. This problem has been reported: http://sourceforge.net/tracker/?func=detail&atid=109863&aid=417923&group_id=9863 ----------- *** src/gtk/app.cpp.orig Sat Apr 21 17:12:35 2001 --- src/gtk/app.cpp Sat Apr 21 17:14:46 2001 *************** *** 582,597 **** --- 582,600 ---- // GL: I'm annoyed ... I don't know where to put this and I don't want to // create a module for that as it's part of the core. #if wxUSE_THREADS delete wxPendingEvents; + wxPendingEvents = 0; delete wxPendingEventsLocker; + wxPendingEventsLocker = 0; #endif wxSystemSettings::Done(); delete[] wxBuffer; + wxBuffer = 0; wxClassInfo::CleanUpClasses(); // check for memory leaks #if (defined(__WXDEBUG__) && wxUSE_MEMORY_TRACING) || wxUSE_DEBUG_CONTEXT *** src/common/resource.cpp.orig Sat Apr 21 17:10:23 2001 --- src/common/resource.cpp Sat Apr 21 17:06:42 2001 *************** *** 110,121 **** --- 110,125 ---- } void wxCleanUpResourceSystem() { delete wxDefaultResourceTable; + wxDefaultResourceTable = 0; if (wxResourceBuffer) + { delete[] wxResourceBuffer; + wxResourceBuffer = 0; + } } void wxLogWarning(char *msg) { wxMessageBox(msg, _("Warning"), wxOK); --------------------------------------------------------------------- exec statements are not checked. Therefore, variables and modules used in exec statements may cause spurious warnings. To avoid the warnings, consider rewriting your code to use eval() or getattr(). For example, consider rewriting this code: exec 'var = object.' + member print var to: var = eval('object.' + member) print var or: var = getattr(object, member) print var --------------------------------------------------------------------- The following code generates a spurious warning: from XXX import * class SameName: def __init__(self): pass And in the file XXX: class SameName: def __init__(self, c): pass class Anyname: def __init__(self): self.xxx = SameName(1) --------------------------------------------------------------------- pychecker-0.8.19/pychecker/0000775000076400007640000000000011512046402015120 5ustar thomasthomaspychecker-0.8.19/pychecker/pcmodules.py0000644000076400007640000005323611512036115017474 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 """ Track loaded PyCheckerModules together with the directory they were loaded from. This allows us to differentiate between loaded modules with the same name but from different paths, in a way that sys.modules doesn't do. """ import re import sys import imp import types import string from pychecker import utils, function, Config, OP # Constants _DEFAULT_MODULE_TOKENS = ('__builtins__', '__doc__', '__file__', '__name__', '__path__') _DEFAULT_CLASS_TOKENS = ('__doc__', '__name__', '__module__') # When using introspection on objects from some C extension modules, # the interpreter will crash. Since pychecker exercises these bugs we # need to blacklist the objects and ignore them. For more info on how # to determine what object is causing the crash, search for this # comment below (ie, it is also several hundred lines down): # # README if interpreter is crashing: # FIXME: the values should indicate the versions of these modules # that are broken. We shouldn't ignore good modules. EVIL_C_OBJECTS = { 'matplotlib.axes.BinOpType': None, # broken on versions <= 0.83.2 # broken on versions at least 2.5.5 up to 2.6 'wx.TheClipboard': None, 'wx._core.TheClipboard': None, 'wx._misc.TheClipboard': None, } __pcmodules = {} def _filterDir(object, ignoreList): """ Return a list of attribute names of an object, excluding the ones in ignoreList. @type ignoreList: list of str @rtype: list of str """ tokens = dir(object) for token in ignoreList: if token in tokens: tokens.remove(token) return tokens def _getClassTokens(c): return _filterDir(c, _DEFAULT_CLASS_TOKENS) def _getPyFile(filename): """Return the file and '.py' filename from a filename which could end with .py, .pyc, or .pyo""" if filename[-1] in 'oc' and filename[-4:-1] == '.py': return filename[:-1] return filename def _getModuleTokens(m): return _filterDir(m, _DEFAULT_MODULE_TOKENS) class Variable: "Class to hold all information about a variable" def __init__(self, name, type): """ @param name: name of the variable @type name: str @param type: type of the variable @type type: type """ self.name = name self.type = type self.value = None def __str__(self) : return self.name __repr__ = utils.std_repr class Class: """ Class to hold all information about a class. @ivar name: name of class @type name: str @ivar classObject: the object representing the class @type classObject: class @ivar module: the module where the class is defined @type module: module @ivar ignoreAttrs: whether to ignore this class's attributes when checking attributes. Can be set because of a bad __getattr__ or because the module this class comes from is blacklisted. @type ignoreAttrs: int (used as bool) @type methods: dict @type members: dict of str -> type @type memberRefs: dict @type statics: dict @type lineNums: dict """ def __init__(self, name, pcmodule): """ @type name: str @type pcmodule: L{PyCheckerModule} """ self.name = name module = pcmodule.module self.classObject = getattr(module, name) modname = getattr(self.classObject, '__module__', None) if modname is None: # hm, some ExtensionClasses don't have a __module__ attribute # so try parsing the type output typerepr = repr(type(self.classObject)) mo = re.match("^$", typerepr) if mo: modname = ".".join(mo.group(1).split(".")[:-1]) # TODO(nnorwitz): this check for __name__ might not be necessary # any more. Previously we checked objects as if they were classes. # This problem is fixed by not adding objects as if they are classes. # zope.interface for example has Provides and Declaration that # look a lot like class objects but do not have __name__ if not hasattr(self.classObject, '__name__'): if modname not in utils.cfg().blacklist: sys.stderr.write("warning: no __name__ attribute " "for class %s (module name: %s)\n" % (self.classObject, modname)) self.classObject.__name__ = name # later pychecker code uses this self.classObject__name__ = self.classObject.__name__ self.module = sys.modules.get(modname) # if the pcmodule has moduleDir, it means we processed it before, # and deleted it from sys.modules if not self.module and pcmodule.moduleDir is None: self.module = module if modname not in utils.cfg().blacklist: sys.stderr.write("warning: couldn't find real module " "for class %s (module name: %s)\n" % (self.classObject, modname)) self.ignoreAttrs = 0 self.methods = {} self.members = { '__class__': types.ClassType, '__doc__': types.StringType, '__dict__': types.DictType, } self.memberRefs = {} self.statics = {} self.lineNums = {} def __str__(self) : return self.name __repr__ = utils.std_repr def getFirstLine(self) : "Return first line we can find in THIS class, not any base classes" lineNums = [] classDir = dir(self.classObject) for m in self.methods.values() : if m != None and m.function.func_code.co_name in classDir: lineNums.append(m.function.func_code.co_firstlineno) if lineNums : return min(lineNums) return 0 def allBaseClasses(self, c = None) : "Return a list of all base classes for this class and its subclasses" baseClasses = [] if c == None : c = self.classObject for base in getattr(c, '__bases__', None) or (): baseClasses = baseClasses + [ base ] + self.allBaseClasses(base) return baseClasses def __getMethodName(self, func_name, className = None) : if func_name[0:2] == '__' and func_name[-2:] != '__' : if className == None : className = self.name if className[0] != '_' : className = '_' + className func_name = className + func_name return func_name def addMethod(self, methodName, method=None): """ Add the given method to this class by name. @type methodName: str @type method: method or None """ if not method: self.methods[methodName] = None else : self.methods[methodName] = function.Function(method, 1) def addMethods(self, classObject): """ Add all methods for this class object to the class. @param classObject: the class object to add methods from. @type classObject: types.ClassType (classobj) """ for classToken in _getClassTokens(classObject): token = getattr(classObject, classToken, None) if token is None: continue # Looks like a method. Need to code it this way to # accommodate ExtensionClass and Python 2.2. Yecchh. if (hasattr(token, "func_code") and hasattr(token.func_code, "co_argcount")): self.addMethod(token.__name__, method=token) elif hasattr(token, '__get__') and \ not hasattr(token, '__set__') and \ type(token) is not types.ClassType: self.addMethod(getattr(token, '__name__', classToken)) else: self.members[classToken] = type(token) self.memberRefs[classToken] = None self.cleanupMemberRefs() # add standard methods for methodName in ('__class__', ): self.addMethod(methodName) def addMembers(self, classObject) : if not utils.cfg().onlyCheckInitForMembers : for classToken in _getClassTokens(classObject) : method = getattr(classObject, classToken, None) if type(method) == types.MethodType : self.addMembersFromMethod(method.im_func) else: try: self.addMembersFromMethod(classObject.__init__.im_func) except AttributeError: pass def addMembersFromMethod(self, method) : if not hasattr(method, 'func_code') : return func_code, code, i, maxCode, extended_arg = OP.initFuncCode(method) stack = [] while i < maxCode : op, oparg, i, extended_arg = OP.getInfo(code, i, extended_arg) if op >= OP.HAVE_ARGUMENT : operand = OP.getOperand(op, func_code, oparg) if OP.LOAD_CONST(op) or OP.LOAD_FAST(op) or OP.LOAD_GLOBAL(op): stack.append(operand) elif OP.LOAD_DEREF(op): try: operand = func_code.co_cellvars[oparg] except IndexError: index = oparg - len(func_code.co_cellvars) operand = func_code.co_freevars[index] stack.append(operand) elif OP.STORE_ATTR(op) : if len(stack) > 0 : if stack[-1] == utils.cfg().methodArgName: value = None if len(stack) > 1 : value = type(stack[-2]) self.members[operand] = value self.memberRefs[operand] = None stack = [] self.cleanupMemberRefs() def cleanupMemberRefs(self) : try : del self.memberRefs[Config.CHECKER_VAR] except KeyError : pass def abstractMethod(self, m): """Return 1 if method is abstract, None if not An abstract method always raises an exception. """ if not self.methods.get(m, None): return None funcCode, codeBytes, i, maxCode, extended_arg = \ OP.initFuncCode(self.methods[m].function) # abstract if the first opcode is RAISE_VARARGS and it raises # NotImplementedError arg = "" while i < maxCode: op, oparg, i, extended_arg = OP.getInfo(codeBytes, i, extended_arg) if OP.LOAD_GLOBAL(op): arg = funcCode.co_names[oparg] elif OP.RAISE_VARARGS(op): # if we saw NotImplementedError sometime before the raise # assume it's related to this raise stmt return arg == "NotImplementedError" if OP.conditional(op): break return None def isAbstract(self): """Return the method names that make a class abstract. An abstract class has at least one abstract method.""" result = [] for m in self.methods.keys(): if self.abstractMethod(m): result.append(m) return result class PyCheckerModule: """ Class to hold all information for a module @ivar module: the module wrapped by this PyCheckerModule @type module: module @ivar moduleName: name of the module @type moduleName: str @ivar moduleDir: if specified, the directory where the module can be loaded from; allows discerning between modules with the same name in a different directory. Note that moduleDir can be the empty string, if the module being tested lives in the current working directory. @type moduleDir: str @ivar variables: dict of variable name -> Variable @type variables: dict of str -> L{Variable} @ivar functions: dict of function name -> function @type functions: dict of str -> L{function.Function} @ivar classes: dict of class name -> class @type classes: dict of str -> L{Class} @ivar modules: dict of module name -> module @type modules: dict of str -> L{PyCheckerModule} @ivar moduleLineNums: mapping of the module's nameds/operands to the filename and linenumber where they are created @type moduleLineNums: dict of str -> (str, int) @type mainCode: L{function.Function} @ivar check: whether this module should be checked @type check: int (used as bool) """ def __init__(self, moduleName, check=1, moduleDir=None): """ @param moduleName: name of the module @type moduleName: str @param check: whether this module should be checked @type check: int (used as bool) @param moduleDir: if specified, the directory where the module can be loaded from; allows discerning between modules with the same name in a different directory. Note that moduleDir can be the empty string, if the module being tested lives in the current working directory. @type moduleDir: str """ self.module = None self.moduleName = moduleName self.moduleDir = moduleDir self.variables = {} self.functions = {} self.classes = {} self.modules = {} self.moduleLineNums = {} self.attributes = [ '__dict__' ] self.mainCode = None self.check = check # key on a combination of moduleName and moduleDir so we have separate # entries for modules with the same name but in different directories addPCModule(self) def __str__(self): return self.moduleName __repr__ = utils.std_repr def addVariable(self, var, varType): """ @param var: name of the variable @type var: str @param varType: type of the variable @type varType: type """ self.variables[var] = Variable(var, varType) def addFunction(self, func): """ @type func: callable """ self.functions[func.__name__] = function.Function(func) def __addAttributes(self, c, classObject) : for base in getattr(classObject, '__bases__', None) or (): self.__addAttributes(c, base) c.addMethods(classObject) c.addMembers(classObject) def addClass(self, name): self.classes[name] = c = Class(name, self) try: objName = utils.safestr(c.classObject) except TypeError: # this can happen if there is a goofy __getattr__ c.ignoreAttrs = 1 else: packages = string.split(objName, '.') c.ignoreAttrs = packages[0] in utils.cfg().blacklist if not c.ignoreAttrs : self.__addAttributes(c, c.classObject) def addModule(self, name, moduleDir=None) : module = getPCModule(name, moduleDir) if module is None : self.modules[name] = module = PyCheckerModule(name, 0) if imp.is_builtin(name) == 0: module.load() else : globalModule = globals().get(name) if globalModule : module.attributes.extend(dir(globalModule)) else : self.modules[name] = module def filename(self) : try : filename = self.module.__file__ except AttributeError : filename = self.moduleName # FIXME: we're blindly adding .py, but it might be something else. if self.moduleDir: filename = self.moduleDir + '/' + filename + '.py' return _getPyFile(filename) def load(self): try : # there's no need to reload modules we already have if no moduleDir # is specified for this module # NOTE: self.moduleDir can be '' if the module tested lives in # the current working directory if self.moduleDir is None: module = sys.modules.get(self.moduleName) if module: pcmodule = getPCModule(self.moduleName) if not pcmodule.module: return self._initModule(module) return 1 return self._initModule(self.setupMainCode()) except (SystemExit, KeyboardInterrupt): exc_type, exc_value, exc_tb = sys.exc_info() raise exc_type, exc_value except: utils.importError(self.moduleName, self.moduleDir) return utils.cfg().ignoreImportErrors def initModule(self, module) : if not self.module: filename = _getPyFile(module.__file__) if string.lower(filename[-3:]) == '.py': try: handle = open(filename) except IOError: pass else: self._setupMainCode(handle, filename, module) return self._initModule(module) return 1 def _initModule(self, module): self.module = module self.attributes = dir(self.module) # interpret module-specific suppressions pychecker_attr = getattr(module, Config.CHECKER_VAR, None) if pychecker_attr is not None : utils.pushConfig() utils.updateCheckerArgs(pychecker_attr, 'suppressions', 0, []) # read all tokens from the real module, and register them for tokenName in _getModuleTokens(self.module): if EVIL_C_OBJECTS.has_key('%s.%s' % (self.moduleName, tokenName)): continue # README if interpreter is crashing: # Change 0 to 1 if the interpretter is crashing and re-run. # Follow the instructions from the last line printed. if utils.cfg().findEvil: print "Add the following line to EVIL_C_OBJECTS or the string to evil in a config file:\n" \ " '%s.%s': None, " % (self.moduleName, tokenName) token = getattr(self.module, tokenName) if isinstance(token, types.ModuleType) : # get the real module name, tokenName could be an alias self.addModule(token.__name__) elif isinstance(token, types.FunctionType) : self.addFunction(token) elif isinstance(token, types.ClassType) or \ hasattr(token, '__bases__') and \ issubclass(type(token), type): self.addClass(tokenName) else : self.addVariable(tokenName, type(token)) if pychecker_attr is not None : utils.popConfig() return 1 def setupMainCode(self): handle, filename, smt = utils.findModule( self.moduleName, self.moduleDir) # FIXME: if the smt[-1] == imp.PKG_DIRECTORY : load __all__ # HACK: to make sibling imports work, we add self.moduleDir to sys.path # temporarily, and remove it later if self.moduleDir is not None: oldsyspath = sys.path[:] sys.path.insert(0, self.moduleDir) module = imp.load_module(self.moduleName, handle, filename, smt) if self.moduleDir is not None: sys.path = oldsyspath # to make sure that subsequent modules with the same moduleName # do not persist, and get their namespace clobbered, delete it del sys.modules[self.moduleName] self._setupMainCode(handle, filename, module) return module def _setupMainCode(self, handle, filename, module): try: self.mainCode = function.create_from_file(handle, filename, module) finally: if handle != None: handle.close() def getToken(self, name): """ Looks up the given name in this module's namespace. @param name: the name of the token to look up in this module. @rtype: one of L{Variable}, L{function.Function}, L{Class}, L{PyCheckerModule}, or None """ if name in self.variables: return self.variables[name] elif name in self.functions: return self.functions[name] elif name in self.classes: return self.classes[name] elif name in self.modules: return self.modules[name] return None def getPCModule(moduleName, moduleDir=None): """ @type moduleName: str @param moduleDir: if specified, the directory where the module can be loaded from; allows discerning between modules with the same name in a different directory. Note that moduleDir can be the empty string, if the module being tested lives in the current working directory. @type moduleDir: str @rtype: L{pychecker.checker.PyCheckerModule} """ global __pcmodules return __pcmodules.get((moduleName, moduleDir), None) def getPCModules(): """ @rtype: list of L{pychecker.checker.PyCheckerModule} """ global __pcmodules return __pcmodules.values() def addPCModule(pcmodule): """ @type pcmodule: L{pychecker.checker.PyCheckerModule} """ global __pcmodules __pcmodules[(pcmodule.moduleName, pcmodule.moduleDir)] = pcmodule pychecker-0.8.19/pychecker/__init__.py0000664000076400007640000000103710505257443017244 0ustar thomasthomas""" Copyright (c) 2001, MetaSlash Inc. All rights reserved. PyChecker is a tool for finding common bugs in python source code. It finds problems that are typically caught by a compiler for less dynamic languages, like C and C++. It is also similar to lint. Contact Info: http://pychecker.sourceforge.net/ pychecker-list@lists.sourceforge.net """ # A version # to check against in the main module (checker.py) # this will allow us to check if there are two versions of checker # in site-packages and local dir MAIN_MODULE_VERSION = 3 pychecker-0.8.19/pychecker/msgs.py0000644000076400007640000002141611511444550016452 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2004, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Warning Messages for PyChecker """ import UserString class WarningClass: level = 0 def __init__(self, msg, level_offset=None): self.msg = msg if level_offset is not None: self.level += level_offset def __mod__(self, args): result = UserString.UserString(self.msg % args) result.level = self.level return result def __str__(self): return self.msg class Internal(WarningClass): level = 100 class Error(WarningClass): level = 90 class Security(WarningClass): level = 90 class Warning(WarningClass): level = 70 class Unused(WarningClass): level = 50 class Deprecated(WarningClass): level = 40 class Style(WarningClass): level = 10 TOO_MANY_WARNINGS = WarningClass("%d errors suppressed, use -#/--limit to increase the number of errors displayed") CHECKER_BROKEN = Internal("INTERNAL ERROR -- STOPPED PROCESSING FUNCTION --\n\t%s") INVALID_CHECKER_ARGS = Internal("Invalid warning suppression arguments --\n\t%s") NO_MODULE_DOC = Style("No module doc string") NO_CLASS_DOC = Style("No doc string for class %s") NO_FUNC_DOC = Style("No doc string for function %s") VAR_NOT_USED = Unused("Variable (%s) not used") IMPORT_NOT_USED = Unused("Imported module (%s) not used") UNUSED_LOCAL = Unused("Local variable (%s) not used") UNUSED_PARAMETER = Unused("Parameter (%s) not used") UNUSED_MEMBERS = Unused("Members (%s) not used in class (%s)") NO_LOCAL_VAR = Unused("No local variable (%s)") VAR_USED_BEFORE_SET = Warning("Variable (%s) used before being set") REDEFINING_ATTR = Warning("Redefining attribute (%s) original line (%d)") MODULE_IMPORTED_AGAIN = Warning("Module (%s) re-imported") MODULE_MEMBER_IMPORTED_AGAIN = Warning("Module member (%s) re-imported") MODULE_MEMBER_ALSO_STAR_IMPORTED = Warning("Module member (%s) re-imported with *") MIX_IMPORT_AND_FROM_IMPORT = Warning("Using import and from ... import for (%s)") IMPORT_SELF = Warning("Module (%s) imports itself") NO_METHOD_ARGS = Error("No method arguments, should have %s as argument") SELF_NOT_FIRST_ARG = Error("%s is not first %smethod argument") SELF_IS_ARG = Error("self is argument in %s") RETURN_FROM_INIT = Error("Cannot return a value from __init__") NO_CTOR_ARGS = Error("Instantiating an object with arguments, but no constructor") GLOBAL_DEFINED_NOT_DECLARED = Warning("Global variable (%s) not defined in module scope") INVALID_GLOBAL = Error("No global (%s) found") INVALID_METHOD = Error("No method (%s) found") INVALID_CLASS_ATTR = Warning("No class attribute (%s) found") INVALID_SET_CLASS_ATTR = Warning("Setting class attribute (%s) not set in __init__") INVALID_MODULE_ATTR = Error("No module attribute (%s) found") LOCAL_SHADOWS_GLOBAL = Warning("Local variable (%s) shadows global defined on line %d") VARIABLE_SHADOWS_BUILTIN = Warning("(%s) shadows builtin") USING_METHOD_AS_ATTR = Warning("Using method (%s) as an attribute (not invoked)") OBJECT_HAS_NO_ATTR = Warning("Object (%s) has no attribute (%s)") METHOD_SIGNATURE_MISMATCH = Warning("Overridden method (%s) doesn't match signature in class (%s)") INVALID_ARG_COUNT1 = Error("Invalid arguments to (%s), got %d, expected %d") INVALID_ARG_COUNT2 = Error("Invalid arguments to (%s), got %d, expected at least %d") INVALID_ARG_COUNT3 = Error("Invalid arguments to (%s), got %d, expected between %d and %d") FUNC_DOESNT_SUPPORT_KW = Error("Function (%s) doesn't support **kwArgs") FUNC_DOESNT_SUPPORT_KW_ARG = Error("Function (%s) doesn't support **kwArgs for name (%s)") FUNC_USES_NAMED_ARGS = Warning("Function (%s) uses named arguments") BASE_CLASS_NOT_INIT = Warning("Base class (%s) __init__() not called") NO_INIT_IN_SUBCLASS = Warning("No __init__() in subclass (%s)") METHODS_NEED_OVERRIDE = Error("Methods (%s) in %s need to be overridden in a subclass") FUNC_TOO_LONG = Style("Function (%s) has too many lines (%d)") TOO_MANY_BRANCHES = Style("Function (%s) has too many branches (%d)") TOO_MANY_RETURNS = Style("Function (%s) has too many returns (%d)") TOO_MANY_ARGS = Style("Function (%s) has too many arguments (%d)") TOO_MANY_LOCALS = Style("Function (%s) has too many local variables (%d)") TOO_MANY_REFERENCES = Style('Law of Demeter violated, more than %d references for (%s)') IMPLICIT_AND_EXPLICIT_RETURNS = Warning("Function returns a value and also implicitly returns None") INCONSISTENT_RETURN_TYPE = Warning("Function return types are inconsistent") INCONSISTENT_TYPE = Warning("Variable (%s) already has types %s and set to %s") CODE_UNREACHABLE = Error("Code appears to be unreachable") CONSTANT_CONDITION = Warning("Using a conditional statement with a constant value (%s)") STRING_ITERATION = Warning("Iterating over a string (%s)") DONT_RETURN_NONE = Error("%s should not return None, raise an exception if not found") IS_LITERAL = Warning("Using is%s %s, may not always work") INVALID_FORMAT = Error("Invalid format string, problem starts near: '%s'") INVALID_FORMAT_COUNT = Error("Format string argument count (%d) doesn't match arguments (%d)") TOO_MANY_STARS_IN_FORMAT = Error("Too many *s in format flags") USING_STAR_IN_FORMAT_MAPPING = Error("Can't use * in formats when using a mapping (dictionary), near: '%s'") CANT_MIX_MAPPING_IN_FORMATS = Error("Can't mix tuple/mapping (dictionary) formats in same format string") INTEGER_DIVISION = Warning("Using integer division (%s / %s) may return integer or float") MODULO_1 = Warning("... % 1 may be constant") USING_TUPLE_ACCESS_TO_LIST = Error("Using a tuple instead of slice as list accessor for (%s)") BOOL_COMPARE = Warning("Comparisons with %s are not necessary and may not work as expected") SHOULDNT_ASSIGN_BUILTIN = Deprecated("Should not assign to %s, it is (or will be) a builtin") SHOULDNT_ASSIGN_NAME = Deprecated("Should not assign to %s, it is similar to builtin %s") SET_VAR_TO_ITSELF = Warning("Setting %s to itself has no effect") MODIFY_VAR_NOOP = Warning("%s %s %s has no effect") DIVIDE_VAR_BY_ITSELF = Warning("%s %s %s is always 1 or ZeroDivisionError") XOR_VAR_WITH_ITSELF = Warning("%s %s %s is always 0") STMT_WITH_NO_EFFECT = Error("Operator (%s) doesn't exist, statement has no effect") POSSIBLE_STMT_WITH_NO_EFFECT = Error("Statement appears to have no effect") UNARY_POSITIVE_HAS_NO_EFFECT = Error("Unary positive (+) usually has no effect") LIST_APPEND_ARGS = Error("[].append() only takes 1 argument in Python 1.6 and above for (%s)") LOCAL_DELETED = Error("(%s) cannot be used after being deleted on line %d") LOCAL_ALREADY_DELETED = Error("Local variable (%s) has already been deleted on line %d") VAR_DELETED_BEFORE_SET = Error("Variable (%s) deleted before being set") CATCH_BAD_EXCEPTION = Warning("Catching a non-Exception object (%s)") CATCH_STR_EXCEPTION = Deprecated("Catching string exceptions are deprecated (%s)") RAISE_BAD_EXCEPTION = Warning("Raising an exception on a non-Exception object (%s)") RAISE_STR_EXCEPTION = Deprecated("Raising string exceptions are deprecated (%s)") SET_EXCEPT_TO_BUILTIN = Error("Setting exception to builtin (%s), consider () around exceptions") USING_KEYWORD = Warning("Using identifier (%s) which will become a keyword in version %s") MODIFYING_DEFAULT_ARG = Warning("Modifying parameter (%s) with a default value may have unexpected consequences") USING_SELF_IN_REPR = Warning("Using `self` in __repr__ method") USING_NONE_RETURN_VALUE = Error("Using the return value from (%s) which is always None") WRONG_UNPACK_SIZE = Error("Unpacking %d values into %d variables") WRONG_UNPACK_FUNCTION = Error("Unpacking function (%s) which returns %d values into %d variables") UNPACK_NON_SEQUENCE = Error("Unpacking a non-sequence (%s) of type %s") NOT_SPECIAL_METHOD = Warning("%s is not a special method") USING_COERCE_IN_NEW_CLASS = Error("Using __coerce__ in new-style class (%s) will not work for binary operations") USING_NEW_STYLE_METHOD_IN_OLD_CLASS = Error("Using %s in old-style class (%s) does not work") USING_PROPERTIES_IN_CLASSIC_CLASS = Error("Using property (%s) in classic class %s may not work") USING_SLOTS_IN_CLASSIC_CLASS = Error("Using __slots__ in classic class %s has no effect, consider deriving from object") EMPTY_SLOTS = Warning("__slots__ are empty in %s") USES_EXEC = Security("Using the exec statement") USES_GLOBAL_EXEC = Security("Using the exec statement in global namespace") USES_INPUT = Security("Using input() is a security problem, consider using raw_input()") USING_DEPRECATED_MODULE = Deprecated("%s module is deprecated") USING_DEPRECATED_ATTR = Deprecated("%s is deprecated") USING_INSECURE_FUNC = Security("%s() is a security problem") USE_INSTEAD = ", consider using %s" USES_CONST_ATTR = Warning("Passing a constant string to %s, consider direct reference") BAD_STRING_FIND = Error("string.find() returns an integer, consider checking >= 0 or < 0 for not found") pychecker-0.8.19/pychecker/function.py0000644000076400007640000002101511511444550017321 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2002, MetaSlash Inc. All rights reserved. """ Object to hold information about functions. Also contain a pseudo Python function object """ import string _ARGS_ARGS_FLAG = 4 _KW_ARGS_FLAG = 8 _CO_FLAGS_MASK = _ARGS_ARGS_FLAG + _KW_ARGS_FLAG class _ReturnValues: """ I am a base class that can track return values. @ivar returnValues: tuple of (line number, stack item, index to next instruction) @type returnValues: tuple of (int, L{pychecker.Stack.Item}, int) """ def __init__(self): self.returnValues = None def returnsNoValue(self): returnValues = self.returnValues # if unset, we don't know if returnValues is None: return 0 # it's an empty list, that means no values if not returnValues: return 1 # make sure each value is not None for rv in returnValues: if not rv[1].isNone(): return 0 return returnValues[-1][1].isImplicitNone() class FakeCode : "This is a holder class for code objects (so we can modify them)" def __init__(self, code, varnames = None) : """ @type code: L{types.CodeType} """ for attr in dir(code): try: setattr(self, attr, getattr(code, attr)) except: pass if varnames is not None: self.co_varnames = varnames class FakeFunction(_ReturnValues): """ This is a holder class for turning non-scoped code (for example at module-global level, or generator expressions) into a function. Pretends to be a normal callable and can be used as constructor argument to L{Function} """ def __init__(self, name, code, func_globals = {}, varnames = None) : _ReturnValues.__init__(self) self.func_name = self.__name__ = name self.func_doc = self.__doc__ = "ignore" self.func_code = FakeCode(code, varnames) self.func_defaults = None self.func_globals = func_globals def __str__(self): return self.func_name def __repr__(self): return '%s from %r' % (self.func_name, self.func_code.co_filename) class Function(_ReturnValues): """ Class to hold all information about a function @ivar function: the function to wrap @type function: callable @ivar isMethod: whether the callable is a method @type isMethod: int (used as bool) @ivar minArgs: the minimum number of arguments that should be passed to this function @type minArgs: int @ivar minArgs: the maximum number of arguments that should be passed to this function, or None in case of *args/unlimited @type maxArgs: int or None @ivar supportsKW: whether the function supports keyword arguments. @type supportsKW: int (used as bool) """ def __init__(self, function, isMethod=0): """ @param function: the function to wrap @type function: callable or L{FakeFunction} @param isMethod: whether the callable is a method @type isMethod: int (used as bool) """ _ReturnValues.__init__(self) self.function = function self.isMethod = isMethod # co_argcount is the number of positional arguments (including # arguments with default values) self.minArgs = self.maxArgs = function.func_code.co_argcount # func_defaults is a tuple containing default argument values for those # arguments that have defaults, or None if no arguments have a default # value if function.func_defaults is not None: self.minArgs = self.minArgs - len(function.func_defaults) # if function uses *args, there is no max # args try: # co_flags is an integer encoding a number of flags for the # interpreter. if function.func_code.co_flags & _ARGS_ARGS_FLAG != 0: self.maxArgs = None self.supportsKW = function.func_code.co_flags & _KW_ARGS_FLAG except AttributeError: # this happens w/Zope self.supportsKW = 0 def __str__(self): return self.function.func_name def __repr__(self): # co_filename is the filename from which the code was compiled # co_firstlineno is the first line number of the function return '<%s from %r:%d>' % (self.function.func_name, self.function.func_code.co_filename, self.function.func_code.co_firstlineno) def arguments(self): """ @returns: a list of argument names to this function @rtype: list of str """ # see http://docs.python.org/reference/datamodel.html#types # for more info on func_code # co_argcount is the number of positional arguments (including # arguments with default values) numArgs = self.function.func_code.co_argcount if self.maxArgs is None: # co_varnames has the name of the *args variable after the # positional arguments numArgs = numArgs + 1 if self.supportsKW: # co_varnames has the name of the **kwargs variable after the # positional arguments and *args variable numArgs = numArgs + 1 # co_varnames is a tuple containing the names of the local variables # (starting with the argument names) # FIXME: a generator seems to have .0 as the first member here, # and then the generator variable as the second. # should we special-case that here ? return self.function.func_code.co_varnames[:numArgs] def isParam(self, name): """ @type name: str @returns: Whether the given name is the name of an argument to the function @rtype: bool """ return name in self.arguments() def isStaticMethod(self): return self.isMethod and isinstance(self.function, type(create_fake)) def isClassMethod(self): try: return self.isMethod and self.function.im_self is not None except AttributeError: return 0 def defaultValue(self, name): """ @type name: str @returns: the default value for the function parameter with the given name. """ func_code = self.function.func_code arg_names = list(func_code.co_varnames[:func_code.co_argcount]) i = arg_names.index(name) if i < self.minArgs: raise ValueError return self.function.func_defaults[i - self.minArgs] def varArgName(self): """ @returns: the name of the *args parameter of the function. @rtype: str """ if self.maxArgs is not None: return None func_code = self.function.func_code return func_code.co_varnames[func_code.co_argcount] def create_fake(name, code, func_globals = {}, varnames = None) : return Function(FakeFunction(name, code, func_globals, varnames)) def create_from_file(file, filename, module): """ @type filename: str @returns: a function that represents the __main__ entry point, if there was a file @rtype: L{Function} """ if file is None: return create_fake(filename, compile('', filename, 'exec')) # Make sure the file is at the beginning # if python compiled the file, it will be at the end file.seek(0) # Read in the source file, see py_compile.compile() for games w/src str codestr = file.read() codestr = string.replace(codestr, "\r\n", "\n") codestr = string.replace(codestr, "\r", "\n") if codestr and codestr[-1] != '\n': codestr = codestr + '\n' code = compile(codestr, filename, 'exec') return Function(FakeFunction('__main__', code, module.__dict__)) def _co_flags_equal(o1, o2) : return (o1.co_flags & _CO_FLAGS_MASK) == (o2.co_flags & _CO_FLAGS_MASK) def same_signature(func, object) : '''Return a boolean value if the has the same signature as a function with the same name in (ie, an overriden method)''' try : baseMethod = getattr(object, func.func_name) base_func_code = baseMethod.im_func.func_code except AttributeError : return 1 return _co_flags_equal(base_func_code, func.func_code) and \ base_func_code.co_argcount == func.func_code.co_argcount pychecker-0.8.19/pychecker/options.py0000755000076400007640000002124611511444550017200 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 "Main module for running pychecker a Tkinter GUI for all the options" import sys import os import Tkinter, tkFileDialog from OptionTypes import * from string import capitalize, strip, rstrip, split import Config MAX_SUBBOX_ROWS = 8 MAX_BOX_COLS = 3 PAD = 10 EDITOR = "xterm -e vi -n +%(line)d %(file)s" if sys.platform == 'win32': EDITOR = "notepad %(file)s" def col_weight(grid): "Set column weights so that sticky grid settings actually work" unused, col = grid.grid_size() for c in range(col): grid.columnconfigure(c, weight=1) def spawn(cmd_list): try: if os.fork(): try: os.execvp(cmd_list[0], cmd_list) finally: sys.exit() except AttributeError: os.execvp(cmd_list[0], cmd_list) def edit(file, line): "Fire up an external editor to see the file at the given line" unused = file, line args = split(EDITOR) cmd_list = [] for word in args: cmd_list.append(word % locals()) spawn(cmd_list) def closeCB(): sys.exit(0) class Results: "Display the warnings produced by checker" def __init__(self, w): self.top = Tkinter.Toplevel(w, name="results") self.top.transient(w) self.top.bind('', self.hide) self.top.bind('', self.hide) self.text = Tkinter.Text(self.top, name="text") self.text.grid() self.text.bind('', self.showFile) close = Tkinter.Button(self.top, name="close", default=Tkinter.ACTIVE, command=self.hide) close.grid() self.text.update_idletasks() def show(self, text): self.text.delete("0.1", "end") self.text.insert("0.1", text) self.top.deiconify() self.top.lift() def hide(self, *unused): self.top.withdraw() def line(self): return split(self.text.index(Tkinter.CURRENT), ".")[0] def showFile(self, unused): import re line = self.line() text = self.text.get(line + ".0", line + ".end") text = rstrip(text) result = re.search("(.*):([0-9]+):", text) if result: path, line = result.groups() edit(path, int(line)) self.text.after(0, self.selectLine) def selectLine(self): line = self.line() self.text.tag_remove(Tkinter.SEL, "1.0", Tkinter.END) self.text.tag_add(Tkinter.SEL, line + ".0", line + ".end") class ConfigDialog: "Dialog for editing options" def __init__(self, tk): self._tk = tk self._cfg, _, _ = Config.setupFromArgs(sys.argv) self._help = None self._optMap = {} self._opts = [] self._file = Tkinter.StringVar() self._results = None if len(sys.argv) > 1: self._file.set(sys.argv[1]) for name, group in Config._OPTIONS: opts = [] for _, useValue, longArg, member, description in group: value = None if member: value = getattr(self._cfg, member) description = member + ": " + capitalize(description) description = strip(description) tk.option_add('*' + longArg + ".help", description) if useValue: if type(value) == type([]): field = List(longArg, value) elif type(value) == type(1): field = Number(longArg, int(value)) elif type(value) == type(''): field = Text(longArg, value) else: field = Boolean(longArg, value) else: field = Boolean(longArg, value) self._optMap[longArg] = field opts.append(field) self._opts.append( (name, opts)) def _add_fields(self, w, opts): count = 0 for opt in opts: f = opt.field(w) c, r = divmod(count, MAX_SUBBOX_ROWS) f.grid(row=r, column=c, sticky=Tkinter.NSEW) count = count + 1 def _add_group(self, w, name, opts): colFrame = Tkinter.Frame(w) label = Tkinter.Label(colFrame, text=name + ":") label.grid(row=0, column=0, sticky=Tkinter.NSEW) gframe = Tkinter.Frame(colFrame, relief=Tkinter.GROOVE, borderwidth=2) gframe.grid(row=1, column=0, sticky=Tkinter.NSEW) self._add_fields(gframe, opts) label = Tkinter.Label(colFrame) label.grid(row=2, column=0, sticky=Tkinter.NSEW) colFrame.rowconfigure(2, weight=1) return colFrame def main(self): frame = Tkinter.Frame(self._tk, name="opts") frame.grid() self._tk.option_readfile('Options.ad') self._fields = {} row, col = 0, 0 rowFrame = Tkinter.Frame(frame) rowFrame.grid(row=row) row = row + 1 for name, opts in self._opts: w = self._add_group(rowFrame, name, opts) w.grid(row=row, column=col, sticky=Tkinter.NSEW, padx=PAD) col = col + 1 if col >= MAX_BOX_COLS: col_weight(rowFrame) rowFrame=Tkinter.Frame(frame) rowFrame.grid(row=row, sticky=Tkinter.NSEW) col = 0 row = row + 1 col_weight(rowFrame) self._help = Tkinter.Label(self._tk, name="helpBox") self._help.grid(row=row) self._help.config(takefocus=0) buttons = Tkinter.Frame(self._tk, name="buttons") ok = Tkinter.Button(buttons, name="ok", command=self.ok, default=Tkinter.ACTIVE) ok.grid(row=row, column=0) default = Tkinter.Button(buttons, name="default", command=self.default) default.grid(row=row, column=1) close = Tkinter.Button(buttons, name="close", command=closeCB) close.grid(row=row, column=2) buttons.grid() f = Tkinter.Frame(self._tk, name="fileStuff") Tkinter.Button(f, name="getfile", command=self.file).grid(row=0, column=1) fileEntry = Tkinter.Entry(f, name="fname", textvariable=self._file) fileEntry.grid(row=0, column=2) Tkinter.Button(f, name="check", command=self.check).grid(row=0, column=3) f.grid(sticky=Tkinter.EW) self._tk.bind_all('', self.focus) self._tk.bind_all('', self.focus) self._tk.bind_all('', self.click) fileEntry.bind('', self.check) self._tk.mainloop() # # Callbacks # def help(self, w): if type(w) == type(''): # occurs with file dialog... return if self._help == w: # ignore help events on help... return text = w.option_get("help", "help") self._help.configure(text=text) def focus(self, ev): self.help(ev.widget) def click(self, ev): self.help(ev.widget) def ok(self): opts = [] # Pull command-line args for _, group in self._opts: for opt in group: arg = opt.arg() if arg: opts.append(arg) # Calculate config self._cfg, _, _ = Config.setupFromArgs(opts) # Set controls based on new config for _, group in Config._OPTIONS: for _, _, longArg, member, _ in group: if member: self._optMap[longArg].set(getattr(self._cfg, member)) def default(self): self._cfg, _, _ = Config.setupFromArgs(sys.argv) for _, group in Config._OPTIONS: for _, _, longArg, member, _ in group: if member: self._optMap[longArg].set(getattr(self._cfg, member)) else: self._optMap[longArg].set(0) def file(self): self._file.set(tkFileDialog.askopenfilename()) def check(self, *unused): import checker import StringIO self.ok() # show effect of all settings checker._allModules = {} warnings = checker.getWarnings([self._file.get()], self._cfg) capture = StringIO.StringIO() if not self._results: self._results = Results(self._help) checker._printWarnings(warnings, capture) value = strip(capture.getvalue()) if not value: value = "None" self._results.show(value) if __name__=='__main__': dirs = os.path.join(os.path.split(os.getcwd())[:-1]) sys.path.append(dirs[0]) tk = Tkinter.Tk() tk.title('PyChecker') ConfigDialog(tk).main() pychecker-0.8.19/pychecker/python.py0000644000076400007640000004425611511444550017031 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2004, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Setup a lot of info about Python builtin types, functions, methods, etc. """ import types import sys from pychecker import utils from pychecker import Stack from pychecker import Warning BOOL = types.IntType # name (type, args: min, max, kwArgs? GLOBAL_FUNC_INFO = { '__import__': (types.ModuleType, 1, 4), 'abs': (Stack.TYPE_UNKNOWN, 1, 1), 'apply': (Stack.TYPE_UNKNOWN, 2, 3), 'buffer': (types.BufferType, 1, 3), 'callable': (BOOL, 1, 1), 'chr': (types.StringType, 1, 1), 'cmp': (types.IntType, 2, 2), 'coerce': ([ types.NoneType, types.TupleType ], 2, 2), 'compile': (types.CodeType, 3, 3), 'complex': (types.ComplexType, 1, 2, ['real', 'imag']), 'delattr': (types.NoneType, 2, 2), 'dir': (types.ListType, 0, 1), 'divmod': (types.TupleType, 2, 2), 'eval': (Stack.TYPE_UNKNOWN, 1, 3), 'execfile': (types.NoneType, 1, 3), 'filter': (types.ListType, 2, 2), 'float': (types.FloatType, 1, 1), 'getattr': (Stack.TYPE_UNKNOWN, 2, 3), 'globals': (types.DictType, 0, 0), 'hasattr': (BOOL, 2, 2), 'hash': (types.IntType, 1, 1), 'hex': (types.StringType, 1, 1), 'id': (types.IntType, 1, 1), 'input': (Stack.TYPE_UNKNOWN, 0, 1), 'int': (types.IntType, 1, 2, ['x']), 'intern': (types.StringType, 1, 1), 'isinstance': (BOOL, 2, 2), 'issubclass': (BOOL, 2, 2), 'len': (types.IntType, 1, 1), 'list': (types.ListType, 1, 1, ['sequence']), 'locals': (types.DictType, 0, 0), 'long': (types.LongType, 1, 2, ['x']), 'map': (types.ListType, 2, None), 'max': (Stack.TYPE_UNKNOWN, 1, None), 'min': (Stack.TYPE_UNKNOWN, 1, None), 'oct': (types.StringType, 1, 1), 'open': (types.FileType, 1, 3, ['name', 'mode', 'buffering']), 'ord': (types.IntType, 1, 1), 'pow': (Stack.TYPE_UNKNOWN, 2, 3), 'range': (types.ListType, 1, 3), 'raw_input': (types.StringType, 0, 1), 'reduce': (Stack.TYPE_UNKNOWN, 2, 3), 'reload': (types.ModuleType, 1, 1), 'repr': (types.StringType, 1, 1), 'round': (types.FloatType, 1, 2), 'setattr': (types.NoneType, 3, 3), 'slice': (types.SliceType, 1, 3), 'str': (types.StringType, 1, 1), 'tuple': (types.TupleType, 1, 1), 'type': (types.TypeType, 1, 1), 'vars': (types.DictType, 0, 1), 'xrange': (types.ListType, 1, 3), } if hasattr(types, 'UnicodeType') : GLOBAL_FUNC_INFO['unichr'] = (types.UnicodeType, 1, 1) GLOBAL_FUNC_INFO['unicode'] = (types.UnicodeType, 1, 3, ['string', 'encoding', 'errors']) if utils.pythonVersion() >= utils.PYTHON_2_2 : GLOBAL_FUNC_INFO['compile'] = (types.CodeType, 3, 5) GLOBAL_FUNC_INFO['dict'] = (types.DictType, 0, 1, ['items']) GLOBAL_FUNC_INFO['file'] = GLOBAL_FUNC_INFO['open'] GLOBAL_FUNC_INFO['float'] = (types.FloatType, 0, 1, ['x']) GLOBAL_FUNC_INFO['int'] = (types.IntType, 0, 2, ['x']) GLOBAL_FUNC_INFO['list'] = (types.ListType, 0, 1, ['sequence']) GLOBAL_FUNC_INFO['long'] = (types.LongType, 0, 2, ['x']) GLOBAL_FUNC_INFO['str'] = (types.StringType, 0, 1, ['object']) # FIXME: type doesn't take 2 args, only 1 or 3 GLOBAL_FUNC_INFO['type'] = (types.TypeType, 1, 3, ['name', 'bases', 'dict']) GLOBAL_FUNC_INFO['tuple'] = (types.TupleType, 0, 1, ['sequence']) GLOBAL_FUNC_INFO['classmethod'] = (types.MethodType, 1, 1) GLOBAL_FUNC_INFO['iter'] = (Stack.TYPE_UNKNOWN, 1, 2) GLOBAL_FUNC_INFO['property'] = (Stack.TYPE_UNKNOWN, 0, 4, ['fget', 'fset', 'fdel', 'doc']) GLOBAL_FUNC_INFO['super'] = (Stack.TYPE_UNKNOWN, 1, 2) GLOBAL_FUNC_INFO['staticmethod'] = (types.MethodType, 1, 1) GLOBAL_FUNC_INFO['unicode'] = (types.UnicodeType, 0, 3, ['string', 'encoding', 'errors']) GLOBAL_FUNC_INFO['bool'] = (BOOL, 1, 1, ['x']) if utils.pythonVersion() >= utils.PYTHON_2_3: GLOBAL_FUNC_INFO['dict'] = (types.DictType, 0, 1, []) if utils.pythonVersion() >= utils.PYTHON_2_5: GLOBAL_FUNC_INFO['max'] = (Stack.TYPE_UNKNOWN, 1, None, ['key']) GLOBAL_FUNC_INFO['min'] = (Stack.TYPE_UNKNOWN, 1, None, ['key']) def tryAddGlobal(name, *args): if globals().has_key(name): GLOBAL_FUNC_INFO[name] = args zipMinArgs = 1 if utils.pythonVersion() >= utils.PYTHON_2_4: zipMinArgs = 0 tryAddGlobal('zip', types.ListType, zipMinArgs, None) tryAddGlobal('enumerate', types.TupleType, 1, 1, ['sequence']) # sum() could also return float/long tryAddGlobal('sum', types.IntType, 1, 2, ['start']) # sorted() and reversed() always return an iterator (FIXME: support iterator) tryAddGlobal('sorted', Stack.TYPE_UNKNOWN, 1, 1) tryAddGlobal('reversed', Stack.TYPE_UNKNOWN, 1, 1) tryAddGlobal('all', BOOL, 1, 1) tryAddGlobal('any', BOOL, 1, 1) _STRING_METHODS = { 'capitalize': (types.StringType, 0, 0), 'center': (types.StringType, 1, 1), 'count': (types.IntType, 1, 1), 'encode': (types.StringType, 0, 2), 'endswith': (BOOL, 1, 3), 'expandtabs': (types.StringType, 0, 1), 'find': (types.IntType, 1, 3), 'index': (types.IntType, 1, 3), 'isalnum': (BOOL, 0, 0), 'isalpha': (BOOL, 0, 0), 'isdigit': (BOOL, 0, 0), 'islower': (BOOL, 0, 0), 'isspace': (BOOL, 0, 0), 'istitle': (BOOL, 0, 0), 'isupper': (BOOL, 0, 0), 'join': (types.StringType, 1, 1), 'ljust': (types.StringType, 1, 1), 'lower': (types.StringType, 0, 0), 'lstrip': (types.StringType, 0, 0), 'replace': (types.StringType, 2, 3), 'rfind': (types.IntType, 1, 3), 'rindex': (types.IntType, 1, 3), 'rjust': (types.StringType, 1, 1), 'rstrip': (types.StringType, 0, 0), 'split': (types.ListType, 0, 2), 'splitlines': (types.ListType, 0, 1), 'startswith': (BOOL, 1, 3), 'strip': (types.StringType, 0, 0), 'swapcase': (types.StringType, 0, 0), 'title': (types.StringType, 0, 0), 'translate': (types.StringType, 1, 2), 'upper': (types.StringType, 0, 0), } if utils.pythonVersion() >= utils.PYTHON_2_2 : _STRING_METHODS['decode'] = (types.UnicodeType, 0, 2) _STRING_METHODS['zfill'] = (types.StringType, 1, 1) if utils.pythonVersion() >= utils.PYTHON_2_4: _STRING_METHODS['rsplit'] = (types.StringType, 0, 2) _STRING_METHODS['center'] = (types.StringType, 1, 2), _STRING_METHODS['ljust'] = (types.StringType, 1, 2), _STRING_METHODS['rjust'] = (types.StringType, 1, 2), BUILTIN_METHODS = { types.DictType : { 'clear': (types.NoneType, 0, 0), 'copy': (types.DictType, 0, 0), 'get': (Stack.TYPE_UNKNOWN, 1, 2), 'has_key': (BOOL, 1, 1), 'items': (types.ListType, 0, 0), 'keys': (types.ListType, 0, 0), 'popitem': (types.TupleType, 0, 0), 'setdefault': (Stack.TYPE_UNKNOWN, 1, 2), 'update': (types.NoneType, 1, 1), 'values': (types.ListType, 0, 0), }, types.ListType : { 'append': (types.NoneType, 1, 1), 'count': (types.IntType, 1, 1), 'extend': (types.NoneType, 1, 1), 'index': (types.IntType, 1, 1), 'insert': (types.NoneType, 2, 2), 'pop': (Stack.TYPE_UNKNOWN, 0, 1), 'remove': (types.NoneType, 1, 1), 'reverse': (types.NoneType, 0, 0), 'sort': (types.NoneType, 0, 1), }, types.FileType : { 'close': (types.NoneType, 0, 0), 'fileno': (types.IntType, 0, 0), 'flush': (types.NoneType, 0, 0), 'isatty': (BOOL, 0, 0), 'read': (types.StringType, 0, 1), 'readinto': (types.NoneType, 1, 1), 'readline': (types.StringType, 0, 1), 'readlines': (types.ListType, 0, 1), 'seek': (types.NoneType, 1, 2), 'tell': (types.IntType, 0, 0), 'truncate': (types.NoneType, 0, 1), 'write': (types.NoneType, 1, 1), 'writelines': (types.NoneType, 1, 1), 'xreadlines': (types.ListType, 0, 0), }, } if utils.pythonVersion() >= utils.PYTHON_2_4: GLOBAL_FUNC_INFO['set'] = (Stack.TYPE_UNKNOWN, 0, 1) GLOBAL_FUNC_INFO['frozenset'] = (Stack.TYPE_UNKNOWN, 0, 1) kwargs = ['cmp', 'key', 'reverse'] BUILTIN_METHODS[types.ListType]['sort'] = (types.NoneType, 0, 3, kwargs) BUILTIN_METHODS[types.DictType]['update'] = (types.NoneType, 1, 1, []) if hasattr({}, 'pop'): BUILTIN_METHODS[types.DictType]['pop'] = (Stack.TYPE_UNKNOWN, 1, 2) if utils.pythonVersion() >= utils.PYTHON_2_5: _STRING_METHODS['partition'] = (types.TupleType, 1, 1) _STRING_METHODS['rpartition'] = (types.TupleType, 1, 1) if utils.pythonVersion() >= utils.PYTHON_2_6: GLOBAL_FUNC_INFO['bin'] = (types.StringType, 1, 1) GLOBAL_FUNC_INFO['bytesarray'] = (bytearray, 0, 1) GLOBAL_FUNC_INFO['bytes'] = (bytes, 0, 1) GLOBAL_FUNC_INFO['format'] = (types.StringType, 1, 2) GLOBAL_FUNC_INFO['next'] = (Stack.TYPE_UNKNOWN, 1, 2) GLOBAL_FUNC_INFO['print'] = (types.NoneType, 0, None, ['sep', 'end', 'file']) def _setupBuiltinMethods() : if utils.pythonVersion() >= utils.PYTHON_2_2 : PY22_DICT_METHODS = { 'iteritems': (types.ListType, 0, 0), 'iterkeys': (types.ListType, 0, 0), 'itervalues': (types.ListType, 0, 0), } BUILTIN_METHODS[types.DictType].update(PY22_DICT_METHODS) try : BUILTIN_METHODS[types.ComplexType] = \ { 'conjugate': (types.ComplexType, 0, 0), } except AttributeError : pass if len(dir('')) > 0 : BUILTIN_METHODS[types.StringType] = _STRING_METHODS try : BUILTIN_METHODS[types.UnicodeType] = _STRING_METHODS except AttributeError : pass _setupBuiltinMethods() MUTABLE_TYPES = (types.ListType, types.DictType, types.InstanceType,) # identifiers which will become a keyword in a future version FUTURE_KEYWORDS = { 'yield': '2.2', 'with': '2.5', 'as': '2.5' } METHODLESS_OBJECTS = { types.NoneType : None, types.IntType : None, types.LongType : None, types.FloatType : None, types.BufferType : None, types.TupleType : None, types.EllipsisType : None, } def _setupBuiltinAttrs() : item = Stack.Item(None, None) BUILTIN_ATTRS[types.MethodType] = dir(item.__init__) del item if utils.pythonVersion() >= utils.PYTHON_2_2 : # FIXME: I'm sure more types need to be added here BUILTIN_ATTRS[types.StringType] = dir(''.__class__) BUILTIN_ATTRS[types.ListType] = dir([].__class__) BUILTIN_ATTRS[types.DictType] = dir({}.__class__) try : import warnings _MSG = "xrange object's 'start', 'stop' and 'step' attributes are deprecated" warnings.filterwarnings('ignore', _MSG) del warnings, _MSG except (ImportError, AssertionError): pass BUILTIN_ATTRS[types.XRangeType] = dir(xrange(0)) try: BUILTIN_ATTRS[types.ComplexType] = dir(complex(0, 1)) except: pass try: BUILTIN_ATTRS[types.UnicodeType] = dir(unicode('')) except: pass try: BUILTIN_ATTRS[types.CodeType] = dir(_setupBuiltinAttrs.func_code) except: pass try: BUILTIN_ATTRS[types.FileType] = dir(sys.__stdin__) except: pass try: raise TypeError except TypeError : try: tb = sys.exc_info()[2] BUILTIN_ATTRS[types.TracebackType] = dir(tb) BUILTIN_ATTRS[types.FrameType] = dir(tb.tb_frame) except: pass tb = None BUILTIN_ATTRS = { types.StringType : dir(''), types.TypeType : dir(type(type)), types.ListType : dir([]), types.DictType : dir({}), types.FunctionType : dir(_setupBuiltinAttrs), types.BuiltinFunctionType : dir(len), types.BuiltinMethodType : dir([].append), types.ClassType : dir(Stack.Item), types.UnboundMethodType : dir(Stack.Item.__init__), types.LambdaType : dir(lambda: None), types.SliceType : dir(slice(0)), } # have to setup the rest this way to support different versions of Python _setupBuiltinAttrs() PENDING_DEPRECATED_MODULES = { 'string': None, 'types': None, } DEPRECATED_MODULES = { 'FCNTL': 'fcntl', 'gopherlib': None, 'macfs': 'Carbon.File or Carbon.Folder', 'posixfile': 'fcntl', 'pre': None, 'regsub': 're', 'statcache': 'os.stat()', 'stringold': None, 'tzparse': None, 'TERMIOS': 'termios', 'whrandom':'random', 'xmllib': 'xml.sax', # C Modules 'mpz': None, 'pcre': None, 'pypcre': None, 'rgbimg': None, 'strop': None, 'xreadlines': 'file', } DEPRECATED_ATTRS = { 'array.read': None, 'array.write': None, 'operator.isCallable': None, 'operator.sequenceIncludes': None, 'pty.master_open': None, 'pty.slave_open': None, 'random.stdgamma': 'random.gammavariate', 'rfc822.AddrlistClass': 'rfc822.AddressList', 'string.atof': None, 'string.atoi': None, 'string.atol': None, 'string.zfill': None, 'sys.exc_traceback': None, 'sys.exit_thread': None, 'tempfile.mktemp': None, 'tempfile.template': None, } # FIXME: can't check these right now, maybe later DEPRECATED_METHODS = { 'email.Message.get_type': 'email.Message.get_content_type', 'email.Message.get_subtype': 'email.Message.get_content_subtype', 'email.Message.get_main_type': 'email.Message.get_content_maintype', 'htmllib.HTMLParser.do_nextid': None, 'pstats.Stats.ignore': None, 'random.Random.cunifvariate': None, 'random.Random.stdgamma': 'Random.gammavariate', } _OS_AND_POSIX_FUNCS = { 'tempnam': None, 'tmpnam': None } SECURITY_FUNCS = { 'os' : _OS_AND_POSIX_FUNCS, 'posix': _OS_AND_POSIX_FUNCS } SPECIAL_METHODS = { '__call__': None, # any number > 1 '__cmp__': 2, '__coerce__': 2, '__contains__': 2, '__del__': 1, '__hash__': 1, '__iter__': 1, '__len__': 1, '__new__': None, # new-style class constructor '__nonzero__': 1, '__hex__': 1, '__oct__': 1, '__repr__': 1, '__str__': 1, '__invert__': 1, '__neg__': 1, '__pos__': 1, '__abs__': 1, '__complex__': 1, '__int__': 1, '__long__': 1, '__float__': 1, '__unicode__': 1, '__eq__': 2, '__ne__': 2, '__ge__': 2, '__gt__': 2, '__le__': 2, '__lt__': 2, '__getattribute__': 2, # only in new-style classes '__get__': 3, '__set__': 3, '__delete__': 2, '__getattr__': 2, '__setattr__': 3, '__delattr__': 2, '__getitem__': 2, '__setitem__': 3, '__delitem__': 2, '__getslice__': 3, '__setslice__': 4, '__delslice__': 3, # getslice is deprecated '__add__': 2, '__radd__': 2, '__iadd__': 2, '__sub__': 2, '__rsub__': 2, '__isub__': 2, '__mul__': 2, '__rmul__': 2, '__imul__': 2, '__div__': 2, '__rdiv__': 2, '__idiv__': 2, # __pow__: 2 or 3 __ipow__: 2 or 3 '__pow__': 3, '__rpow__': 2, '__ipow__': 3, '__truediv__': 2, '__rtruediv__': 2, '__itruediv__': 2, '__floordiv__': 2, '__rfloordiv__': 2, '__ifloordiv__': 2, '__mod__': 2, '__rmod__': 2, '__imod__': 2, '__divmod__': 2, '__rdivmod__': 2, # no inplace op for divmod() '__lshift__': 2, '__rlshift__': 2, '__ilshift__': 2, '__rshift__': 2, '__rrshift__': 2, '__irshift__': 2, '__and__': 2, '__rand__': 2, '__iand__': 2, '__xor__': 2, '__rxor__': 2, '__ixor__': 2, '__or__': 2, '__ror__': 2, '__ior__': 2, # these are related to pickling '__getstate__': 1, '__setstate__': 2, '__copy__': 1, '__deepcopy__': 2, '__getinitargs__': 1, '__getnewargs__': 1, '__reduce__': 1, '__reduce_ex__': 2, } if utils.pythonVersion() >= utils.PYTHON_2_5: SPECIAL_METHODS['__enter__'] = 1 SPECIAL_METHODS['__exit__'] = 4 NEW_STYLE_CLASS_METHODS = ['__getattribute__', '__set__', '__get__', '__delete__'] pychecker-0.8.19/pychecker/checker.py0000755000076400007640000003144711512036115017110 0ustar thomasthomas#!/usr/bin/env python # -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2004, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Check python source code files for possible errors and print warnings Contact Info: http://pychecker.sourceforge.net/ pychecker-list@lists.sourceforge.net """ import string import types import sys import imp import os import glob # see __init__.py for meaning, this must match the version there LOCAL_MAIN_VERSION = 3 def setupNamespace(path) : # remove pychecker if it's the first component, it needs to be last if sys.path[0][-9:] == 'pychecker' : del sys.path[0] # make sure pychecker is last in path, so we can import checker_path = os.path.dirname(os.path.dirname(path)) if checker_path not in sys.path : sys.path.append(checker_path) def setupSysPathForDevelopment(): import pychecker this_module = sys.modules[__name__] # in 2.2 and older, this_module might not have __file__ at all if not hasattr(this_module, '__file__'): return this_path = os.path.normpath(os.path.dirname(this_module.__file__)) pkg_path = os.path.normpath(os.path.dirname(pychecker.__file__)) if pkg_path != this_path: # pychecker was probably found in site-packages, insert this # directory before the other one so we can do development and run # our local version and not the version from site-packages. pkg_dir = os.path.dirname(pkg_path) i = 0 for p in sys.path: if os.path.normpath(p) == pkg_dir: sys.path.insert(i-1, os.path.dirname(this_path)) break i = i + 1 del sys.modules['pychecker'] if __name__ == '__main__' : setupNamespace(sys.argv[0]) setupSysPathForDevelopment() from pychecker import utils from pychecker import printer from pychecker import warn from pychecker import OP from pychecker import Config from pychecker import function from pychecker import msgs from pychecker import pcmodules from pychecker.Warning import Warning _cfg = None _VERSION_MISMATCH_ERROR = ''' There seem to be two versions of PyChecker being used. One is probably in python/site-packages, the other in a local directory. If you want to run the local version, you must remove the version from site-packages. Or you can install the current version by doing python setup.py install. ''' def cfg() : return utils.cfg() def _flattenList(list) : "Returns a list which contains no lists" new_list = [] for element in list : if type(element) == types.ListType : new_list.extend(_flattenList(element)) else : new_list.append(element) return new_list def getModules(arg_list) : """ arg_list is a list of arguments to pychecker; arguments can represent a module name, a filename, or a wildcard file specification. Returns a list of (module name, dirPath) that can be imported, where dirPath is the on-disk path to the module name for that argument. dirPath can be None (in case the given argument is an actual module). """ new_arguments = [] for arg in arg_list : # is this a wildcard filespec? (necessary for windows) if '*' in arg or '?' in arg or '[' in arg : arg = glob.glob(arg) new_arguments.append(arg) PY_SUFFIXES = ['.py'] PY_SUFFIX_LENS = [3] if _cfg.quixote: PY_SUFFIXES.append('.ptl') PY_SUFFIX_LENS.append(4) modules = [] for arg in _flattenList(new_arguments) : # if arg is an actual module, return None for the directory arg_dir = None # is it a .py file? for suf, suflen in zip(PY_SUFFIXES, PY_SUFFIX_LENS): if len(arg) > suflen and arg[-suflen:] == suf: arg_dir = os.path.dirname(arg) if arg_dir and not os.path.exists(arg) : print 'File or pathname element does not exist: "%s"' % arg continue module_name = os.path.basename(arg)[:-suflen] arg = module_name modules.append((arg, arg_dir)) return modules def getAllModules(): """ Returns a list of all modules that should be checked. @rtype: list of L{pcmodules.PyCheckerModule} """ modules = [] for module in pcmodules.getPCModules(): if module.check: modules.append(module) return modules _BUILTIN_MODULE_ATTRS = { 'sys': [ 'ps1', 'ps2', 'tracebacklimit', 'exc_type', 'exc_value', 'exc_traceback', 'last_type', 'last_value', 'last_traceback', ], } def fixupBuiltinModules(needs_init=0): for moduleName in sys.builtin_module_names : # Skip sys since it will reset sys.stdout in IDLE and cause # stdout to go to the real console rather than the IDLE console. # FIXME: this breaks test42 # if moduleName == 'sys': # continue if needs_init: _ = pcmodules.PyCheckerModule(moduleName, 0) # builtin modules don't have a moduleDir module = pcmodules.getPCModule(moduleName) if module is not None : try : m = imp.init_builtin(moduleName) except ImportError : pass else : extra_attrs = _BUILTIN_MODULE_ATTRS.get(moduleName, []) module.attributes = [ '__dict__' ] + dir(m) + extra_attrs def _printWarnings(warnings, stream=None): if stream is None: stream = sys.stdout warnings.sort() lastWarning = None for warning in warnings : if lastWarning is not None: # ignore duplicate warnings if cmp(lastWarning, warning) == 0: continue # print blank line between files if lastWarning.file != warning.file: stream.write("\n") lastWarning = warning warning.output(stream, removeSysPath=True) class NullModule: def __getattr__(self, unused_attr): return None def install_ignore__import__(): _orig__import__ = None def __import__(name, globals=None, locals=None, fromlist=None): if globals is None: globals = {} if locals is None: locals = {} if fromlist is None: fromlist = () try: pymodule = _orig__import__(name, globals, locals, fromlist) except ImportError: pymodule = NullModule() if not _cfg.quiet: modname = '.'.join((name,) + fromlist) sys.stderr.write("Can't import module: %s, ignoring.\n" % modname) return pymodule # keep the orig __import__ around so we can call it import __builtin__ _orig__import__ = __builtin__.__import__ __builtin__.__import__ = __import__ def processFiles(files, cfg=None, pre_process_cb=None): """ @type files: list of str @type cfg: L{Config.Config} @param pre_process_cb: callable notifying of module name, filename @type pre_process_cb: callable taking (str, str) """ warnings = [] # insert this here, so we find files in the local dir before std library if sys.path[0] != '' : sys.path.insert(0, '') # ensure we have a config object, it's necessary global _cfg if cfg is not None: _cfg = cfg elif _cfg is None: _cfg = Config.Config() if _cfg.ignoreImportErrors: install_ignore__import__() utils.initConfig(_cfg) utils.debug('Processing %d files' % len(files)) for file, (moduleName, moduleDir) in zip(files, getModules(files)): if callable(pre_process_cb): pre_process_cb("module %s (%s)" % (moduleName, file)) # create and load the PyCheckerModule, tricking sys.path temporarily oldsyspath = sys.path[:] sys.path.insert(0, moduleDir) pcmodule = pcmodules.PyCheckerModule(moduleName, moduleDir=moduleDir) loaded = pcmodule.load() sys.path = oldsyspath if not loaded: w = Warning(pcmodule.filename(), 1, msgs.Internal("NOT PROCESSED UNABLE TO IMPORT")) warnings.append(w) utils.debug('Processed %d files' % len(files)) utils.popConfig() return warnings # only used by TKInter options.py def getWarnings(files, cfg = None, suppressions = None): warnings = processFiles(files, cfg) fixupBuiltinModules() return warnings + warn.find(getAllModules(), _cfg, suppressions) def _print_processing(name) : if not _cfg.quiet : sys.stderr.write("Processing %s...\n" % name) def main(argv) : __pychecker__ = 'no-miximport' import pychecker if LOCAL_MAIN_VERSION != pychecker.MAIN_MODULE_VERSION : sys.stderr.write(_VERSION_MISMATCH_ERROR) sys.exit(100) # remove empty arguments argv = filter(None, argv) # if the first arg starts with an @, read options from the file # after the @ (this is mostly for windows) if len(argv) >= 2 and argv[1][0] == '@': # read data from the file command_file = argv[1][1:] try: f = open(command_file, 'r') command_line = f.read() f.close() except IOError, err: sys.stderr.write("Unable to read commands from file: %s\n %s\n" % \ (command_file, err)) sys.exit(101) # convert to an argv list, keeping argv[0] and the files to process argv = argv[:1] + string.split(command_line) + argv[2:] global _cfg _cfg, files, suppressions = Config.setupFromArgs(argv[1:]) utils.initConfig(_cfg) if not files : return 0 # Now that we've got the args, update the list of evil C objects for evil_doer in _cfg.evil: pcmodules.EVIL_C_OBJECTS[evil_doer] = None # insert this here, so we find files in the local dir before std library sys.path.insert(0, '') utils.debug('main: Finding import warnings') importWarnings = processFiles(files, _cfg, _print_processing) utils.debug('main: Found %d import warnings' % len(importWarnings)) fixupBuiltinModules() if _cfg.printParse : for module in getAllModules() : printer.module(module) utils.debug('main: Finding warnings') # suppressions is a tuple of suppressions, suppressionRegexs dicts warnings = warn.find(getAllModules(), _cfg, suppressions) utils.debug('main: Found %d warnings' % len(warnings)) if not _cfg.quiet : print "\nWarnings...\n" if warnings or importWarnings : _printWarnings(importWarnings + warnings) return 1 if not _cfg.quiet : print "None" return 0 # FIXME: this is a nasty side effect for import checker if __name__ == '__main__' : try : sys.exit(main(sys.argv)) except Config.UsageError : sys.exit(127) else : _orig__import__ = None _suppressions = None _warnings_cache = {} def _get_unique_warnings(warnings): for i in range(len(warnings)-1, -1, -1): w = warnings[i].format() if _warnings_cache.has_key(w): del warnings[i] else: _warnings_cache[w] = 1 return warnings def __import__(name, globals=None, locals=None, fromlist=None): if globals is None: globals = {} if locals is None: locals = {} if fromlist is None: fromlist = [] check = not sys.modules.has_key(name) and name[:10] != 'pychecker.' pymodule = _orig__import__(name, globals, locals, fromlist) if check : try : # FIXME: can we find a good moduleDir ? module = pcmodules.PyCheckerModule(pymodule.__name__) if module.initModule(pymodule): warnings = warn.find([module], _cfg, _suppressions) _printWarnings(_get_unique_warnings(warnings)) else : print 'Unable to load module', pymodule.__name__ except Exception: name = getattr(pymodule, '__name__', utils.safestr(pymodule)) # FIXME: can we use it here ? utils.importError(name) return pymodule def _init() : global _cfg, _suppressions, _orig__import__ args = string.split(os.environ.get('PYCHECKER', '')) _cfg, files, _suppressions = Config.setupFromArgs(args) utils.initConfig(_cfg) fixupBuiltinModules(1) # keep the orig __import__ around so we can call it import __builtin__ _orig__import__ = __builtin__.__import__ __builtin__.__import__ = __import__ if not os.environ.get('PYCHECKER_DISABLED') : _init() pychecker-0.8.19/pychecker/printer.py0000644000076400007640000000300311511444550017154 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001, MetaSlash Inc. All rights reserved. "Helper functions for printing out info about objects" from pychecker import utils def printFunction(spaces, prefix, func, className = None) : params = '' argcount = func.func_code.co_argcount defaultArgStart = argcount if func.func_defaults != None : defaultArgStart = argcount - len(func.func_defaults) for i in range(0, argcount) : arg = func.func_code.co_varnames[i] if i >= defaultArgStart : arg = arg + " = %s" % utils.safestr(func.func_defaults[i - defaultArgStart]) params = params + "%s, " % arg params = "(%s)" % params[:-2] if className == None : className = "" else : className = className + "." print "%s%s%s%s%s" % (spaces, prefix, className, func.func_name, params) def module(module) : print "Module: ", module.moduleName if module.module == None : return print " Imports: ", module.modules.keys() print " Variables:", module.variables.keys() print "" for function in module.functions.values() : printFunction(" ", "Function: ", function.function) print "" for c in module.classes.values() : for method in c.methods.values() : if method != None : printFunction(" ", "", method.function, c.name) print "" def attrs(object) : for attr in dir(object) : print " %s: %s" % (attr, `getattr(object, attr)`) pychecker-0.8.19/pychecker/CodeChecks.py0000644000076400007640000026536611512036115017505 0ustar thomasthomas# -*- Mode: Python; test-case-name: test.test_pychecker_CodeChecks -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2006, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Find warnings in byte code from Python source files. """ # For documentation about dispatcher arguments, look for # dispatcher functions for operands import keyword import string import types from pychecker import msgs from pychecker import utils from pychecker import Warning from pychecker import OP from pychecker import Stack from pychecker import python __pychecker__ = 'no-argsused' def cfg() : return utils.cfg() def getFunctionArgErr(funcName, argCount, minArgs, maxArgs): """ Check that the number of arguments given is correct according to the given minArgs and maxArgs. @type funcName: str @type argCount: int @ivar minArgs: the minimum number of arguments that should be passed to this function @type minArgs: int @ivar minArgs: the maximum number of arguments that should be passed to this function, or None in case of *args/unlimited @type maxArgs: int or None @rtype: L{msgs.WarningClass} or None """ err = None if maxArgs == None: if argCount < minArgs : err = msgs.INVALID_ARG_COUNT2 % (funcName, argCount, minArgs) elif argCount < minArgs or argCount > maxArgs: if minArgs == maxArgs: err = msgs.INVALID_ARG_COUNT1 % (funcName, argCount, minArgs) else: err = msgs.INVALID_ARG_COUNT3 % (funcName, argCount, minArgs, maxArgs) return err def _checkFunctionArgCount(code, func_name, argCount, minArgs, maxArgs, objectReference=0): """ @param objectReference: whether the first argument references self @type objectReference: int (used as bool) """ # there is an implied argument for object creation and self.xxx() # FIXME: this is where test44 fails if objectReference: minArgs = minArgs - 1 if maxArgs is not None: maxArgs = maxArgs - 1 err = getFunctionArgErr(func_name, argCount, minArgs, maxArgs) if err: code.addWarning(err) def _checkFunctionArgs(code, func, objectReference, argCount, kwArgs, checkArgCount=1): """ @param code: The code block containing the invocation of the function to be checked @type code: L{Code} @param func: The function to check the invocation against @type func: L{function.Function} @param objectReference: whether the first argument references self @type objectReference: int (used as bool) @param checkArgCount: whether the first argument references self @type checkArgCount: int (used as bool) """ func_name = func.function.func_code.co_name if kwArgs : args_len = func.function.func_code.co_argcount arg_names = func.function.func_code.co_varnames[argCount:args_len] if argCount < args_len and kwArgs[0] in arg_names: if cfg().namedArgs : code.addWarning(msgs.FUNC_USES_NAMED_ARGS % func_name) # convert the named args into regular params, and really check while argCount < args_len and kwArgs and kwArgs[0] in arg_names: argCount = argCount + 1 kwArgs = kwArgs[1:] _checkFunctionArgs(code, func, objectReference, argCount, kwArgs, checkArgCount) return if not func.supportsKW : code.addWarning(msgs.FUNC_DOESNT_SUPPORT_KW % func_name) if checkArgCount: _checkFunctionArgCount(code, func_name, argCount, func.minArgs, func.maxArgs, objectReference) def _getReferenceFromModule(module, identifier): """ Looks up the given identifier in the module. If it is a function, returns (function, None, 0) If it is a class instantiation, returns (__init__ function, class, 1) @type module: L{pychecker.checker.PyCheckerModule} @type identifier: str @returns: a triple of: - the function object (which can be the __init__ for the class) - the class, if the identifier references a class - 0 if it was a function, 1 if it was a class @rtype: triple of (function, class or None, int (as bool)) """ # if the identifier is in the list of module's functions, return # the function, with no class, and method 0 func = module.functions.get(identifier, None) if func is not None: return func, None, 0 # now look it up as a class instantiation c = module.classes.get(identifier, None) if c is not None : func = c.methods.get(utils.INIT, None) return func, c, 1 # not found as either return None, None, 0 def _getFunction(module, stackValue): # FIXME: it's not clear to me if the above method really returns # whether the stack value is a constructor """ Return (function, class, is_a_method) from the stack value @type module: L{pychecker.checker.PyCheckerModule} @type stackValue: L{Stack.Item} @rtype: tuple of (function or None, class or None, int (as bool)) """ identifier = stackValue.data if type(identifier) == types.StringType: return _getReferenceFromModule(module, identifier) # find the module this references i, maxLen = 0, len(identifier) while i < maxLen : name = utils.safestr(identifier[i]) if module.classes.has_key(name) or module.functions.has_key(name) : break refModule = module.modules.get(name, None) if refModule is not None : module = refModule else : return None, None, 0 i = i + 1 # if we got to the end, there is only modules, nothing we can do # we also can't handle if there is more than 2 items left if i >= maxLen or (i+2) < maxLen : return None, None, 0 if (i+1) == maxLen : return _getReferenceFromModule(module, identifier[-1]) # we can't handle self.x.y if (i+2) == maxLen and identifier[0] == cfg().methodArgName: return None, None, 0 c = module.classes.get(identifier[-2], None) if c is None : return None, None, 0 return c.methods.get(identifier[-1], None), c, 0 def _validateKwArgs(code, info, func_name, kwArgs): if len(info) < 4: code.addWarning(msgs.FUNC_DOESNT_SUPPORT_KW % func_name) elif not info[3]: return try: # info could be from a builtin method which means that # info[3] is not a list. dummy = info[3][0] except IndexError: return for arg in kwArgs: if arg not in info[3]: code.addWarning(msgs.FUNC_DOESNT_SUPPORT_KW_ARG % (func_name, arg)) def _checkBuiltin(code, loadValue, argCount, kwArgs, check_arg_count = 1) : returnValue = Stack.makeFuncReturnValue(loadValue, argCount) func_name = loadValue.data if loadValue.type == Stack.TYPE_GLOBAL : info = python.GLOBAL_FUNC_INFO.get(func_name, None) if info is not None : if func_name == 'input' and cfg().usesInput: code.addWarning(msgs.USES_INPUT) if cfg().constAttr and \ ((func_name == 'setattr' and argCount >= 2) or (func_name == 'getattr' and argCount == 2)): arg2 = code.stack[-argCount + 1] if arg2.const and not keyword.iskeyword(arg2.data): # lambda with setattr and const is a common way of setting # attributes, so allow it if code.func.function.func_name != '': code.addWarning(msgs.USES_CONST_ATTR % func_name) if kwArgs: _validateKwArgs(code, info, func_name, kwArgs) elif check_arg_count : _checkFunctionArgCount(code, func_name, argCount, info[1], info[2]) returnValue = Stack.Item(returnValue.data, info[0]) returnValue.setStringType(info[0]) elif type(func_name) == types.TupleType and len(func_name) <= 2 : objType = code.typeMap.get(utils.safestr(func_name[0]), []) if types.ListType in objType : try : if func_name[1] == 'append' and argCount > 1 : code.addWarning(msgs.LIST_APPEND_ARGS % func_name[0]) check_arg_count = 0 except AttributeError : # FIXME: why do we need to catch AttributeError??? pass if len(objType) == 1 : # if it's a builtin, check method builtinType = python.BUILTIN_METHODS.get(objType[0]) if builtinType is not None : methodInfo = builtinType.get(func_name[1]) # set func properly if kwArgs : _validateKwArgs(code, methodInfo, func_name[1], kwArgs) elif methodInfo : returnValue = Stack.Item(func_name[1], methodInfo[0]) returnValue.setStringType(methodInfo[0]) if check_arg_count and methodInfo is not None : _checkFunctionArgCount(code, func_name[1], argCount, methodInfo[1], methodInfo[2]) return returnValue _IMMUTABLE_LIST_METHODS = ('count', 'index',) _IMMUTABLE_DICT_METHODS = ('copy', 'get', 'has_key', 'items', 'keys', 'values', 'iteritems', 'iterkeys', 'itervalues') def _checkModifyDefaultArg(code, objectName, methodName=None) : try : value = code.func.defaultValue(objectName) objectType = type(value) if objectType in python.MUTABLE_TYPES : if objectType == types.DictType and \ methodName in _IMMUTABLE_DICT_METHODS : return if objectType == types.ListType and \ methodName in _IMMUTABLE_LIST_METHODS : return code.addWarning(msgs.MODIFYING_DEFAULT_ARG % objectName) except ValueError : pass def _isexception(object) : # FIXME: i have no idea why this function is necessary # it seems that the issubclass() should work, but it doesn't always if hasattr(object, 'type'): if object.type == types.TupleType: # if we have a tuple, we can't check the contents (not enough info) ## for item in object.value: ## if not _isexception(item): ## return 0 return 1 try: # try/except is necessary for globals like NotImplemented if issubclass(object, Exception) : return 1 # Python 2.5 added a BaseException to the hierarchy. That's # really what we need to check if it exists. if utils.pythonVersion() >= utils.PYTHON_2_5: if issubclass(object, BaseException): return 1 except TypeError: return 0 for c in object.__bases__ : if utils.startswith(utils.safestr(c), 'exceptions.') : return 1 if len(c.__bases__) > 0 and _isexception(c) : return 1 return 0 def _checkStringFind(code, loadValue): if len(loadValue.data) == 2 and loadValue.data[1] == 'find': try: if types.StringType in code.typeMap.get(loadValue.data[0], []): op = code.nextOpInfo()[0] if OP.IS_CONDITIONAL_JUMP(op) or OP.IS_NOT(op): code.addWarning(msgs.BAD_STRING_FIND) except TypeError: # we don't care if loadValue.data[0] is not hashable pass def _checkAbstract(refClass, code, name): name_list = refClass.isAbstract() if name_list: name_list.sort() names = string.join(name_list, ", ") code.addWarning(msgs.METHODS_NEED_OVERRIDE % (names, name)) _SEQUENCE_TYPES = (types.TupleType, types.ListType, types.StringType) try: _SEQUENCE_TYPES = _SEQUENCE_TYPES + (types.UnicodeType,) except AttributeError: pass # FIXME: this is not complete. errors will be caught only sometimes, # depending on the order the functions/methods are processed # in the dict. Need to be able to run through all functions # twice, but because the code sucks, this is not possible. def _checkReturnValueUse(code, func): if func.returnValues is None: return err = None opInfo = code.nextOpInfo() if func.returnsNoValue(): # make sure we really know how to check for all the return types for rv in func.returnValues: if rv[1].type in _UNCHECKABLE_STACK_TYPES: return if not OP.POP_TOP(opInfo[0]): err = msgs.USING_NONE_RETURN_VALUE % utils.safestr(func) elif OP.UNPACK_SEQUENCE(opInfo[0]): # verify unpacking into proper # of vars varCount = opInfo[1] stackRV = func.returnValues[0][1] returnType = stackRV.getType({}) funcCount = stackRV.length if returnType in _SEQUENCE_TYPES: if varCount != funcCount and funcCount > 0: err = msgs.WRONG_UNPACK_FUNCTION % (utils.safestr(func), funcCount, varCount) elif returnType not in _UNCHECKABLE_STACK_TYPES: err = msgs.UNPACK_NON_SEQUENCE % (utils.safestr(func), _getTypeStr(returnType)) if err: code.addWarning(err) def _handleFunctionCall(codeSource, code, argCount, indexOffset = 0, check_arg_count = 1) : 'Checks for warnings, returns function called (may be None)' if not code.stack : return kwArgCount = argCount >> utils.VAR_ARGS_BITS argCount = argCount & utils.MAX_ARGS_MASK # function call on stack is before the args, and keyword args funcIndex = argCount + 2 * kwArgCount + 1 + indexOffset if funcIndex > len(code.stack) : funcIndex = 0 # to find on stack, we have to look backwards from top of stack (end) funcIndex = -funcIndex # store the keyword names/keys to check if using named arguments kwArgs = [] if kwArgCount > 0 : # loop backwards by 2 (keyword, value) in stack to find keyword args for i in range(-2 - indexOffset, (-2 * kwArgCount - 1), -2) : kwArgs.append(code.stack[i].data) kwArgs.reverse() loadValue = code.stack[funcIndex] funcName = loadValue.getName() returnValue = Stack.makeFuncReturnValue(loadValue, argCount) if loadValue.isMethodCall(codeSource.classObject, cfg().methodArgName): methodName = loadValue.data[1] try : m = codeSource.classObject.methods[methodName] if m != None : objRef = not m.isStaticMethod() _checkFunctionArgs(code, m, objRef, argCount, kwArgs, check_arg_count) except KeyError : sattr = codeSource.classObject.statics.get(methodName) if sattr is not None : funcName = sattr.getName() if sattr is None and cfg().callingAttribute : code.addWarning(msgs.INVALID_METHOD % methodName) elif loadValue.type in (Stack.TYPE_ATTRIBUTE, Stack.TYPE_GLOBAL) and \ type(loadValue.data) in (types.StringType, types.TupleType) : # apply(func, (args)), can't check # of args, so just return func if loadValue.data == 'apply' : loadValue = code.stack[funcIndex+1] funcName = loadValue.getName() else : if cfg().modifyDefaultValue and \ type(loadValue.data) == types.TupleType : _checkModifyDefaultArg(code, loadValue.data[0], loadValue.data[1]) func, refClass, method = _getFunction(codeSource.module, loadValue) if func == None and type(loadValue.data) == types.TupleType and \ len(loadValue.data) == 2 : # looks like we are making a method call data = loadValue.data if type(data[0]) == types.StringType : # do we know the type of the local variable? varType = code.typeMap.get(data[0]) if varType is not None and len(varType) == 1 : if hasattr(varType[0], 'methods') : # it's a class & we know the type, get the method func = varType[0].methods.get(data[1]) if func is not None : method = 1 if cfg().abstractClasses and refClass and method: _checkAbstract(refClass, code, funcName) if cfg().stringFind: _checkStringFind(code, loadValue) if func != None : if refClass and func.isClassMethod(): argCount = argCount + 1 _checkFunctionArgs(code, func, method, argCount, kwArgs, check_arg_count) # if this isn't a c'tor, we should check if not (refClass and method) and cfg().checkReturnValues: _checkReturnValueUse(code, func) if refClass : if method : # c'tor, return the class as the type returnValue = Stack.Item(loadValue, refClass) elif func.isClassMethod(): # FIXME: do anything here? pass elif argCount > 0 and cfg().methodArgName and \ not func.isStaticMethod() and \ code.stack[funcIndex].type == Stack.TYPE_ATTRIBUTE and \ code.stack[funcIndex+1].data != cfg().methodArgName: e = msgs.SELF_NOT_FIRST_ARG % (cfg().methodArgName, '') code.addWarning(e) elif refClass and method : returnValue = Stack.Item(loadValue, refClass) if (argCount > 0 or len(kwArgs) > 0) and \ not refClass.ignoreAttrs and \ not refClass.methods.has_key(utils.INIT) and \ not _isexception(refClass.classObject) : code.addWarning(msgs.NO_CTOR_ARGS) else : returnValue = _checkBuiltin(code, loadValue, argCount, kwArgs, check_arg_count) if returnValue.type is types.NoneType and \ not OP.POP_TOP(code.nextOpInfo()[0]) : name = utils.safestr(loadValue.data) if type(loadValue.data) == types.TupleType : name = string.join(loadValue.data, '.') # lambda with setattr is a common way of setting # attributes, so allow it if name != 'setattr' \ or code.func.function.func_name != '': code.addWarning(msgs.USING_NONE_RETURN_VALUE % name) code.stack = code.stack[:funcIndex] + [ returnValue ] code.functionsCalled[funcName] = loadValue def _classHasAttribute(c, attr) : return (c.methods.has_key(attr) or c.members.has_key(attr) or hasattr(c.classObject, attr)) def _checkClassAttribute(attr, c, code) : if _classHasAttribute(c, attr) : try : del c.memberRefs[attr] except KeyError : pass elif cfg().classAttrExists : if attr not in cfg().missingAttrs: code.addWarning(msgs.INVALID_CLASS_ATTR % attr) def _checkModuleAttribute(attr, module, code, ref) : try: if attr not in module.modules[ref].attributes and \ not utils.endswith(ref, '.' + attr) : code.addWarning(msgs.INVALID_MODULE_ATTR % attr) except (KeyError, TypeError): # if ref isn't found, or ref isn't even hashable, we don't care # we may not know, or ref could be something funky [e for e].method() pass try: _checkClassAttribute(attr, module.classes[ref], code) except (KeyError, TypeError): # if ref isn't found, or ref isn't even hashable, we don't care # we may not know, or ref could be something funky [e for e].method() pass def _getGlobalName(name, func) : # get the right name of global refs (for from XXX import YYY) opModule = func.function.func_globals.get(name) try : if opModule and isinstance(opModule, types.ModuleType) : name = opModule.__name__ except : # we have to do this in case the class raises an access exception # due to overriding __special__() methods pass return name def _checkNoEffect(code, ignoreStmtWithNoEffect=0): if (not ignoreStmtWithNoEffect and OP.POP_TOP(code.nextOpInfo()[0]) and cfg().noEffect): code.addWarning(msgs.POSSIBLE_STMT_WITH_NO_EFFECT) def _makeConstant(code, index, factoryFunction) : """ Build a constant on the stack ((), [], or {}) @param index: how many items the constant will consume from the stack @param factoryFunction: the factory function from L{pychecker.Stack} to use when creating the actual constant """ if index > 0 : # replace the bottom of the stack with the result of applying the # factory function to it code.stack[-index:] = [ factoryFunction(code.stack[-index:]) ] _checkNoEffect(code) else : code.pushStack(factoryFunction()) def _hasGlobal(operand, module, func, main) : # returns whether we have a global with the operand's name, because of: # - being in the function's global list # - main being set to 1 # - being in the module's list of global operands # - being a builtin return (func.function.func_globals.has_key(operand) or main or module.moduleLineNums.has_key(operand) or __builtins__.has_key(operand)) def _checkGlobal(operand, module, func, code, err, main = 0) : # if the given operand is not in the global namespace, add the given err if not _hasGlobal(operand, module, func, main) : code.addWarning(err % operand) if not cfg().reportAllGlobals : func.function.func_globals[operand] = operand def _handleComparison(stack, operand) : num_ops = 2 if operand == 'exception match': num_ops = 1 si = min(len(stack), num_ops) compareValues = stack[-si:] for _ in range(si, 2) : compareValues.append(Stack.Item(None, None)) stack[-si:] = [ Stack.makeComparison(compareValues, operand) ] return compareValues # FIXME: this code needs to be vetted; star imports should actually # import all the names from the module and put them in the new module # namespace so we detect collisions def _handleImport(code, operand, module, main, fromName): """ @param code: the code block in which the import is happening @type code: L{Code} @param operand: what is being imported (the module name in case of normal import, or the object names in the module in case of from ... import names/*) @type operand: str @param module: the module in which the import is happening @type module: L{pychecker.checker.PyCheckerModule} @param main: whether the import is in the source's global namespace (__main__) @type main: int (treated as bool) @param fromName: the name that's being imported @type fromName: str """ assert type(operand) is str # FIXME: this function should be refactored/cleaned up key = operand tmpOperand = tmpFromName = operand if fromName is not None : tmpOperand = tmpFromName = fromName key = (fromName, operand) if cfg().deprecated: try: undeprecated = python.DEPRECATED_MODULES[tmpFromName] except KeyError: pass else: msg = msgs.USING_DEPRECATED_MODULE % tmpFromName if undeprecated: msg.data = msg.data + msgs.USE_INSTEAD % undeprecated code.addWarning(msg) if cfg().reimportSelf and tmpOperand == module.module.__name__ : code.addWarning(msgs.IMPORT_SELF % tmpOperand) modline1 = module.moduleLineNums.get(tmpOperand, None) modline2 = module.moduleLineNums.get((tmpFromName, '*'), None) key2 = (tmpFromName,) if fromName is not None and operand != '*' : key2 = (tmpFromName, operand) modline3 = module.moduleLineNums.get(key2, None) if modline1 is not None or modline2 is not None or modline3 is not None : err = None if fromName is None : if modline1 is not None : err = msgs.MODULE_IMPORTED_AGAIN % operand elif cfg().mixImport : err = msgs.MIX_IMPORT_AND_FROM_IMPORT % tmpFromName else : if modline3 is not None and operand != '*' : err = 'from %s import %s' % (tmpFromName, operand) err = msgs.MODULE_MEMBER_IMPORTED_AGAIN % err elif modline1 is not None : if cfg().mixImport and code.getLineNum() != modline1[1] : err = msgs.MIX_IMPORT_AND_FROM_IMPORT % tmpFromName else : err = msgs.MODULE_MEMBER_ALSO_STAR_IMPORTED % fromName # filter out warnings when files are different (ie, from X import ...) if err is not None and cfg().moduleImportErrors : codeBytes = module.mainCode if codeBytes is None or \ codeBytes.function.func_code.co_filename == code.func_code.co_filename : code.addWarning(err) if main : fileline = (code.func_code.co_filename, code.getLineNum()) module.moduleLineNums[key] = fileline if fromName is not None : module.moduleLineNums[(fromName,)] = fileline def _handleImportFrom(code, operand, module, main): """ @type code: L{Code} @param operand: what is being imported; can be * for star imports @param main: whether the import is in the source's global namespace (__main__) @type main: int (treated as bool) """ # previous opcode is IMPORT_NAME fromName = code.stack[-1].data if utils.pythonVersion() < utils.PYTHON_2_0 and \ OP.POP_TOP(code.nextOpInfo()[0]): code.popNextOp() # FIXME: thomas: why are we pushing the operand, which represents what # we import, not where we import from ? code.pushStack(Stack.Item(operand, types.ModuleType)) _handleImport(code, operand, module, main, fromName) # http://www.python.org/doc/current/lib/typesseq-strings.html _FORMAT_CONVERTERS = 'diouxXeEfFgGcrs' # NOTE: lLh are legal in the flags, but are ignored by python, we warn _FORMAT_FLAGS = '*#- +.' + string.digits def _getFormatInfo(formatString, code) : variables = [] # first get rid of all the instances of %% in the string, they don't count formatString = string.replace(formatString, "%%", "") sections = string.split(formatString, '%') percentFormatCount = formatCount = string.count(formatString, '%') mappingFormatCount = 0 # skip the first item in the list, it's always empty for section in sections[1:] : orig_section = section if not section: w = msgs.INVALID_FORMAT % orig_section w.data = w.data + ' (end of format string)' code.addWarning(w) continue # handle dictionary formats if section[0] == '(' : mappingFormatCount = mappingFormatCount + 1 varname = string.split(section, ')') if varname[1] == '' : code.addWarning(msgs.INVALID_FORMAT % section) variables.append(varname[0][1:]) section = varname[1] if not section : # no format data to check continue # FIXME: we ought to just define a regular expression to check # formatRE = '[ #+-]*([0-9]*|*)(|.(|*|[0-9]*)[diouxXeEfFgGcrs].*' stars = 0 for i in range(0, len(section)) : if section[i] in _FORMAT_CONVERTERS : break if section[i] in _FORMAT_FLAGS : if section[i] == '*' : stars = stars + 1 if mappingFormatCount > 0 : code.addWarning(msgs.USING_STAR_IN_FORMAT_MAPPING % section) if stars > 2 : code.addWarning(msgs.TOO_MANY_STARS_IN_FORMAT) formatCount = formatCount + stars if section[i] not in _FORMAT_CONVERTERS : code.addWarning(msgs.INVALID_FORMAT % orig_section) if mappingFormatCount > 0 and mappingFormatCount != percentFormatCount : code.addWarning(msgs.CANT_MIX_MAPPING_IN_FORMATS) return formatCount, variables def _getConstant(code, module, data) : data = utils.safestr(data.data) formatString = code.constants.get(data) if formatString is not None : return formatString formatString = module.variables.get(data) if formatString is not None and formatString.value is not None : return formatString.value return None _UNCHECKABLE_FORMAT_STACK_TYPES = \ (Stack.TYPE_UNKNOWN, Stack.TYPE_FUNC_RETURN, Stack.TYPE_ATTRIBUTE, Stack.TYPE_GLOBAL, Stack.TYPE_EXCEPT) _UNCHECKABLE_STACK_TYPES = _UNCHECKABLE_FORMAT_STACK_TYPES + (types.NoneType,) def _getFormatString(code, codeSource) : if len(code.stack) <= 1 : return '' formatString = code.stack[-2] if formatString.type != types.StringType or not formatString.const : formatString = _getConstant(code, codeSource.module, formatString) if formatString is None or type(formatString) != types.StringType : return '' return formatString return formatString.data def _getFormatWarnings(code, codeSource) : formatString = _getFormatString(code, codeSource) if not formatString : return args = 0 count, variables = _getFormatInfo(formatString, code) topOfStack = code.stack[-1] if topOfStack.isLocals() : for varname in variables : if not code.unusedLocals.has_key(varname) : code.addWarning(msgs.NO_LOCAL_VAR % varname) else : code.unusedLocals[varname] = None else : stackItemType = topOfStack.getType(code.typeMap) if ((stackItemType == types.DictType and len(variables) > 0) or codeSource.func.isParam(topOfStack.data) or stackItemType in _UNCHECKABLE_FORMAT_STACK_TYPES) : return if topOfStack.type == types.TupleType : args = topOfStack.length elif stackItemType == types.TupleType : args = len(code.constants.get(topOfStack.data, (0,))) else : args = 1 if args and count != args : code.addWarning(msgs.INVALID_FORMAT_COUNT % (count, args)) def _checkAttributeType(code, stackValue, attr) : """ @type code: {Code} @type stackValue: {Stack.Item} @type attr: str """ if not cfg().checkObjectAttrs: return varTypes = code.typeMap.get(utils.safestr(stackValue.data), None) if not varTypes: return # the value may have been converted on stack (`v`) otherTypes = [] if stackValue.type not in varTypes: otherTypes = [stackValue.type] for varType in varTypes + otherTypes: # ignore built-in types that have no attributes if python.METHODLESS_OBJECTS.has_key(varType): continue attrs = python.BUILTIN_ATTRS.get(varType, None) if attrs is not None: if attr in attrs: return continue if hasattr(varType, 'ignoreAttrs') : if varType.ignoreAttrs or _classHasAttribute(varType, attr) : return elif not hasattr(varType, 'attributes') or attr in varType.attributes : return code.addWarning(msgs.OBJECT_HAS_NO_ATTR % (stackValue.data, attr)) def _getTypeStr(t): returnStr = utils.safestr(t) strs = string.split(returnStr, "'") try: if len(strs) == 3: returnStr = strs[-2] except IndexError: pass return returnStr def _getLineNum(co, instr_index): co_lnotab = co.co_lnotab lineno = co.co_firstlineno addr = 0 for lnotab_index in range(0, len(co_lnotab), 2): addr = addr + ord(co_lnotab[lnotab_index]) if addr > instr_index: return lineno lineno = lineno + ord(co_lnotab[lnotab_index+1]) return lineno class Code : """ Hold all the code state information necessary to find warnings. @ivar bytes: the raw bytecode for this code object @type bytes: str @ivar func_code: the function code object @type func_code: L{types.CodeType} @ivar index: index into bytes for the current instruction @type index: int @ivar extended_arg: extended argument for the current instruction @type extended_arg: int @ivar maxCode: length of bytes @type maxCode: int @ivar stack: @type stack: list of L{Stack.Item} @ivar warnings: list of warnings @type warnings: list of L{pychecker.Warning.Warning} @ivar returnValues: tuple of (line number, stack item, index to next instruction) @type returnValues: tuple of (int, L{Stack.Item}, int) @ivar typeMap: dict of token name -> list of wrapped types; type can also be string defined in L{Stack} with TYPE_ @type typeMap: dict of str -> list of str or L{pcmodules.Class} @ivar codeObjects: dict of name/anonymous index -> code @type codeObjects: dict of str/int -> L{types.CodeType} @ivar codeOrder: ordered list of when the given key was added to codeObjects @type codeOrder: list of str/int """ # opcodes are either 1 byte (no argument) or 3 bytes (with argument) long # opcode can be EXTENDED_ARGS which then accumulates to the previous arg # to span values > 64K def __init__(self) : self.bytes = None self.func = None self.func_code = None self.index = 0 self.indexList = [] self.extended_arg = 0 self.lastLineNum = 0 self.maxCode = 0 self.has_except = 0 self.try_finally_first = 0 self.starts_and_ends_with_finally = 0 self.returnValues = [] self.raiseValues = [] self.stack = [] self.unpackCount = 0 self.loops = 0 self.branches = {} self.warnings = [] self.globalRefs = {} self.unusedLocals = {} self.deletedLocals = {} self.functionsCalled = {} self.typeMap = {} self.constants = {} self.codeObjects = {} self.codeOrder = [] def init(self, func) : self.func = func self.func_code, self.bytes, self.index, self.maxCode, self.extended_arg = \ OP.initFuncCode(func.function) self.lastLineNum = self.func_code.co_firstlineno self.returnValues = [] # initialize the arguments to unused for arg in func.arguments() : self.unusedLocals[arg] = 0 self.typeMap[arg] = [ Stack.TYPE_UNKNOWN ] def getLineNum(self): line = self.lastLineNum # if we don't have linenum info, calc it from co_lntab & index if line == self.func_code.co_firstlineno: # FIXME: this could be optimized, if we kept last line info line = _getLineNum(self.func_code, self.index - 1) return line def getWarning(self, err, line = None) : """ @type err: L{msgs.WarningClass} """ if line is None : line = self.getLineNum() return Warning.Warning(self.func_code, line, err) def addWarning(self, err, line = None) : """ @type line: int or L{types.CodeType} or None @type err: L{Warning.Warning} or L{msgs.WarningClass} """ w = err if not isinstance(w, Warning.Warning): w = self.getWarning(err, line) utils.debug('adding warning: %s', w.format()) self.warnings.append(w) def popNextOp(self) : """ Pops the next bytecode instruction from the code object for processing. The opcode and oparg are integers coming from the byte code. The operand is the object referenced by the oparg, from the respective array (co_consts, co_names, co_varnames) Changes L{index} and L{extended_arg} to point to the next operation. @returns: tuple of (opcode, oparg, operand) @rtype: tuple of (int, int, object) """ self.indexList.append(self.index) info = OP.getInfo(self.bytes, self.index, self.extended_arg) op, oparg, self.index, self.extended_arg = info if op < OP.HAVE_ARGUMENT : utils.debug("DIS %d %s" % (self.indexList[-1], OP.name[op])) operand = None else : operand = OP.getOperand(op, self.func_code, oparg) self.label = label = OP.getLabel(op, oparg, self.index) utils.debug("DIS %d %s" % (self.indexList[-1], OP.name[op]), oparg, operand) if label != None : self.addBranch(label) return op, oparg, operand def nextOpInfo(self, offset = 0) : """ Peeks ahead at the next instruction. @returns: tuple of (opcode, oparg, index) or (-1, 0, -1) if no next @rtype: tuple of (int, int, int) """ try : return OP.getInfo(self.bytes, self.index + offset, 0)[0:3] except IndexError : return -1, 0, -1 def getFirstOp(self) : # find the first real op, maybe we should not check if params are used i = extended_arg = 0 while i < self.maxCode : op, oparg, i, extended_arg = OP.getInfo(self.bytes, i, extended_arg) if not OP.LINE_NUM(op) : if not (OP.LOAD_CONST(op) or OP.LOAD_GLOBAL(op)) : return op raise RuntimeError('Could not find first opcode in function') def pushStack(self, item, ignoreStmtWithNoEffect=0): self.stack.append(item) _checkNoEffect(self, ignoreStmtWithNoEffect) def popStack(self) : if self.stack : del self.stack[-1] def popStackItems(self, count) : stackLen = len(self.stack) if stackLen > 0 : count = min(count, stackLen) del self.stack[-count:] def unpack(self) : if self.unpackCount : self.unpackCount = self.unpackCount - 1 else : self.popStack() def __getStringStackType(self, data) : try : return data.getType({}) except AttributeError : return Stack.TYPE_UNKNOWN def __getStackType(self) : if not self.stack : return Stack.TYPE_UNKNOWN if not self.unpackCount : return self.__getStringStackType(self.stack[-1]) data = self.stack[-1].data if type(data) == types.TupleType : try : return self.__getStringStackType(data[len(data)-self.unpackCount]) except IndexError : # happens when unpacking a var for which we don't know the size pass return Stack.TYPE_UNKNOWN def setType(self, name) : valueList = self.typeMap.get(name, []) newType = self.__getStackType() # longs are being merged with ints, assume they are the same # comparisons are really ints anyways if newType in (types.LongType, Stack.TYPE_COMPARISON): newType = types.IntType if newType not in valueList : valueList.append(newType) # need to ignore various types (Unknown, Func return values, etc) # also ignore None, don't care if they use it and a real type if valueList and newType not in _UNCHECKABLE_STACK_TYPES and \ cfg().inconsistentTypes: oldTypes = [] # only add types to the value list that are "interesting" for typeToAdd in valueList: if typeToAdd not in _UNCHECKABLE_STACK_TYPES and \ typeToAdd != newType: oldTypes.append(_getTypeStr(typeToAdd)) # do we have any "interesting" old types? if so, warn if oldTypes: self.addWarning(msgs.INCONSISTENT_TYPE % \ (name, oldTypes, _getTypeStr(newType))) self.typeMap[name] = valueList def addReturn(self) : if len(self.stack) > 0 : value = (self.getLineNum(), self.stack[-1], self.nextOpInfo()[2]) self.returnValues.append(value) self.popStack() def addRaise(self) : self.raiseValues.append((self.getLineNum(), None, self.nextOpInfo()[2])) def addBranch(self, label) : if label is not None : self.branches[label] = self.branches.get(label, 0) + 1 def removeBranch(self, label) : branch = self.branches.get(label, None) if branch is not None : if branch == 1 : del self.branches[label] else : self.branches[label] = branch - 1 def remove_unreachable_code(self, label) : if len(self.indexList) >= 2 : index = self.indexList[-2] if index >= 0 and OP.POP_BLOCK(ord(self.bytes[index])) : index = self.indexList[-3] if index >= 0 : op = ord(self.bytes[index]) if OP.RETURN_VALUE(op) or OP.RAISE_VARARGS(op) or \ OP.END_FINALLY(ord(self.bytes[label-1])) : self.removeBranch(label) def updateCheckerArgs(self, operand) : """ If the operand is a __pychecker__ argument string, update the checker arguments for this Code object. @rtype: bool @returns: whether the checker arguments were updated. """ rc = utils.shouldUpdateArgs(operand) if rc : # pass the location of the __pychecker__ arguments utils.updateCheckerArgs(self.stack[-1].data, self.func_code, self.getLineNum(), self.warnings) return rc def updateModuleLineNums(self, module, operand) : """ @type module: L{pychecker.checker.PyCheckerModule} """ filelist = (self.func_code.co_filename, self.getLineNum()) module.moduleLineNums[operand] = filelist def addCodeObject(self, key, code): """ Add the given code object, maintaining order of addition. @param key: the key to be used for storing in self.codeObjects @type key: str or int @param code: the code to be stored @type code: L{types.CodeType} """ self.codeObjects[key] = code self.codeOrder.append(key) class CodeSource: """ Holds source information about a code block (module, class, func, etc) @ivar module: module the source is for @type module: L{pychecker.checker.PyCheckerModule} @ivar func: function the source is for @type func: L{pychecker.function.Function} @ivar classObject: the class object, if applicable @type classObject: L{pychecker.checker.Class} or None @ivar main: whether this code block is in the source's global namespace (__main__) @type main: int (used as bool) @ivar in_class: whether this code block is inside a class scope @type in_class: int (used as bool) @ivar calling_code: list of functions that call this source @type calling_code: list of callable """ def __init__(self, module, func, c, main, in_class, code): self.module = module self.func = func self.classObject = c self.main = main self.in_class = in_class self.code = code self.calling_code = [] def _checkException(code, name) : if code.stack and code.stack[-1].type == Stack.TYPE_EXCEPT : if __builtins__.has_key(name) : code.addWarning(msgs.SET_EXCEPT_TO_BUILTIN % name) def _checkAssign(code, name): if name in _BAD_ASSIGN_NAMES: code.addWarning(msgs.SHOULDNT_ASSIGN_BUILTIN % name) else: cap = string.capitalize(name) if cap in _BAD_ASSIGN_NAMES: code.addWarning(msgs.SHOULDNT_ASSIGN_NAME % (name, cap)) def _checkVariableOperationOnItself(code, lname, msg): if code.stack and code.stack[-1].getName() == lname: code.addWarning(msg % lname) # checks if the given varname is a known future keyword, and warn if so def _checkFutureKeywords(code, varname) : kw = python.FUTURE_KEYWORDS.get(varname) if kw is not None : code.addWarning(msgs.USING_KEYWORD % (varname, kw)) ### dispatcher functions for operands # All these functions have the following documentation: # @type oparg: int # @param oparg: # @type operand: object # @param operand: # @param codeSource: # @type codeSource: L{CodeSource} # @param code: # @type code: L{Code} # Implements name = TOS. namei is the index of name in the attribute co_names # of the code object. The compiler tries to use STORE_FAST or STORE_GLOBAL if # possible. def _STORE_NAME(oparg, operand, codeSource, code) : if not code.updateCheckerArgs(operand) : module = codeSource.module # not a __pychecker__ operand, so continue checking _checkFutureKeywords(code, operand) if not codeSource.in_class : _checkShadowBuiltin(code, operand) # complain if the code is called and declares global on an # undefined name if not codeSource.calling_code : _checkGlobal(operand, module, codeSource.func, code, msgs.GLOBAL_DEFINED_NOT_DECLARED, codeSource.main) else : # we're in a class if code.stack : codeSource.classObject.statics[operand] = code.stack[-1] codeSource.classObject.lineNums[operand] = code.getLineNum() var = module.variables.get(operand) if var is not None and code.stack and code.stack[-1].const : var.value = code.stack[-1].data if code.unpackCount : code.unpackCount = code.unpackCount - 1 else: _checkAssign(code, operand) _checkException(code, operand) code.popStack() if not module.moduleLineNums.has_key(operand) and codeSource.main : code.updateModuleLineNums(module, operand) _STORE_GLOBAL = _STORE_NAME def _checkLoadGlobal(codeSource, code, varname) : _checkFutureKeywords(code, varname) should_check = 1 if code.func_code.co_name == utils.LAMBDA : # this could really be a local reference, check first if not codeSource.main and codeSource.calling_code: func = getattr(codeSource.calling_code[-1], 'function', None) if func is not None and varname in func.func_code.co_varnames : _handleLoadLocal(code, codeSource, varname) should_check = 0 if should_check : # if a global var starts w/__ and the global is referenced in a class # we have to strip off the _class-name, to get the original name if codeSource.classObject and \ utils.startswith(varname, '_' + codeSource.classObject.name + '__'): varname = varname[len(codeSource.classObject.name)+1:] # make sure we remember each global ref to check for unused code.globalRefs[_getGlobalName(varname, codeSource.func)] = varname if not codeSource.in_class : _checkGlobal(varname, codeSource.module, codeSource.func, code, msgs.INVALID_GLOBAL) def _LOAD_NAME(oparg, operand, codeSource, code) : _checkLoadGlobal(codeSource, code, operand) # if there was from XXX import *, _* names aren't imported if codeSource.module.modules.has_key(operand) and \ hasattr(codeSource.module.module, operand) : operand = getattr(codeSource.module.module, operand).__name__ opType, const = Stack.TYPE_GLOBAL, 0 if operand == 'None' : opType, const = types.NoneType, 0 elif operand == 'Ellipsis' : opType, const = types.EllipsisType, 1 code.pushStack(Stack.Item(operand, opType, const)) _LOAD_GLOBAL = _LOAD_NAME def _LOAD_DEREF(oparg, operand, codeSource, code) : if type(oparg) == types.IntType : func_code = code.func_code try: argname = func_code.co_cellvars[oparg] except IndexError: argname = func_code.co_freevars[oparg - len(func_code.co_cellvars)] code.pushStack(Stack.Item(argname, types.StringType)) if code.func_code.co_name != utils.LAMBDA : code.unusedLocals[argname] = None else : _LOAD_GLOBAL(oparg, operand, codeSource, code) _LOAD_CLOSURE = _LOAD_DEREF # Implements del name, where namei is the index into co_names attribute of the # code object. def _DELETE_NAME(oparg, operand, codeSource, code) : _checkLoadGlobal(codeSource, code, operand) # FIXME: handle deleting global multiple times _DELETE_GLOBAL = _DELETE_NAME def _make_const(value): if type(value) == types.TupleType: return Stack.makeTuple(map(_make_const, value)) return Stack.Item(value, type(value), 1) def _LOAD_CONST(oparg, operand, codeSource, code): code.pushStack(_make_const(operand)) # add code objects to code.codeObjects if type(operand) == types.CodeType: name = operand.co_name obj = code.codeObjects.get(name, None) if name in (utils.LAMBDA, utils.GENEXP, utils.GENEXP25): # use a unique key, so we can have multiple lambdas if code.index in code.codeObjects: msg = "LOAD_CONST: code.index %d is already in codeObjects" \ % code.index code.addWarning(msgs.CHECKER_BROKEN % msg) code.addCodeObject(code.index, operand) elif obj is None: code.addCodeObject(name, operand) elif cfg().redefiningFunction: code.addWarning(msgs.REDEFINING_ATTR % (name, obj.co_firstlineno)) def _checkLocalShadow(code, module, varname) : # FIXME: why is this only for variables, not for classes/functions ? if module.variables.has_key(varname) and cfg().shadows : line = module.moduleLineNums.get(varname, ('', 0)) w = code.getWarning(msgs.LOCAL_SHADOWS_GLOBAL % (varname, line[1])) if line[0] != w.file: w.err = '%s in file %s' % (w.err, line[0]) code.addWarning(w) def _checkShadowBuiltin(code, varname) : # Check if the given variable name shadows a builtin if __builtins__.has_key(varname) and varname[0] != '_' and \ cfg().shadowBuiltins: code.addWarning(msgs.VARIABLE_SHADOWS_BUILTIN % varname) def _checkLoadLocal(code, codeSource, varname, deletedWarn, usedBeforeSetWarn) : _checkFutureKeywords(code, varname) deletedLine = code.deletedLocals.get(varname) if deletedLine : code.addWarning(deletedWarn % (varname, deletedLine)) elif not code.unusedLocals.has_key(varname) and \ not codeSource.func.isParam(varname) : code.addWarning(usedBeforeSetWarn % varname) code.unusedLocals[varname] = None _checkLocalShadow(code, codeSource.module, varname) def _handleLoadLocal(code, codeSource, varname) : _checkLoadLocal(code, codeSource, varname, msgs.LOCAL_DELETED, msgs.VAR_USED_BEFORE_SET) def _LOAD_FAST(oparg, operand, codeSource, code) : code.pushStack(Stack.Item(operand, type(operand))) _handleLoadLocal(code, codeSource, operand) def _STORE_FAST(oparg, operand, codeSource, code) : if not code.updateCheckerArgs(operand) : # not a __pychecker__ operand, so continue checking _checkFutureKeywords(code, operand) if code.stack and code.stack[-1].type == types.StringType and \ not code.stack[-1].const: _checkVariableOperationOnItself(code, operand, msgs.SET_VAR_TO_ITSELF) code.setType(operand) if not code.unpackCount and code.stack and \ (code.stack[-1].const or code.stack[-1].type == types.TupleType) : if code.constants.has_key(operand) : del code.constants[operand] else : code.constants[operand] = code.stack[-1].data _checkLocalShadow(code, codeSource.module, operand) _checkShadowBuiltin(code, operand) _checkAssign(code, operand) _checkException(code, operand) if code.deletedLocals.has_key(operand) : del code.deletedLocals[operand] if not code.unusedLocals.has_key(operand) : errLine = code.getLineNum() if code.unpackCount and not cfg().unusedLocalTuple : errLine = -errLine code.unusedLocals[operand] = errLine code.unpack() def _DELETE_FAST(oparg, operand, codeSource, code) : _checkLoadLocal(code, codeSource, operand, msgs.LOCAL_ALREADY_DELETED, msgs.VAR_DELETED_BEFORE_SET) code.deletedLocals[operand] = code.getLineNum() def _checkAttribute(top, operand, codeSource, code) : if top.data == cfg().methodArgName and codeSource.classObject != None : _checkClassAttribute(operand, codeSource.classObject, code) elif type(top.type) == types.StringType or top.type == types.ModuleType : _checkModuleAttribute(operand, codeSource.module, code, top.data) else : _checkAttributeType(code, top, operand) def _checkExcessiveReferences(code, top, extraAttr = None) : if cfg().maxReferences <= 0 : return try : data = top.data if extraAttr is not None : data = data + (extraAttr,) maxReferences = cfg().maxReferences if data[0] == cfg().methodArgName: maxReferences = maxReferences + 1 if len(data) > maxReferences : name = string.join(top.data, '.') code.addWarning(msgs.TOO_MANY_REFERENCES % (maxReferences, name)) except TypeError : pass def _checkDeprecated(code, identifierTuple): # check deprecated module.function try: name = string.join(identifierTuple, '.') undeprecated = python.DEPRECATED_ATTRS[name] except (KeyError, TypeError): pass else: msg = msgs.USING_DEPRECATED_ATTR % name if undeprecated: msg.data = msg.data + msgs.USE_INSTEAD % undeprecated code.addWarning(msg) def _LOAD_ATTR(oparg, operand, codeSource, code) : if len(code.stack) > 0 : top = code.stack[-1] _checkAttribute(top, operand, codeSource, code) top.addAttribute(operand) if len(top.data) == 2: if cfg().deprecated: _checkDeprecated(code, top.data) try: insecure = python.SECURITY_FUNCS.get(top.data[0]) except TypeError: pass else: if insecure and insecure.has_key(operand): func = string.join(top.data, '.') code.addWarning(msgs.USING_INSECURE_FUNC % func) nextOp = code.nextOpInfo()[0] if not OP.LOAD_ATTR(nextOp) : if OP.POP_TOP(nextOp) and cfg().noEffect: code.addWarning(msgs.POSSIBLE_STMT_WITH_NO_EFFECT) else : _checkExcessiveReferences(code, top) def _ok_to_set_attr(classObject, basename, attr) : return (cfg().onlyCheckInitForMembers and classObject != None and basename == cfg().methodArgName and not _classHasAttribute(classObject, attr)) def _STORE_ATTR(oparg, operand, codeSource, code) : if code.stack : top = code.stack.pop() top_name = '%s.%s' % (top.getName(), operand) try: # FIXME: this is a hack to handle code like: # a.a = [x for x in range(2) if x > 1] previous = code.stack[-1] except IndexError: previous = None if top.type in (types.StringType, Stack.TYPE_ATTRIBUTE) and \ previous and previous.type == Stack.TYPE_ATTRIBUTE: _checkVariableOperationOnItself(code, top_name, msgs.SET_VAR_TO_ITSELF) _checkExcessiveReferences(code, top, operand) if _ok_to_set_attr(codeSource.classObject, top.data, operand) : code.addWarning(msgs.INVALID_SET_CLASS_ATTR % operand) code.unpack() def _DELETE_ATTR(oparg, operand, codeSource, code) : if len(code.stack) > 0 : _checkAttribute(code.stack[-1], operand, codeSource, code) def _getExceptionInfo(codeSource, item): # FIXME: probably ought to try to handle raise module.Error if item.type is types.StringType and item.const == 1: return item.data, 1 e = None if item.type is Stack.TYPE_GLOBAL: try: e = eval(item.data) except NameError: pass if not e: try: c = codeSource.module.classes.get(item.data) except TypeError: # item.data may not be hashable (e.g., list) return e, 0 if c is not None: e = c.classObject else: v = codeSource.module.variables.get(item.data) if v is not None: return v, (v.type == types.StringType) return e, 0 _UNCHECKABLE_CATCH_TYPES = (Stack.TYPE_UNKNOWN, Stack.TYPE_ATTRIBUTE) def _checkCatchException(codeSource, code, item): if not cfg().badExceptions: return if item.data is None or item.type in _UNCHECKABLE_CATCH_TYPES: return e, is_str = _getExceptionInfo(codeSource, item) if is_str: code.addWarning(msgs.CATCH_STR_EXCEPTION % item.data) elif e is not None and not _isexception(e): code.addWarning(msgs.CATCH_BAD_EXCEPTION % item.data) def _handleExceptionChecks(codeSource, code, checks): for item in checks: if item is not None: if item.type is not types.TupleType: _checkCatchException(codeSource, code, item) else: for ti in item.data: if isinstance(ti, Stack.Item): _checkCatchException(codeSource, code, ti) _BOOL_NAMES = ('True', 'False') _BAD_ASSIGN_NAMES = _BOOL_NAMES + ('None',) def _checkBoolean(code, checks): for item in checks: try: data = string.capitalize(item.data) if item.type is Stack.TYPE_GLOBAL and data in _BOOL_NAMES: code.addWarning(msgs.BOOL_COMPARE % item.data) except (AttributeError, TypeError): # TypeError is necessary for Python 1.5.2 pass # ignore items that are not a StackItem or a string def _COMPARE_OP(oparg, operand, codeSource, code) : compareValues = _handleComparison(code.stack, operand) if oparg == OP.EXCEPT_COMPARISON: _handleExceptionChecks(codeSource, code, compareValues) elif oparg < OP.IN_COMPARISON: # '<', '<=', '==', '!=', '>', '>=' _checkBoolean(code, compareValues) elif oparg < OP.IS_COMPARISON: # 'in', 'not in' # TODO: any checks that should be done here? pass elif cfg().isLiteral: # X is Y or X is not Y comparison second_arg = code.stack[-1].data[2] # FIXME: how should booleans be handled, need to think about it ## if second_arg.const or (second_arg.type == Stack.TYPE_GLOBAL and ## second_arg.data in ['True', 'False']): if second_arg.const and second_arg.data is not None: data = second_arg.data if second_arg.type is types.DictType: data = {} not_str = '' if oparg != OP.IS_COMPARISON: not_str = ' not' code.addWarning(msgs.IS_LITERAL % (not_str, data)) _checkNoEffect(code) def _IMPORT_NAME(oparg, operand, codeSource, code) : code.pushStack(Stack.Item(operand, types.ModuleType)) # only handle straight import names; FROM or STAR are done separately nextOp = code.nextOpInfo()[0] if not OP.IMPORT_FROM(nextOp) and not OP.IMPORT_STAR(nextOp): _handleImport(code, operand, codeSource.module, codeSource.main, None) def _IMPORT_FROM(oparg, operand, codeSource, code) : _handleImportFrom(code, operand, codeSource.module, codeSource.main) # this is necessary for python 1.5 (see STORE_GLOBAL/NAME) if utils.pythonVersion() < utils.PYTHON_2_0 : code.popStack() if not codeSource.main : code.unusedLocals[operand] = None elif not codeSource.module.moduleLineNums.has_key(operand) : code.updateModuleLineNums(codeSource.module, operand) # Loads all symbols not starting with '_' directly from the module TOS to the # local namespace. The module is popped after loading all names. This opcode # implements from module import *. def _IMPORT_STAR(oparg, operand, codeSource, code): # codeSource: the piece of source code doing the import # code: the piece of code matching codeSource doing the import _handleImportFrom(code, '*', codeSource.module, codeSource.main) # Python 2.3 introduced some optimizations that create problems # this is a utility for ignoring these cases def _shouldIgnoreCodeOptimizations(code, bytecodes, offset, length=None): if utils.pythonVersion() < utils.PYTHON_2_3: return 0 if length is None: length = offset - 1 try: start = code.index - offset return bytecodes == code.bytes[start:start+length] except IndexError: return 0 # In Python 2.3, a, b = 1,2 generates this code: # ... # ROT_TWO # JUMP_FORWARD 2 # DUP_TOP # POP_TOP # # which generates a Possible stmt w/no effect # ROT_TWO = 2; JUMP_FORWARD = 110; 2, 0 is the offset (2) _IGNORE_SEQ = '%c%c%c%c' % (2, 110, 2, 0) def _shouldIgnoreNoEffectWarning(code): return _shouldIgnoreCodeOptimizations(code, _IGNORE_SEQ, 5) def _DUP_TOP(oparg, operand, codeSource, code) : if len(code.stack) > 0 : code.pushStack(code.stack[-1], _shouldIgnoreNoEffectWarning(code)) # Duplicate count items, keeping them in the same order. Due to implementation # limits, count should be between 1 and 5 inclusive. def _DUP_TOPX(oparg, operand, codeSource, code): if oparg > 5: code.addWarning(msgs.Warning( 'DUP_TOPX has oparg %d, should not be more than 5' % oparg)) if len(code.stack) > oparg - 1: source = code.stack[-oparg:] for item in source: code.pushStack(item, _shouldIgnoreNoEffectWarning(code)) def _popn(code, n) : if len(code.stack) >= 2 : loadValue = code.stack[-2] if cfg().modifyDefaultValue and loadValue.type == types.StringType : _checkModifyDefaultArg(code, loadValue.data) code.popStackItems(n) def _DELETE_SUBSCR(oparg, operand, codeSource, code) : _popn(code, 2) def _STORE_SUBSCR(oparg, operand, codeSource, code) : _popn(code, 3) def _CALL_FUNCTION(oparg, operand, codeSource, code) : _handleFunctionCall(codeSource, code, oparg) def _CALL_FUNCTION_VAR(oparg, operand, codeSource, code) : _handleFunctionCall(codeSource, code, oparg, 1, 0) def _CALL_FUNCTION_KW(oparg, operand, codeSource, code) : _handleFunctionCall(codeSource, code, oparg, 1) def _CALL_FUNCTION_VAR_KW(oparg, operand, codeSource, code) : _handleFunctionCall(codeSource, code, oparg, 2, 0) # Pushes a new function object on the stack. TOS is the code associated with # the function. The function object is defined to have argc default parameters, # which are found below TOS. def _MAKE_FUNCTION(oparg, operand, codeSource, code) : newValue = Stack.makeFuncReturnValue(code.stack[-1], oparg) code.popStackItems(oparg+1) code.pushStack(newValue) def _MAKE_CLOSURE(oparg, operand, codeSource, code) : _MAKE_FUNCTION(max(0, oparg - 1), operand, codeSource, code) def _BUILD_MAP(oparg, operand, codeSource, code) : # Pushes a new dictionary object onto the stack. The dictionary is # pre-sized to hold count entries. # before python 2.6, the argument was always zero # since 2.6, the argument is the size the dict should be pre-sized to # In either case, _BUILD_MAP does not consume anything from the stack _makeConstant(code, 0, Stack.makeDict) def _BUILD_TUPLE(oparg, operand, codeSource, code) : # Creates a tuple consuming count items from the stack, and pushes the # resulting tuple onto the stack. _makeConstant(code, oparg, Stack.makeTuple) def _BUILD_LIST(oparg, operand, codeSource, code) : # Works as BUILD_TUPLE, but creates a list. _makeConstant(code, oparg, Stack.makeList) def _STORE_MAP(oparg, operand, codeSource, code) : _popn(code, 2) # Creates a new class object. TOS is the methods dictionary, TOS1 the tuple # of the names of the base classes, and TOS2 the class name. def _BUILD_CLASS(oparg, operand, codeSource, code) : newValue = Stack.makeFuncReturnValue(code.stack[-1], types.ClassType) code.popStackItems(3) code.pushStack(newValue) # example disassembly: # (' 18 BUILD_LIST', 6, None) # (' 21 STORE_FAST', 0, 'l') # (' 24 LOAD_FAST', 0, 'l') # (' 27 LOAD_CONST', 7, 0) # (' 30 LOAD_CONST', 0, None) # (' 33 LOAD_CONST', 2, 2) # (' 36 BUILD_SLICE', 3, None) def _BUILD_SLICE(oparg, operand, codeSource, code): argCount = oparg assert argCount in [2, 3] if argCount == 3: start = code.stack[-3].data stop = code.stack[-2].data step = code.stack[-1].data sourceName = code.stack[-4].data else: start = code.stack[-2].data stop = code.stack[-1].data step = None sourceName = code.stack[-3].data if sourceName in code.constants: source = code.constants[sourceName] if step: sl = source[start:stop:step] else: sl = source[start:stop] # push new slice on stack code.stack[-argCount:] = [Stack.Item(sl, types.ListType, 1, len(sl)), ] else: # FIXME: not sure what we do with a non-constant slice ? # push a non-constant slice of the source on stack code.stack[-argCount:] = [Stack.Item( sourceName, types.ListType, 0, 1), ] _checkNoEffect(code) # old pre-2.7 LIST_APPEND, argumentless def _LIST_APPEND(oparg, operand, codeSource, code): code.popStackItems(2) # new 2.7 LIST_APPEND, takes argument as number of items to append def _LIST_APPEND_2_7(oparg, operand, codeSource, code): count = oparg code.popStackItems(1 + count) def _modifyStackName(code, suffix): if code.stack: tos = code.stack[-1] tos_type = type(tos.data) if tos_type == types.StringType: tos.data = tos.data + suffix elif tos_type == types.TupleType and \ type(tos.data[-1]) == types.StringType: tos.data = tos.data[:-1] + (tos.data[-1] + suffix,) def _UNARY_CONVERT(oparg, operand, codeSource, code) : if code.stack: stackValue = code.stack[-1] if stackValue.data == cfg().methodArgName and \ stackValue.const == 0 and codeSource.classObject is not None and \ codeSource.func.function.func_name == '__repr__' : code.addWarning(msgs.USING_SELF_IN_REPR) stackValue.data = utils.safestr(stackValue.data) stackValue.type = types.StringType _modifyStackName(code, '-repr') def _UNARY_POSITIVE(oparg, operand, codeSource, code) : if OP.UNARY_POSITIVE(code.nextOpInfo()[0]) : code.addWarning(msgs.STMT_WITH_NO_EFFECT % '++') code.popNextOp() elif cfg().unaryPositive and code.stack and not code.stack[-1].const : code.addWarning(msgs.UNARY_POSITIVE_HAS_NO_EFFECT) _modifyStackName(code, '-pos') def _UNARY_NEGATIVE(oparg, operand, codeSource, code) : if OP.UNARY_NEGATIVE(code.nextOpInfo()[0]) : code.addWarning(msgs.STMT_WITH_NO_EFFECT % '--') _modifyStackName(code, '-neg') def _UNARY_NOT(oparg, operand, codeSource, code) : _modifyStackName(code, '-not') def _UNARY_INVERT(oparg, operand, codeSource, code) : if OP.UNARY_INVERT(code.nextOpInfo()[0]) : code.addWarning(msgs.STMT_WITH_NO_EFFECT % '~~') _modifyStackName(code, '-invert') def _popStackRef(code, operand, count = 2) : # pop count items, then push a ref to the operand on the stack code.popStackItems(count) code.pushStack(Stack.Item(operand, Stack.TYPE_UNKNOWN)) def _popModifiedStack(code, suffix=' '): code.popStack() _modifyStackName(code, suffix) def _pop(oparg, operand, codeSource, code) : code.popStack() _POP_TOP = _PRINT_ITEM = _pop def _popModified(oparg, operand, codeSource, code): _popModifiedStack(code) def _BINARY_RSHIFT(oparg, operand, codeSource, code): _coerce_type(code) _popModified(oparg, operand, codeSource, code) _BINARY_LSHIFT = _BINARY_RSHIFT def _checkModifyNoOp(code, op, msg=msgs.MODIFY_VAR_NOOP, modifyStack=1): stack = code.stack if len(stack) >= 2: if (stack[-1].type != Stack.TYPE_UNKNOWN and stack[-2].type != Stack.TYPE_UNKNOWN): name = stack[-1].getName() if name != Stack.TYPE_UNKNOWN and name == stack[-2].getName(): code.addWarning(msg % (name, op, name)) if modifyStack: code.popStack() stack[-1].const = 0 _modifyStackName(code, op) def _BINARY_AND(oparg, operand, codeSource, code): # Don't modify the stack, since _coerce_type() will do it. _checkModifyNoOp(code, '&', modifyStack=0) _coerce_type(code) def _BINARY_OR(oparg, operand, codeSource, code): # Don't modify the stack, since _coerce_type() will do it. _checkModifyNoOp(code, '|', modifyStack=0) _coerce_type(code) def _BINARY_XOR(oparg, operand, codeSource, code): # Don't modify the stack, since _coerce_type() will do it. _checkModifyNoOp(code, '^', msgs.XOR_VAR_WITH_ITSELF, modifyStack=0) _coerce_type(code) def _PRINT_ITEM_TO(oparg, operand, codeSource, code): code.popStackItems(2) def _PRINT_NEWLINE_TO(oparg, operand, codeSource, code): code.popStackItems(1) try: ComplexType = types.ComplexType except NameError: ComplexType = types.FloatType # need some numeric type here _NUMERIC_TYPES = (types.IntType, types.FloatType, ComplexType) # FIXME: This is pathetically weak, need to handle more types def _coerce_type(code) : _checkNoEffect(code) newItem = Stack.Item('', Stack.TYPE_UNKNOWN) if len(code.stack) >= 2 : s1, s2 = code.stack[-2:] s1type = s1.getType(code.typeMap) s2type = s2.getType(code.typeMap) if s1type != s2type : if s1type in _NUMERIC_TYPES and s2type in _NUMERIC_TYPES : newType = types.FloatType if s1type == ComplexType or s2type == ComplexType: newType = ComplexType newItem.type = newType code.popStackItems(2) code.pushStack(newItem) def _BINARY_ADD(oparg, operand, codeSource, code) : stack = code.stack if len(stack) >= 2 and (stack[-1].const and stack[-2].const and stack[-1].type == stack[-2].type) : value = stack[-2].data + stack[-1].data code.popStackItems(2) code.pushStack(Stack.Item(value, type(value), 1)) else : _coerce_type(code) def _BINARY_SUBTRACT(oparg, operand, codeSource, code) : _coerce_type(code) _BINARY_POWER = _BINARY_SUBTRACT def _BINARY_SUBSCR(oparg, operand, codeSource, code) : _checkNoEffect(code) if len(code.stack) >= 2 : stack = code.stack varType = code.typeMap.get(utils.safestr(stack[-2].data), []) if types.ListType in varType and stack[-1].type == types.TupleType : code.addWarning(msgs.USING_TUPLE_ACCESS_TO_LIST % stack[-2].data) _popStackRef(code, operand) def _isint(stackItem, code) : if type(stackItem.data) == types.IntType : return 1 stackTypes = code.typeMap.get(stackItem.data, []) if len(stackTypes) != 1 : return 0 return types.IntType in stackTypes def _BINARY_DIVIDE(oparg, operand, codeSource, code) : _checkNoEffect(code) _checkModifyNoOp(code, '/', msgs.DIVIDE_VAR_BY_ITSELF, 0) if cfg().intDivide and len(code.stack) >= 2 : if _isint(code.stack[-1], code) and _isint(code.stack[-2], code) : # don't warn if we are going to convert the result to an int if not (len(code.stack) >= 3 and code.stack[-3].data == 'int' and OP.CALL_FUNCTION(code.nextOpInfo()[0])): code.addWarning(msgs.INTEGER_DIVISION % tuple(code.stack[-2:])) _popModifiedStack(code, '/') def _BINARY_TRUE_DIVIDE(oparg, operand, codeSource, code) : _checkNoEffect(code) _checkVariableOperationOnItself(code, operand, msgs.DIVIDE_VAR_BY_ITSELF) _popModifiedStack(code, '/') _BINARY_FLOOR_DIVIDE = _BINARY_TRUE_DIVIDE def _BINARY_MULTIPLY(oparg, operand, codeSource, code) : if len(code.stack) >= 2 : formatString = _getFormatString(code, codeSource) if formatString and type(code.stack[-1].data) == types.IntType : code.stack[-2].data = formatString * code.stack[-1].data code.popStack() else: _coerce_type(code) else: _popModifiedStack(code, '*') def _BINARY_MODULO(oparg, operand, codeSource, code) : _checkNoEffect(code) if cfg().modulo1 and code.stack and code.stack[-1].data == 1: if len(code.stack) < 2 or \ code.stack[-2].getType(code.typeMap) != types.FloatType: code.addWarning(msgs.MODULO_1) _getFormatWarnings(code, codeSource) _popModifiedStack(code, '%') if code.stack: code.stack[-1].const = 0 def _ROT_TWO(oparg, operand, codeSource, code) : if len(code.stack) >= 2 : tmp = code.stack[-2] code.stack[-2] = code.stack[-1] code.stack[-1] = tmp def _ROT_THREE(oparg, operand, codeSource, code) : """Lifts second and third stack item one position up, moves top down to position three.""" if len(code.stack) >= 3 : second = code.stack[-2] third = code.stack[-3] code.stack[-3] = code.stack[-1] code.stack[-2] = third code.stack[-1] = second def _ROT_FOUR(oparg, operand, codeSource, code) : """Lifts second, third and forth stack item one position up, moves top down to position four.""" if len(code.stack) >= 4 : second = code.stack[-2] third = code.stack[-3] fourth = code.stack[-4] code.stack[-4] = code.stack[-1] code.stack[-3] = fourth code.stack[-2] = third code.stack[-1] = second def _SETUP_EXCEPT(oparg, operand, codeSource, code) : code.has_except = 1 code.pushStack(Stack.Item(None, Stack.TYPE_EXCEPT)) code.pushStack(Stack.Item(None, Stack.TYPE_EXCEPT)) def _SETUP_FINALLY(oparg, operand, codeSource, code) : if not code.has_except : code.try_finally_first = 1 # SETUP_WITH added in 2.7 # not sure what _SETUP_FINALLY does exactly, but looking at the 2.6 # implementation for the new SETUP_WITH, it ends with SETUP_FINALLY, # so SETUP_WITH should do the same def _SETUP_WITH(oparg, operand, codeSource, code): if not code.has_except: code.try_finally_first = 1 # WITH_CLEANUP since 2.5 # four stack possibilities: # * TOP = None # * (TOP, SECOND) = (WHY_{RETURN,CONTINUE}), retval # * TOP = WHY_*; no retval below it # * (TOP, SECOND, THIRD) = exc_info() def _WITH_CLEANUP(oparg, operand, codeSource, code): if code.stack: top = code.stack[-1] # FIXME: only implement the first one, only one I've seen if top.isNone(): code.popStack() else: # FIXME: NotImplementedError gets recaught and reraised with # less useful info raise IndexError('WITH_CLEANUP with TOS %r' % top) # pop exit ? I didn't see it in my example case def _END_FINALLY(oparg, operand, codeSource, code) : if code.try_finally_first and code.index == (len(code.bytes) - 4) : code.starts_and_ends_with_finally = 1 def _LINE_NUM(oparg, operand, codeSource, code) : code.lastLineNum = oparg def _UNPACK_SEQUENCE(oparg, operand, codeSource, code) : code.unpackCount = oparg if code.stack: top = code.stack[-1] # if we know we have a tuple, make sure we unpack it into the # right # of variables topType = top.getType(code.typeMap) if topType in _SEQUENCE_TYPES: length = top.length # we don't know the length, maybe it's constant and we can find out if length == 0: value = code.constants.get(utils.safestr(top.data)) if type(value) in _SEQUENCE_TYPES: length = len(value) if length > 0 and length != oparg: if cfg().unpackLength: code.addWarning(msgs.WRONG_UNPACK_SIZE % (length, oparg)) elif topType not in _UNCHECKABLE_STACK_TYPES: if cfg().unpackNonSequence: code.addWarning(msgs.UNPACK_NON_SEQUENCE % (top.data, _getTypeStr(topType))) _modifyStackName(code, '-unpack') def _SLICE_1_ARG(oparg, operand, codeSource, code) : _popStackRef(code, operand) def _SLICE0(oparg, operand, codeSource, code) : # Implements TOS = TOS[:]. _popStackRef(code, operand, count=1) _SLICE1 = _SLICE2 = _SLICE_1_ARG def _SLICE3(oparg, operand, codeSource, code) : _popStackRef(code, operand, 3) def _STORE_SLICE0(oparg, operand, codeSource, code) : # FIXME: can we check here if we're storing to something that supports # slice assignment ? _popStackRef(code, operand, 2) def _STORE_SLICE1(oparg, operand, codeSource, code) : # FIXME: can we check here if we're storing to something that supports # slice assignment ? _popStackRef(code, operand, 3) _STORE_SLICE2 = _STORE_SLICE1 def _DELETE_SLICE1(oparg, operand, codeSource, code) : # FIXME: can we check here if we're deleting from something that supports # slice deletion ? _popStackRef(code, operand, count=2) _DELETE_SLICE2 = _DELETE_SLICE1 def _DELETE_SLICE3(oparg, operand, codeSource, code) : # FIXME: can we check here if we're deleting from something that supports # slice deletion ? _popStackRef(code, operand, 3) def _check_string_iteration(code, index): try: item = code.stack[index] except IndexError: return if item.getType(code.typeMap) == types.StringType and \ cfg().stringIteration: code.addWarning(msgs.STRING_ITERATION % item.data) def _FOR_LOOP(oparg, operand, codeSource, code) : code.loops = code.loops + 1 _check_string_iteration(code, -2) _popStackRef(code, '', 2) def _GET_ITER(oparg, operand, codeSource, code) : _check_string_iteration(code, -1) def _FOR_ITER(oparg, operand, codeSource, code) : code.loops = code.loops + 1 _popStackRef(code, '', 1) def _jump(oparg, operand, codeSource, code): if len(code.stack) >0: topOfStack = code.stack[-1] if topOfStack.isMethodCall(codeSource.classObject, cfg().methodArgName): name = topOfStack.data[-1] if codeSource.classObject.methods.has_key(name): code.addWarning(msgs.USING_METHOD_AS_ATTR % name) _JUMP_ABSOLUTE = _jump def _skip_loops(bytes, i, lastLineNum, max) : extended_arg = 0 blockCount = 1 while i < max : op, oparg, i, extended_arg = OP.getInfo(bytes, i, extended_arg) if OP.LINE_NUM(op) : lastLineNum = oparg elif OP.FOR_LOOP(op) or OP.FOR_ITER(op) or OP.SETUP_LOOP(op) : blockCount = blockCount + 1 elif OP.POP_BLOCK(op) : blockCount = blockCount - 1 if blockCount <= 0 : break return lastLineNum, i def _is_unreachable(code, topOfStack, branch, ifFalse) : # Are we are checking exceptions, but we not catching all exceptions? if (topOfStack.type == Stack.TYPE_COMPARISON and topOfStack.data[1] == 'exception match' and topOfStack.data[2] is not Exception) : return 1 # do we possibly have while 1: ? if not (topOfStack.const and topOfStack.data == 1 and ifFalse): return 0 # get the op just before the branch (ie, -3) op, oparg, i, extended_arg = OP.getInfo(code.bytes, branch - 3, 0) # are we are jumping to before the while 1: (LOAD_CONST, JUMP_IF_FALSE) if not (OP.JUMP_ABSOLUTE(op) and oparg == (code.index - 3*3)) : return 0 # check if we break out of the loop i = code.index lastLineNum = code.getLineNum() while i < branch : op, oparg, i, extended_arg = OP.getInfo(code.bytes, i, extended_arg) if OP.LINE_NUM(op) : lastLineNum = oparg elif OP.BREAK_LOOP(op) : return 0 elif OP.FOR_LOOP(op) or OP.FOR_ITER(op) or OP.SETUP_LOOP(op) : lastLineNum, i = _skip_loops(code.bytes, i, lastLineNum, branch) i = code.index - 3*4 op, oparg, i, extended_arg = OP.getInfo(code.bytes, i, 0) if OP.SETUP_LOOP(op) : # a little lie to pretend we have a raise after a while 1: code.removeBranch(i + oparg) code.raiseValues.append((lastLineNum, None, i + oparg)) return 1 # In Python 2.3, while/if 1: gets optimized to # ... # JUMP_FORWARD 4 # JUMP_IF_FALSE ? # POP_TOP # # which generates a Using a conditional statement with a constant value # JUMP_FORWARD = 110; 4, 0 is the offset (4) _IGNORE_BOGUS_JUMP = '%c%c%c' % (110, 4, 0) def _shouldIgnoreBogusJumps(code): return _shouldIgnoreCodeOptimizations(code, _IGNORE_BOGUS_JUMP, 6, 3) def _checkConstantCondition(code, topOfStack, ifFalse, nextIsPop): # don't warn when doing (test and 'true' or 'false') # still warn when doing (test and None or 'false') # since 2.7, instead of JUMP_IF_x/POP_TOP, we have POP_JUMP_IF_x, so # the next opcode is not POP_TOP, but a possible LOAD_CONST already. candidate = 0 if nextIsPop: candidate = 1 if ifFalse or not OP.LOAD_CONST(code.nextOpInfo(candidate)[0]) or \ not topOfStack.data or topOfStack.type is types.NoneType: if not _shouldIgnoreBogusJumps(code): code.addWarning(msgs.CONSTANT_CONDITION % utils.safestr(topOfStack)) def _jump_conditional(oparg, operand, codeSource, code, ifFalse, nextIsPop): # FIXME: this doesn't work in 2.3+ since constant conditions # are optimized away by the compiler. if code.stack: topOfStack = code.stack[-1] if (topOfStack.const or topOfStack.type is types.NoneType) and \ cfg().constantConditions and \ (topOfStack.data != 1 or cfg().constant1): _checkConstantCondition(code, topOfStack, ifFalse, nextIsPop) if _is_unreachable(code, topOfStack, code.label, ifFalse): code.removeBranch(code.label) _jump(oparg, operand, codeSource, code) # JUMP_IF_FALSE(delta) # If TOS is false, increment the bytecode counter by delta. TOS is not changed. def _JUMP_IF_FALSE(oparg, operand, codeSource, code): _jump_conditional(oparg, operand, codeSource, code, 1, 1) def _JUMP_IF_TRUE(oparg, operand, codeSource, code): _jump_conditional(oparg, operand, codeSource, code, 0, 1) def _JUMP_FORWARD(oparg, operand, codeSource, code): _jump(oparg, operand, codeSource, code) code.remove_unreachable_code(code.label) # POP_JUMP_IF_FALSE(target) # If TOS is false, sets the bytecode counter to target. TOS is popped. def _POP_JUMP_IF_FALSE(oparg, operand, codeSource, code): _jump_conditional(oparg, operand, codeSource, code, 1, 0) _pop(oparg, operand, codeSource, code) def _POP_JUMP_IF_TRUE(oparg, operand, codeSource, code): _jump_conditional(oparg, operand, codeSource, code, 0, 0) _pop(oparg, operand, codeSource, code) def _JUMP_IF_FALSE_OR_POP(oparg, operand, codeSource, code): # FIXME: can the next one still be a pop ? _jump_conditional(oparg, operand, codeSource, code, 1, 0) # FIXME: should we really POP here ? We don't know whether the condition # is true or false... _pop(oparg, operand, codeSource, code) def _JUMP_IF_TRUE_OR_POP(oparg, operand, codeSource, code): _jump_conditional(oparg, operand, codeSource, code, 0, 0) # FIXME: same here _pop(oparg, operand, codeSource, code) def _RETURN_VALUE(oparg, operand, codeSource, code) : if not codeSource.calling_code : code.addReturn() def _EXEC_STMT(oparg, operand, codeSource, code) : if cfg().usesExec : if code.stack and code.stack[-1].isNone() : code.addWarning(msgs.USES_GLOBAL_EXEC) else : code.addWarning(msgs.USES_EXEC) def _YIELD_VALUE(oparg, operand, codeSource, code) : # FIXME: any kind of checking we need to do on a yield ? globals ? code.popStack() def _checkStrException(code, varType, item): if varType is types.StringType: code.addWarning(msgs.RAISE_STR_EXCEPTION % item.data) def _RAISE_VARARGS(oparg, operand, codeSource, code) : code.addRaise() if not cfg().badExceptions: return if oparg > 0 and len(code.stack) >= oparg: item = code.stack[-oparg] if item.type not in (Stack.TYPE_FUNC_RETURN, Stack.TYPE_UNKNOWN): if item.type is Stack.TYPE_GLOBAL: e, is_str = _getExceptionInfo(codeSource, item) if is_str: _checkStrException(code, e.type, item) elif e is not None and not _isexception(e): code.addWarning(msgs.RAISE_BAD_EXCEPTION % item.data) else: _checkStrException(code, item.getType(code.typeMap), item) def _SET_ADD(oparg, operand, codeSource, code): code.popStackItems(1) def _MAP_ADD(oparg, operand, codeSource, code): code.popStackItems(2) def _empty(oparg, operand, codeSource, code): pass def _unimplemented(oparg, operand, codeSource, code): raise NotImplementedError('No DISPATCH member for operand') # these op codes do not interact with the stack _PRINT_NEWLINE = _empty _LOAD_LOCALS = _empty _POP_BLOCK = _empty _BREAK_LOOP = _empty _SETUP_LOOP = _empty _CONTINUE_LOOP = _empty _STORE_DEREF = _empty _STOP_CODE = _empty _NOP = _empty # FIXME: all these could use an implementation _INPLACE_FLOOR_DIVIDE = _INPLACE_TRUE_DIVIDE = _unimplemented _EXTENDED_ARG = _unimplemented _PRINT_EXPR = _unimplemented _DELETE_SLICE0 = _unimplemented _STORE_SLICE3 = _unimplemented # new in 2.7 _BUILD_SET = _unimplemented # dispatched from pychecker/warn.py DISPATCH = [ None ] * 256 DISPATCH[ 0] = _STOP_CODE DISPATCH[ 1] = _POP_TOP DISPATCH[ 2] = _ROT_TWO DISPATCH[ 3] = _ROT_THREE DISPATCH[ 4] = _DUP_TOP DISPATCH[ 5] = _ROT_FOUR DISPATCH[ 9] = _NOP # since Python 2.4 DISPATCH[ 10] = _UNARY_POSITIVE DISPATCH[ 11] = _UNARY_NEGATIVE DISPATCH[ 12] = _UNARY_NOT DISPATCH[ 13] = _UNARY_CONVERT DISPATCH[ 15] = _UNARY_INVERT DISPATCH[ 18] = _LIST_APPEND DISPATCH[ 19] = _BINARY_POWER DISPATCH[ 20] = _BINARY_MULTIPLY DISPATCH[ 21] = _BINARY_DIVIDE DISPATCH[ 22] = _BINARY_MODULO DISPATCH[ 23] = _BINARY_ADD DISPATCH[ 24] = _BINARY_SUBTRACT DISPATCH[ 25] = _BINARY_SUBSCR DISPATCH[ 26] = _BINARY_FLOOR_DIVIDE DISPATCH[ 27] = _BINARY_TRUE_DIVIDE DISPATCH[ 28] = _INPLACE_FLOOR_DIVIDE DISPATCH[ 29] = _INPLACE_TRUE_DIVIDE DISPATCH[ 30] = _SLICE0 DISPATCH[ 31] = _SLICE1 DISPATCH[ 32] = _SLICE2 DISPATCH[ 33] = _SLICE3 DISPATCH[ 40] = _STORE_SLICE0 DISPATCH[ 41] = _STORE_SLICE1 DISPATCH[ 42] = _STORE_SLICE2 DISPATCH[ 43] = _STORE_SLICE3 DISPATCH[ 50] = _DELETE_SLICE0 DISPATCH[ 51] = _DELETE_SLICE1 DISPATCH[ 52] = _DELETE_SLICE2 DISPATCH[ 53] = _DELETE_SLICE3 DISPATCH[ 54] = _STORE_MAP # Since Python 2.6 DISPATCH[ 55] = _BINARY_ADD # INPLACE DISPATCH[ 56] = _BINARY_SUBTRACT # INPLACE DISPATCH[ 57] = _BINARY_MULTIPLY # INPLACE DISPATCH[ 58] = _BINARY_DIVIDE # INPLACE DISPATCH[ 59] = _BINARY_MODULO # INPLACE DISPATCH[ 60] = _STORE_SUBSCR DISPATCH[ 61] = _DELETE_SUBSCR DISPATCH[ 62] = _BINARY_LSHIFT DISPATCH[ 63] = _BINARY_RSHIFT DISPATCH[ 64] = _BINARY_AND DISPATCH[ 65] = _BINARY_XOR DISPATCH[ 66] = _BINARY_OR DISPATCH[ 67] = _BINARY_POWER # INPLACE DISPATCH[ 68] = _GET_ITER DISPATCH[ 70] = _PRINT_EXPR DISPATCH[ 71] = _PRINT_ITEM DISPATCH[ 72] = _PRINT_NEWLINE DISPATCH[ 73] = _PRINT_ITEM_TO DISPATCH[ 74] = _PRINT_NEWLINE_TO DISPATCH[ 75] = _BINARY_LSHIFT # INPLACE DISPATCH[ 76] = _BINARY_RSHIFT # INPLACE DISPATCH[ 77] = _BINARY_AND # INPLACE DISPATCH[ 78] = _BINARY_XOR # INPLACE DISPATCH[ 79] = _BINARY_OR # INPLACE DISPATCH[ 80] = _BREAK_LOOP DISPATCH[ 81] = _WITH_CLEANUP # Since Python 2.5 DISPATCH[ 82] = _LOAD_LOCALS DISPATCH[ 83] = _RETURN_VALUE DISPATCH[ 84] = _IMPORT_STAR DISPATCH[ 85] = _EXEC_STMT DISPATCH[ 86] = _YIELD_VALUE DISPATCH[ 87] = _POP_BLOCK DISPATCH[ 88] = _END_FINALLY DISPATCH[ 89] = _BUILD_CLASS DISPATCH[ 90] = _STORE_NAME DISPATCH[ 91] = _DELETE_NAME DISPATCH[ 92] = _UNPACK_SEQUENCE DISPATCH[ 93] = _FOR_ITER # changed from no arguments to taking an argument and 18 to 94 in 2.7 # Python svn revision 67818 DISPATCH[ 94] = _LIST_APPEND_2_7 DISPATCH[ 95] = _STORE_ATTR DISPATCH[ 96] = _DELETE_ATTR DISPATCH[ 97] = _STORE_GLOBAL DISPATCH[ 98] = _DELETE_GLOBAL DISPATCH[ 99] = _DUP_TOPX DISPATCH[100] = _LOAD_CONST DISPATCH[101] = _LOAD_NAME DISPATCH[102] = _BUILD_TUPLE DISPATCH[103] = _BUILD_LIST if utils.pythonVersion() >= utils.PYTHON_2_7: DISPATCH[104] = _BUILD_SET DISPATCH[105] = _BUILD_MAP DISPATCH[106] = _LOAD_ATTR DISPATCH[107] = _COMPARE_OP DISPATCH[108] = _IMPORT_NAME DISPATCH[109] = _IMPORT_FROM else: DISPATCH[104] = _BUILD_MAP DISPATCH[105] = _LOAD_ATTR DISPATCH[106] = _COMPARE_OP DISPATCH[107] = _IMPORT_NAME DISPATCH[108] = _IMPORT_FROM DISPATCH[110] = _JUMP_FORWARD if utils.pythonVersion() >= utils.PYTHON_2_7: DISPATCH[111] = _POP_JUMP_IF_FALSE DISPATCH[112] = _POP_JUMP_IF_TRUE else: DISPATCH[111] = _JUMP_IF_FALSE DISPATCH[112] = _JUMP_IF_TRUE DISPATCH[113] = _JUMP_ABSOLUTE if utils.pythonVersion() >= utils.PYTHON_2_7: DISPATCH[114] = _JUMP_IF_FALSE_OR_POP DISPATCH[115] = _JUMP_IF_TRUE_OR_POP else: DISPATCH[114] = _FOR_LOOP DISPATCH[116] = _LOAD_GLOBAL DISPATCH[119] = _CONTINUE_LOOP DISPATCH[120] = _SETUP_LOOP DISPATCH[121] = _SETUP_EXCEPT DISPATCH[122] = _SETUP_FINALLY DISPATCH[124] = _LOAD_FAST DISPATCH[125] = _STORE_FAST DISPATCH[126] = _DELETE_FAST DISPATCH[127] = _LINE_NUM DISPATCH[130] = _RAISE_VARARGS DISPATCH[131] = _CALL_FUNCTION DISPATCH[132] = _MAKE_FUNCTION DISPATCH[133] = _BUILD_SLICE DISPATCH[134] = _MAKE_CLOSURE DISPATCH[135] = _LOAD_CLOSURE DISPATCH[136] = _LOAD_DEREF DISPATCH[137] = _STORE_DEREF DISPATCH[140] = _CALL_FUNCTION_VAR DISPATCH[141] = _CALL_FUNCTION_KW DISPATCH[142] = _CALL_FUNCTION_VAR_KW # changed from 143 to 145 in 2.7, because 143 is now _SETUP_WITH # Python svn revision 72912 if utils.pythonVersion() >= utils.PYTHON_2_7: DISPATCH[143] = _SETUP_WITH DISPATCH[145] = _EXTENDED_ARG else: DISPATCH[143] = _EXTENDED_ARG # Added in 2.7 # Python svn revision 77422 DISPATCH[146] = _SET_ADD DISPATCH[147] = _MAP_ADD pychecker-0.8.19/pychecker/OptionTypes.py0000644000076400007640000000663611511444550020005 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 import Tkinter def bool(value): if value: return 1 return 0 class Base: "Base class for all OptionTypes" def __init__(self, name, default): self._name = name self._default = default self._var = None def name(self): return self._name def set(self, value): self._var.set(value) class Boolean(Base): "A option type for editing boolean values" def __init__(self, name, default): Base.__init__(self, name, default) def field(self, w): self._var = Tkinter.BooleanVar() if self._default: self._var.set(1) else: self._var.set(0) frame = Tkinter.Frame(w, name = self._name + "Frame") result = Tkinter.Checkbutton(frame, name=self._name, text=self._name, variable=self._var) result.grid(sticky=Tkinter.W) frame.columnconfigure(0, weight=1) return frame def arg(self): if bool(self._var.get()) != bool(self._default): if bool(self._var.get()): return "--" + self._name return "--no-" + self._name return None class Number(Base): "OptionType for editing numbers" def __init__(self, name, default): Base.__init__(self, name, default) def field(self, w): self._var = Tkinter.IntVar() self._var.set(self._default) frame = Tkinter.Frame(w, name = self._name + "Frame") label = Tkinter.Label(frame, text=self._name + ":") label.grid(row=0, column=0, sticky=Tkinter.W) entry = Tkinter.Entry(frame, name=self._name, textvariable=self._var, width=4) entry.grid(row=0, column=1, sticky=Tkinter.E) for i in range(2): frame.columnconfigure(i, weight=1) return frame def arg(self): if self._var.get() != self._default: return "--%s=%d" % (self._name, self._var.get()) return None class Text(Base): "OptionType for editing a little bit of text" def __init__(self, name, default): Base.__init__(self, name, default) def width(self): return int(min(15, len(self._default) * 1.20)) def field(self, w): self._var = Tkinter.StringVar() self._var.set(self._default) frame = Tkinter.Frame(w, name = self._name + "Frame") label = Tkinter.Label(frame, text=self._name + ":") label.grid(row=0, column=0, sticky=Tkinter.W) entry = Tkinter.Entry(frame, name=self._name, textvariable=self._var, width=self.width()) entry.grid(row=0, column=1, sticky=Tkinter.E) for i in range(2): frame.columnconfigure(i, weight=1) return frame def arg(self): if self._var.get() != self._default: return "--%s=%s" % (self._name, self._var.get()) return None def join(list): import string return string.join(list, ", ") class List(Text): "OptionType for editing a list of values" def __init__(self, name, default): Text.__init__(self, name, join(default)) def set(self, value): self._var.set(join(value)) pychecker-0.8.19/pychecker/Stack.py0000644000076400007640000001244611512036115016544 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2002, MetaSlash Inc. All rights reserved. """ Module to hold manipulation of elements on the stack. """ import types from pychecker import utils DATA_UNKNOWN = "-unknown-" LOCALS = 'locals' # These should really be defined by subclasses TYPE_UNKNOWN = "-unknown-" TYPE_FUNC_RETURN = "-return-value-" TYPE_ATTRIBUTE = "-attribute-" TYPE_COMPARISON = "-comparison-" TYPE_GLOBAL = "-global-" TYPE_EXCEPT = "-except-" class Item: """ Representation of data on the stack @ivar is_really_string: whether the stack item really is a string. """ def __init__(self, data, dataType, const=0, length=0): """ @param data: the actual data of the stack item @type dataType: type @param const: whether the item is a constant or not @type const: int @type length: int """ self.data = data self.type = dataType self.const = const self.length = length self.is_really_string = 0 def __str__(self) : if type(self.data) == types.TupleType: value = '(' for item in self.data: value = value + utils.safestr(item) + ', ' # strip off the ', ' for multiple items if len(self.data) > 1: value = value[:-2] return value + ')' return utils.safestr(self.data) def __repr__(self): return 'Stack Item: (%r, %r, %d)' % (self.data, self.type, self.const) def isNone(self): return (self.type != TYPE_UNKNOWN and self.data is None or (self.data == 'None' and not self.const)) def isImplicitNone(self) : return self.data is None and self.const def isMethodCall(self, c, methodArgName): return self.type == TYPE_ATTRIBUTE and c != None and \ len(self.data) == 2 and self.data[0] == methodArgName def isLocals(self): return self.type == types.DictType and self.data == LOCALS def setStringType(self, value = types.StringType): self.is_really_string = value == types.StringType def getType(self, typeMap): """ @type typeMap: dict of str -> list of str or L{pcmodules.Class} """ # FIXME: looks like StringType is used for real strings but also # for names of objects. Couldn't this be split to avoid # self.is_really_string ? if self.type != types.StringType or self.is_really_string: return self.type # FIXME: I assert here because there were if's to this effect, # and a return of type(self.data). Remove this assert later. assert type(self.data) == types.StringType # it's a StringType but not really a string # if it's constant, return type of data if self.const: return types.StringType # it's a non-constant StringType, so treat it as the name of a token # and look up the actual type in the typeMap localTypes = typeMap.get(self.data, []) if len(localTypes) == 1: return localTypes[0] return TYPE_UNKNOWN def getName(self): if self.type == TYPE_ATTRIBUTE and type(self.data) != types.StringType: strValue = "" # convert the tuple into a string ('self', 'data') -> self.data for item in self.data: strValue = '%s.%s' % (strValue, utils.safestr(item)) return strValue[1:] return utils.safestr(self.data) def addAttribute(self, attr): if type(self.data) == types.TupleType: self.data = self.data + (attr,) else: self.data = (self.data, attr) self.type = TYPE_ATTRIBUTE # FIXME: I haven't seen makeDict with anything else than (), 1 def makeDict(values=(), const=1): """ @param values: the values to make a dict out of @type values: FIXME: tuple of L{Item} ? @param const: whether the dict is constant @returns: A Stack.Item representing a dict @rtype: L{Item} """ values = tuple(values) if not values: values = ('', ) return Item(values, types.DictType, const, len(values)) def makeTuple(values=(), const=1): """ @param values: the values to make a tuple out of @type values: tuple of L{Item} @param const: whether the tuple is constant @returns: A Stack.Item representing a tuple @rtype: L{Item} """ return Item(tuple(values), types.TupleType, const, len(values)) # FIXME: I haven't seen makeList with anything else than const=1 def makeList(values=[], const=1): """ @param values: the values to make a list out of @type values: list of L{Item} @param const: whether the list is constant @returns: A Stack.Item representing a list @rtype: L{Item} """ return Item(values, types.ListType, const, len(values)) def makeFuncReturnValue(stackValue, argCount) : data = DATA_UNKNOWN # vars() without params == locals() if stackValue.type == TYPE_GLOBAL and \ (stackValue.data == LOCALS or (argCount == 0 and stackValue.data == 'vars')) : data = LOCALS return Item(data, TYPE_FUNC_RETURN) def makeComparison(stackItems, comparison) : return Item((stackItems[0], comparison, stackItems[1]), TYPE_COMPARISON) pychecker-0.8.19/pychecker/Config.py0000644000076400007640000005042111512036115016677 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2004, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Configuration information for checker. """ import sys import os import getopt import string import re import time def get_warning_levels(): import types from pychecker import msgs WarningClass = msgs.WarningClass result = {} for name in vars(msgs).keys(): obj = getattr(msgs, name) if (obj is not WarningClass and isinstance(obj, types.ClassType) and issubclass(obj, WarningClass)): result[name.capitalize()] = obj return result _WARNING_LEVELS = get_warning_levels() _RC_FILE = ".pycheckrc" CHECKER_VAR = '__pychecker__' _VERSION = '0.8.18' _DEFAULT_BLACK_LIST = [ "Tkinter", "wxPython", "gtk", "GTK", "GDK", ] _DEFAULT_VARIABLE_IGNORE_LIST = [ '__version__', '__warningregistry__', '__all__', '__credits__', '__test__', '__author__', '__email__', '__revision__', '__id__', '__copyright__', '__license__', '__date__', ] _DEFAULT_UNUSED_LIST = [ '_', 'empty', 'unused', 'dummy', ] _DEFAULT_MISSING_ATTRS_LIST = [] # _OPTIONS = ( # (categoryName, [ # (shortArg, useValue, longArg, member, description), # ... # ]), # ... # ) _OPTIONS = ( ('Major Options', [ ('', 0, 'only', 'only', 'only warn about files passed on the command line'), ('e', 1, 'level', None, 'the maximum error level of warnings to be displayed'), ('#', 1, 'limit', 'limit', 'the maximum number of warnings to be displayed'), ('F', 1, 'config', None, 'specify .pycheckrc file to use'), ('', 0, 'quixote', None, 'support Quixote\'s PTL modules'), ('', 1, 'evil', 'evil', 'list of evil C extensions that crash the interpreter'), ('', 0, 'keepgoing', 'ignoreImportErrors', 'ignore import errors'), ]), ('Error Control', [ ('i', 0, 'import', 'importUsed', 'unused imports'), ('k', 0, 'pkgimport', 'packageImportUsed', 'unused imports from __init__.py'), ('M', 0, 'reimportself', 'reimportSelf', 'module imports itself'), ('X', 0, 'reimport', 'moduleImportErrors', 'reimporting a module'), ('x', 0, 'miximport', 'mixImport', 'module does import and from ... import'), ('l', 0, 'local', 'localVariablesUsed', 'unused local variables, except tuples'), ('t', 0, 'tuple', 'unusedLocalTuple', 'all unused local variables, including tuples'), ('9', 0, 'members', 'membersUsed', 'all unused class data members'), ('v', 0, 'var', 'allVariablesUsed', 'all unused module variables'), ('p', 0, 'privatevar', 'privateVariableUsed', 'unused private module variables'), ('g', 0, 'allglobals', 'reportAllGlobals', 'report each occurrence of global warnings'), ('n', 0, 'namedargs', 'namedArgs', 'functions called with named arguments (like keywords)'), ('a', 0, 'initattr', 'onlyCheckInitForMembers', 'Attributes (members) must be defined in __init__()'), ('I', 0, 'initsubclass', 'initDefinedInSubclass', 'Subclass.__init__() not defined'), ('u', 0, 'callinit', 'baseClassInitted', 'Baseclass.__init__() not called'), ('0', 0, 'abstract', 'abstractClasses', 'Subclass needs to override methods that only throw exceptions'), ('N', 0, 'initreturn', 'returnNoneFromInit', 'Return None from __init__()'), ('8', 0, 'unreachable', 'unreachableCode', 'unreachable code'), ('2', 0, 'constCond', 'constantConditions', 'a constant is used in a conditional statement'), ('1', 0, 'constant1', 'constant1', '1 is used in a conditional statement (if 1: or while 1:)'), ( '', 0, 'stringiter', 'stringIteration', 'check if iterating over a string'), ( '', 0, 'stringfind', 'stringFind', 'check improper use of string.find()'), ('A', 0, 'callattr', 'callingAttribute', 'Calling data members as functions'), ('y', 0, 'classattr', 'classAttrExists', 'class attribute does not exist'), ('S', 1, 'self', 'methodArgName', 'First argument to methods'), ('', 1, 'classmethodargs', 'classmethodArgNames', 'First argument to classmethods'), ('T', 0, 'argsused', 'argumentsUsed', 'unused method/function arguments'), ('z', 0, 'varargsused', 'varArgumentsUsed', 'unused method/function variable arguments'), ('G', 0, 'selfused', 'ignoreSelfUnused', 'ignore if self is unused in methods'), ('o', 0, 'override', 'checkOverridenMethods', 'check if overridden methods have the same signature'), ('', 0, 'special', 'checkSpecialMethods', 'check if __special__ methods exist and have the correct signature'), ('U', 0, 'reuseattr', 'redefiningFunction', 'check if function/class/method names are reused'), ('Y', 0, 'positive', 'unaryPositive', 'check if using unary positive (+) which is usually meaningless'), ('j', 0, 'moddefvalue', 'modifyDefaultValue', 'check if modify (call method) on a parameter that has a default value'), ( '', 0, 'changetypes', 'inconsistentTypes', 'check if variables are set to different types'), ( '', 0, 'unpack', 'unpackNonSequence', 'check if unpacking a non-sequence'), ( '', 0, 'unpacklen', 'unpackLength', 'check if unpacking sequence with the wrong length'), ( '', 0, 'badexcept', 'badExceptions', 'check if raising or catching bad exceptions'), ('4', 0, 'noeffect', 'noEffect', 'check if statement appears to have no effect'), ('', 0, 'modulo1', 'modulo1', 'check if using (expr % 1), it has no effect on integers and strings'), ('', 0, 'isliteral', 'isLiteral', "check if using (expr is const-literal), doesn't always work on integers and strings"), ('', 0, 'constattr', 'constAttr', "check if a constant string is passed to getattr()/setattr()"), ]), ('Possible Errors', [ ('r', 0, 'returnvalues', 'checkReturnValues', 'check consistent return values'), ('C', 0, 'implicitreturns', 'checkImplicitReturns', 'check if using implict and explicit return values'), ('O', 0, 'objattrs', 'checkObjectAttrs', 'check that attributes of objects exist'), ('7', 0, 'slots', 'slots', 'various warnings about incorrect usage of __slots__'), ('3', 0, 'properties', 'classicProperties', 'using properties with classic classes'), ( '', 0, 'emptyslots', 'emptySlots', 'check if __slots__ is empty'), ('D', 0, 'intdivide', 'intDivide', 'check if using integer division'), ('w', 0, 'shadow', 'shadows', 'check if local variable shadows a global'), ('s', 0, 'shadowbuiltin', 'shadowBuiltins', 'check if a variable shadows a builtin'), ]), ('Security', [ ( '', 0, 'input', 'usesInput', 'check if input() is used'), ('6', 0, 'exec', 'usesExec', 'check if the exec statement is used'), ]), ('Suppressions', [ ('q', 0, 'stdlib', 'ignoreStandardLibrary', 'ignore warnings from files under standard library'), ('b', 1, 'blacklist', 'blacklist', 'ignore warnings from the list of modules\n\t\t\t'), ('Z', 1, 'varlist', 'variablesToIgnore', 'ignore global variables not used if name is one of these values\n\t\t\t'), ('E', 1, 'unusednames', 'unusedNames', 'ignore unused locals/arguments if name is one of these values\n\t\t\t'), ('', 1, 'missingattrs', 'missingAttrs', 'ignore missing class attributes if name is one of these values\n\t\t\t'), ( '', 0, 'deprecated', 'deprecated', 'ignore use of deprecated modules/functions'), ]), ('Complexity', [ ('L', 1, 'maxlines', 'maxLines', 'maximum lines in a function'), ('B', 1, 'maxbranches', 'maxBranches', 'maximum branches in a function'), ('R', 1, 'maxreturns', 'maxReturns', 'maximum returns in a function'), ('J', 1, 'maxargs', 'maxArgs', 'maximum # of arguments to a function'), ('K', 1, 'maxlocals', 'maxLocals', 'maximum # of locals in a function'), ('5', 1, 'maxrefs', 'maxReferences', 'maximum # of identifier references (Law of Demeter)'), ('m', 0, 'moduledoc', 'noDocModule', 'no module doc strings'), ('c', 0, 'classdoc', 'noDocClass', 'no class doc strings'), ('f', 0, 'funcdoc', 'noDocFunc', 'no function/method doc strings'), ]), ('Debug', [ ( '', 0, 'rcfile', None, 'print a .pycheckrc file generated from command line args'), ('P', 0, 'printparse', 'printParse', 'print internal checker parse structures'), ('d', 0, 'debug', 'debug', 'turn on debugging for checker'), ('', 0, 'findevil', 'findEvil', 'print each class object to find one that crashes'), ('Q', 0, 'quiet', 'quiet', 'turn off all output except warnings'), ('V', 0, 'version', None, 'print the version of PyChecker and exit'), ]) ) def init() : GET_OPT_VALUE = (('', ''), (':', '='),) shortArgs, longArgs = "", [] for _, group in _OPTIONS : for opt in group: optStr = GET_OPT_VALUE[opt[1]] shortArgs = shortArgs + opt[0] + optStr[0] longArgs.append(opt[2] + optStr[1]) longArgs.append('no-' + opt[2] + optStr[1]) options = {} for _, group in _OPTIONS : for opt in group: shortArg, useValue, longArg, member, description = opt if shortArg != '' : options['-' + shortArg] = opt options['--no-' + longArg] = options['--' + longArg] = opt return shortArgs, longArgs, options _SHORT_ARGS, _LONG_ARGS, _OPTIONS_DICT = init() def _getRCfiles(filename) : """Return a list of .rc filenames, on Windows use the current directory on UNIX use the user's home directory """ files = [] home = os.environ.get('HOME') if home : files.append(home + os.sep + filename) files.append(filename) return files _RC_FILE_HEADER = '''# # .pycheckrc file created by PyChecker v%s @ %s # # It should be placed in your home directory (value of $HOME). # If $HOME is not set, it will look in the current directory. # ''' def outputRc(cfg) : output = _RC_FILE_HEADER % (_VERSION, time.ctime(time.time())) for name, group in _OPTIONS : for opt in group: shortArg, useValue, longArg, member, description = opt if member is None : continue description = string.strip(description) value = getattr(cfg, member) optStr = '# %s\n%s = %s\n\n' % (description, member, `value`) output = output + optStr return output class UsageError(Exception) : """Exception to indicate that the application should exit due to command line usage error.""" _SUPPRESSIONS_ERR = \ '''\nWarning, error processing defaults file: %s \%s must be a dictionary ({}) -- ignoring suppressions\n''' def _getSuppressions(name, dict, filename) : suppressions = dict.get(name, {}) if type(suppressions) != type({}) : print _SUPPRESSIONS_ERR % (filename, name) suppressions = {} return suppressions class Config : "Hold configuration information" def __init__(self) : "Initialize configuration with default values." # files to process (typically from cmd line) self.files = {} self.debug = 0 self.quiet = 0 self.only = 0 self.level = 0 self.limit = 10 self.ignoreImportErrors = 0 self.onlyCheckInitForMembers = 0 self.printParse = 0 self.quixote = 0 self.evil = [] self.findEvil = 0 self.noDocModule = 0 self.noDocClass = 0 self.noDocFunc = 0 self.reportAllGlobals = 0 self.allVariablesUsed = 0 self.privateVariableUsed = 1 self.membersUsed = 0 self.importUsed = 1 self.reimportSelf = 1 self.moduleImportErrors = 1 self.mixImport = 1 self.packageImportUsed = 1 self.localVariablesUsed = 1 self.unusedLocalTuple = 0 self.initDefinedInSubclass = 0 self.baseClassInitted = 1 self.abstractClasses = 1 self.callingAttribute = 0 self.classAttrExists = 1 self.namedArgs = 0 self.returnNoneFromInit = 1 self.unreachableCode = 0 self.constantConditions = 1 self.constant1 = 0 self.stringIteration = 1 self.inconsistentTypes = 0 self.unpackNonSequence = 1 self.unpackLength = 1 self.badExceptions = 1 self.noEffect = 1 self.deprecated = 1 self.modulo1 = 1 self.isLiteral = 1 self.stringFind = 1 self.unusedNames = _DEFAULT_UNUSED_LIST self.variablesToIgnore = _DEFAULT_VARIABLE_IGNORE_LIST self.blacklist = _DEFAULT_BLACK_LIST self.missingAttrs = _DEFAULT_MISSING_ATTRS_LIST self.ignoreStandardLibrary = 0 self.methodArgName = 'self' self.classmethodArgNames = ['cls', 'klass'] self.checkOverridenMethods = 1 self.checkSpecialMethods = 1 self.argumentsUsed = 1 self.varArgumentsUsed = 1 self.ignoreSelfUnused = 0 self.redefiningFunction = 1 self.maxLines = 200 self.maxBranches = 50 self.maxReturns = 10 self.maxArgs = 10 self.maxLocals = 40 self.maxReferences = 5 self.slots = 1 self.emptySlots = 1 self.classicProperties = 1 self.checkObjectAttrs = 1 self.checkReturnValues = 1 self.checkImplicitReturns = 1 self.intDivide = 1 self.shadows = 1 self.shadowBuiltins = 1 self.unaryPositive = 1 self.modifyDefaultValue = 1 self.usesExec = 0 self.usesInput = 1 self.constAttr = 1 def loadFile(self, filename): """ Load suppressions from the given file. @type filename: str @rtype: tuple of (dict, dict) """ suppressions = {} suppressionRegexs = {} try: tmpGlobals, tmpLocals = {}, {} execfile(filename, tmpGlobals, tmpLocals) suppressions = _getSuppressions('suppressions', tmpLocals, filename) regexs = _getSuppressions('suppressionRegexs', tmpLocals, filename) # debug them here, since the options in tmpLocals can turn off # debugging again # We don't have an active config here yet. Push ourselves, # since we first got loaded with command line arguments, # and so -d shows these suppression messages from pychecker import utils utils.initConfig(self) if suppressions: utils.debug('Loaded %d suppressions from %s', len(suppressions), filename) if suppressionRegexs: utils.debug('Loaded %d suppression regexs from %s', len(suppressionRegexs), filename) utils.popConfig() # now set our attributes based on the locals for key, value in tmpLocals.items(): if self.__dict__.has_key(key): self.__dict__[key] = value elif key not in ('suppressions', 'suppressionRegexs') and \ key[0] != '_': print "Warning, option (%s) doesn't exist, ignoring" % key for regex_str in regexs.keys(): regex = re.compile(regex_str) suppressionRegexs[regex] = regexs[regex_str] except IOError: pass # ignore if no file except Exception, detail: print "Warning, error loading defaults file:", filename, detail return suppressions, suppressionRegexs def loadFiles(self, filenames, oldSuppressions = None) : """ @type filenames: list of str @type oldSuppression: tuple of (dict, dict) @rtype: tuple of (dict, dict) """ if oldSuppressions is None : oldSuppressions = ({}, {}) suppressions = oldSuppressions[0] suppressionRegexs = oldSuppressions[1] for filename in filenames: updates = self.loadFile(filename) suppressions.update(updates[0]) suppressionRegexs.update(updates[1]) return suppressions, suppressionRegexs def processArgs(self, argList, otherConfigFiles = None) : try : args, files = getopt.getopt(argList, _SHORT_ARGS, _LONG_ARGS) except getopt.error, detail : raise UsageError, detail # setup files from cmd line for f in files: self.files[os.path.abspath(f)] = 1 if otherConfigFiles is None: otherConfigFiles = [] for arg, value in args : shortArg, useValue, longArg, member, description = _OPTIONS_DICT[arg] if member == None : # FIXME: this whole block is a hack if longArg == 'rcfile' : sys.stdout.write(outputRc(self)) continue elif longArg == 'quixote' : import quixote quixote.enable_ptl() self.quixote = 1 continue elif longArg == 'config' : otherConfigFiles.append(value) continue elif longArg == 'version' : # FIXME: it would be nice to define this in only one place print _VERSION sys.exit(0) elif longArg == 'level': normalizedValue = value.capitalize() if not _WARNING_LEVELS.has_key(normalizedValue): sys.stderr.write('Invalid warning level (%s). ' 'Must be one of: %s\n' % (value, _WARNING_LEVELS.keys())) sys.exit(1) self.level = _WARNING_LEVELS[normalizedValue].level continue elif value : newValue = value memberType = type(getattr(self, member)) if memberType == type(0) : newValue = int(newValue) elif memberType == type([]) : newValue = string.split(newValue, ',') elif memberType == type('') and \ newValue[0] in '\'"': try: newValue = eval(newValue) except: msg = 'Invalid option parameter: %s for %s\n' % \ (`newValue`, arg) sys.stderr.write(msg) setattr(self, member, newValue) elif arg[0:2] == '--' : setattr(self, member, arg[2:5] != 'no-') else : # for shortArgs we only toggle setattr(self, member, not getattr(self, member)) if self.variablesToIgnore.count(CHECKER_VAR) <= 0 : self.variablesToIgnore.append(CHECKER_VAR) return files def printArg(shortArg, longArg, description, defaultValue, useValue) : defStr = '' shortArgStr = ' ' if shortArg: shortArgStr = '-%s,' % shortArg if defaultValue != None : if not useValue : if defaultValue : defaultValue = 'on' else : defaultValue = 'off' defStr = ' [%s]' % defaultValue args = "%s --%s" % (shortArgStr, longArg) print " %-18s %s%s" % (args, description, defStr) def usage(cfg = None) : print "Usage for: checker.py [options] PACKAGE ...\n" print " PACKAGEs can be a python package, module or filename\n" print "Long options can be preceded with no- to turn off (e.g., no-namedargs)\n" print "Category" print " Options: Change warning for ... [default value]" if cfg is None : cfg = Config() for name, group in _OPTIONS : print print name + ":" for opt in group: shortArg, useValue, longArg, member, description = opt defValue = None if member != None : defValue = cfg.__dict__[member] printArg(shortArg, longArg, description, defValue, useValue) def setupFromArgs(argList): """ @param argList: the list of command-line arguments @type argList: list of str @rtype: list of L{Config}, list of str, tuple of (dict, dict) """ cfg = Config() try : otherConfigFiles = [] files = cfg.processArgs(argList, otherConfigFiles) suppressions = cfg.loadFiles(_getRCfiles(_RC_FILE)) if otherConfigFiles: suppressions = cfg.loadFiles(otherConfigFiles, suppressions) return cfg, files, suppressions except UsageError : usage(cfg) raise pychecker-0.8.19/pychecker/OP.py0000644000076400007640000001227511511444550016022 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2004, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Python byte code operations. Very similar to the dis and opcode module, but dis does not exist in Jython, so recreate the small portion we need here. """ from pychecker import utils def LINE_NUM(op): return op == 127 def LOAD_GLOBAL(op): return op == 116 def LOAD_CONST(op): return op == 100 def LOAD_FAST(op): return op == 124 if utils.pythonVersion() >= utils.PYTHON_2_7: def LOAD_ATTR(op): return op == 106 else: def LOAD_ATTR(op): return op == 105 def LOAD_DEREF(op): return op == 136 def STORE_ATTR(op): return op == 95 def POP_TOP(op): return op == 1 if utils.pythonVersion() >= utils.PYTHON_2_7: def IMPORT_FROM(op): return op == 109 else: def IMPORT_FROM(op): return op == 108 def IMPORT_STAR(op): return op == 84 def UNARY_POSITIVE(op): return op == 10 def UNARY_NEGATIVE(op): return op == 11 def UNARY_INVERT(op): return op == 15 def RETURN_VALUE(op): return op == 83 def JUMP_FORWARD(op): return op == 110 def JUMP_ABSOLUTE(op): return op == 113 def FOR_ITER(op): return op == 93 def FOR_LOOP(op): return op == 114 def SETUP_LOOP(op): return op == 120 def BREAK_LOOP(op): return op == 80 def RAISE_VARARGS(op): return op == 130 def POP_BLOCK(op): return op == 87 def END_FINALLY(op): return op == 88 def CALL_FUNCTION(op): return op == 131 def UNPACK_SEQUENCE(op) : "Deal w/Python 1.5.2 (UNPACK_[LIST|TUPLE]) or 2.0 (UNPACK_SEQUENCE)" return op in (92, 93,) if utils.pythonVersion() >= utils.PYTHON_2_7: def IS_CONDITIONAL_JUMP(op): return op in (111, 112, 114, 115) else: def IS_CONDITIONAL_JUMP(op): return op in (111, 112) def IS_NOT(op): return op == 12 HAVE_ARGUMENT = 90 # Opcodes from here have an argument # moved 2 places in 2.7 if utils.pythonVersion() >= utils.PYTHON_2_7: EXTENDED_ARG = 145 else: EXTENDED_ARG = 143 if utils.pythonVersion() >= utils.PYTHON_2_7: _HAS_NAME = (90, 91, 95, 96, 97, 98, 101, 106, 108, 109, 116,) else: _HAS_NAME = (90, 91, 95, 96, 97, 98, 101, 105, 107, 108, 116,) _HAS_LOCAL = (124, 125, 126,) _HAS_CONST = (100,) if utils.pythonVersion() >= utils.PYTHON_2_7: _HAS_COMPARE = (107,) else: _HAS_COMPARE = (106,) if utils.pythonVersion() >= utils.PYTHON_2_7: _HAS_JREL = (93, 110, 120, 121, 122, 143,) _HAS_JABS = (111, 112, 113, 114, 115, 119,) else: # FIXME: 2.6 here has 93 and does not have 114 _HAS_JREL = (110, 111, 112, 114, 120, 121, 122,) _HAS_JABS = (113, 119,) _CMP_OP = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is', 'is not', 'exception match', 'BAD') EXCEPT_COMPARISON = 10 IS_COMPARISON = 8 IN_COMPARISON = 6 NOT_IN_COMPARISON = 7 def getOperand(op, func_code, oparg) : """ Get the actual object the oparg references. @rtype: object """ if op in _HAS_NAME : return func_code.co_names[oparg] elif op in _HAS_LOCAL : return func_code.co_varnames[oparg] elif op in _HAS_CONST : return func_code.co_consts[oparg] elif op in _HAS_COMPARE : return _CMP_OP[oparg] return None def getLabel(op, oparg, i) : if op in _HAS_JREL : return i + oparg elif op in _HAS_JABS : return oparg return None def getInfo(code, index, extended_arg) : """Returns (op, oparg, index, extended_arg) based on code this is a helper function while looping through byte code, refer to the standard module dis.disassemble() for more info""" # get the operation we are performing op = ord(code[index]) index = index + 1 if op >= HAVE_ARGUMENT : # get the argument to the operation oparg = ord(code[index]) + ord(code[index+1])*256 + extended_arg index = index + 2 extended_arg = 0 if op == EXTENDED_ARG : extended_arg = oparg * 65536L else : oparg, extended_arg = 0, 0 return op, oparg, index, extended_arg def initFuncCode(func) : """Returns (func_code, code, i, maxCode, extended_arg) based on func, this is a helper function to setup looping through byte code""" func_code = func.func_code code = func_code.co_code return func_code, code, 0, len(code), 0 def conditional(op): "returns true if the code results in conditional execution" return op in [83, # return 93, # for_iter 111, 112, 114, 115, # conditional jump 121, # setup_exec 130 # raise_varargs ] # this code is here for debugging purposes. # Jython doesn't support dis, so don't rely on it try : import dis name = dis.opname except ImportError : class Name: 'Turn name[x] into x' def __getitem__(self, x): from pychecker import utils return utils.safestr(x) name = Name() pychecker-0.8.19/pychecker/Warning.py0000644000076400007640000000476711511444550017120 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Warning class to hold info about each warning. """ class Warning : """ Class which holds warning information. @ivar file: file where the warning was found. @type file: str @ivar line: line number where the warning was found. @type line: int @type err: L{msgs.WarningClass} """ def __init__(self, file, line, err) : """ @param file: an object from which the file where the warning was found can be derived @type file: L{types.CodeType}, L{function.FakeCode} or str @param line: the line where the warning was found; if file was str, then line will be a code object. @type line: int or L{types.CodeType} or None @type err: L{msgs.WarningClass} """ if hasattr(file, "function") : # file is a function.FakeCode file = file.function.func_code.co_filename elif hasattr(file, "co_filename") : # file is a types.CodeType file = file.co_filename elif hasattr(line, "co_filename") : # file was a str file = line.co_filename if file[:2] == './' : file = file[2:] self.file = file if hasattr(line, "co_firstlineno") : line = line.co_firstlineno if line == None : line = 1 self.line = line self.err = err self.level = err.level def __cmp__(self, warn) : if warn == None : return 1 if not self.file and not self.line: return 1 if self.file != warn.file : return cmp(self.file, warn.file) if self.line != warn.line : return cmp(self.line, warn.line) return cmp(self.err, warn.err) def format(self, removeSysPath=True) : if not self.file and not self.line: return str(self.err) file = self.file if removeSysPath: import sys for path in sys.path: if not path or path == '.': continue if file.startswith(path): file = '[system path]' + file[len(path):] return "%s:%d: %s" % (file, self.line, self.err) def output(self, stream, removeSysPath=True) : stream.write(self.format(removeSysPath) + "\n") pychecker-0.8.19/pychecker/utils.py0000644000076400007640000002123711512036115016635 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2004, MetaSlash Inc. All rights reserved. """ Utility functions. """ import re import sys import os import string import copy import imp import traceback import types from pychecker import msgs from pychecker import Config from pychecker.Warning import Warning VAR_ARGS_BITS = 8 MAX_ARGS_MASK = ((1 << VAR_ARGS_BITS) - 1) INIT = '__init__' LAMBDA = '' GENEXP = '' GENEXP25 = '' # number of instructions to check backwards if it was a return BACK_RETURN_INDEX = 4 _cfg = [] def cfg() : return _cfg[-1] def initConfig(cfg) : _cfg.append(cfg) def pushConfig() : newCfg = copy.copy(cfg()) _cfg.append(newCfg) def popConfig() : del _cfg[-1] def shouldUpdateArgs(operand) : return operand == Config.CHECKER_VAR def updateCheckerArgs(argStr, func, lastLineNum, warnings): """ @param argStr: list of space-separated options, as passed on the command line e.g 'blacklist=wrongname initattr no-classdoc' @type argStr: str @param func: 'suppressions' or code object @type func: str or {function.FakeCode} or {types.CodeType} @param lastLineNum: the last line number of the given function; compare to func.co_firstlineno if exists @type lastLineNum: int or {types.CodeType} @param warnings: list of warnings to append to @type warnings: list of L{Warning} @rtype: int @returns: 1 if the arguments were invalid, 0 if ok. """ try: argList = string.split(argStr) # if func is code, might trigger # TypeError: code.__cmp__(x,y) requires y to be a 'code', not a 'str' if argList and not type(func) == str: debug('func %r: pychecker args %r', func, argStr) # don't require long options to start w/--, we can add that for them for i in range(0, len(argList)): if argList[i][0] != '-': argList[i] = '--' + argList[i] cfg().processArgs(argList) return 1 except Config.UsageError, detail: # this gets triggered when parsing a bad __pychecker__ declaration warn = Warning(func, lastLineNum, msgs.INVALID_CHECKER_ARGS % detail) warnings.append(warn) return 0 def debug(formatString, *args): if cfg().debug: if args: if '%' in formatString: message = formatString % args else: args = [isinstance(a, str) and a or repr(a) for a in args] message = formatString + " " + " ".join(args) else: message = formatString print "DEBUG:", message PYTHON_1_5 = 0x10502 PYTHON_2_0 = 0x20000 PYTHON_2_1 = 0x20100 PYTHON_2_2 = 0x20200 PYTHON_2_3 = 0x20300 PYTHON_2_4 = 0x20400 PYTHON_2_5 = 0x20500 PYTHON_2_6 = 0x20600 PYTHON_2_7 = 0x20700 PYTHON_3_0 = 0x30000 def pythonVersion() : return sys.hexversion >> 8 def startswith(s, substr) : "Ugh, supporting python 1.5 is a pain" return s[0:len(substr)] == substr def endswith(s, substr) : "Ugh, supporting python 1.5 is a pain" return s[-len(substr):] == substr # generic method that can be slapped into any class, thus the self parameter def std_repr(self) : return "<%s at 0x%x: %s>" % (self.__class__.__name__, id(self), safestr(self)) try: unicode, UnicodeError except NameError: class UnicodeError(Exception): pass def safestr(value): try: return str(value) except UnicodeError: return unicode(value) def _q_file(f): # crude hack!!! # imp.load_module requires a real file object, so we can't just # fiddle def lines and yield them import tempfile fd, newfname = tempfile.mkstemp(suffix=".py", text=True) newf = os.fdopen(fd, 'r+') os.unlink(newfname) for line in f: mat = re.match(r'(\s*def\s+\w+\s*)\[(html|plain)\](.*)', line) if mat is None: newf.write(line) else: newf.write(mat.group(1)+mat.group(3)+'\n') newf.seek(0) return newf def _q_find_module(p, path): if not cfg().quixote: return imp.find_module(p, path) else: for direc in path: try: return imp.find_module(p, [direc]) except ImportError: f = os.path.join(direc, p+".ptl") if os.path.exists(f): return _q_file(file(f)), f, ('.ptl', 'U', 1) def findModule(name, moduleDir=None) : """Returns the result of an imp.find_module(), ie, (file, filename, smt) name can be a module or a package name. It is *not* a filename.""" path = sys.path[:] if moduleDir: path.insert(0, moduleDir) packages = string.split(name, '.') for p in packages : # smt = (suffix, mode, type) handle, filename, smt = _q_find_module(p, path) if smt[-1] == imp.PKG_DIRECTORY : try : # package found - read path info from init file m = imp.load_module(p, handle, filename, smt) finally : if handle is not None : handle.close() # importing xml plays a trick, which replaces itself with _xmlplus # both have subdirs w/same name, but different modules in them # we need to choose the real (replaced) version if m.__name__ != p : try : handle, filename, smt = _q_find_module(m.__name__, path) m = imp.load_module(p, handle, filename, smt) finally : if handle is not None : handle.close() new_path = m.__path__ if type(new_path) == types.ListType : new_path = filename if new_path not in path : path.insert(1, new_path) elif smt[-1] != imp.PY_COMPILED: if p is not packages[-1] : if handle is not None : handle.close() raise ImportError, "No module named %s" % packages[-1] return handle, filename, smt # in case we have been given a package to check return handle, filename, smt def _getLineInFile(moduleName, moduleDir, linenum): line = '' handle, filename, smt = findModule(moduleName, moduleDir) if handle is None: return '' try: lines = handle.readlines() line = string.rstrip(lines[linenum - 1]) except (IOError, IndexError): pass handle.close() return line def importError(moduleName, moduleDir=None): exc_type, exc_value, tb = sys.exc_info() # First, try to get a nice-looking name for this exception type. exc_name = getattr(exc_type, '__name__', None) if not exc_name: # either it's a string exception or a user-defined exception class # show string or fully-qualified class name exc_name = safestr(exc_type) # Print a traceback, unless this is an ImportError. ImportError is # presumably the most common import-time exception, so this saves # the clutter of a traceback most of the time. Also, the locus of # the error is usually irrelevant for ImportError, so the lack of # traceback shouldn't be a problem. if exc_type is SyntaxError: # SyntaxErrors are special, we want to control how we format # the output and make it consistent for all versions of Python e = exc_value msg = '%s (%s, line %d)' % (e.msg, e.filename, e.lineno) line = _getLineInFile(moduleName, moduleDir, e.lineno) offset = e.offset if type(offset) is not types.IntType: offset = 0 exc_value = '%s\n %s\n %s^' % (msg, line, ' ' * offset) elif exc_type is not ImportError: sys.stderr.write(" Caught exception importing module %s:\n" % moduleName) try: tbinfo = traceback.extract_tb(tb) except: tbinfo = [] sys.stderr.write(" Unable to format traceback\n") for filename, line, func, text in tbinfo[1:]: sys.stderr.write(" File \"%s\", line %d" % (filename, line)) if func != "?": sys.stderr.write(", in %s()" % func) sys.stderr.write("\n") if text: sys.stderr.write(" %s\n" % text) # And finally print the exception type and value. # Careful formatting exc_value -- can fail for some user exceptions sys.stderr.write(" %s: " % exc_name) try: sys.stderr.write(safestr(exc_value) + '\n') except: sys.stderr.write('**error formatting exception value**\n') pychecker-0.8.19/pychecker/warn.py0000644000076400007640000007725611512036116016461 0ustar thomasthomas# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (c) 2001-2002, MetaSlash Inc. All rights reserved. # Portions Copyright (c) 2005, Google, Inc. All rights reserved. """ Print out warnings from Python source files. """ import os.path import sys import string import types import traceback import imp import re from pychecker import OP from pychecker import Stack from pychecker import function from pychecker import python from pychecker import pcmodules from pychecker import msgs from pychecker import utils from pychecker import CodeChecks from pychecker.Warning import Warning def cfg() : return utils.cfg() def _checkSelfArg(method, warnings) : """Return a Warning if there is no self parameter or the first parameter to a method is not self.""" if not cfg().methodArgName: return code = method.function.func_code err = None if method.isStaticMethod(): if code.co_argcount > 0 and cfg().methodArgName == code.co_varnames[0]: err = msgs.SELF_IS_ARG % 'staticmethod' elif code.co_argcount < 1: err = msgs.NO_METHOD_ARGS % cfg().methodArgName else: if method.isClassMethod(): if code.co_varnames[0] not in cfg().classmethodArgNames: err = msgs.SELF_NOT_FIRST_ARG % \ (cfg().classmethodArgNames, 'class') elif code.co_varnames[0] != cfg().methodArgName: err = msgs.SELF_NOT_FIRST_ARG % (cfg().methodArgName, '') if err is not None : warnings.append(Warning(code, code, err)) def _checkNoSelfArg(func, warnings) : "Return a Warning if there is a self parameter to a function." code = func.function.func_code if code.co_argcount > 0 and cfg().methodArgName in code.co_varnames: warnings.append(Warning(code, code, msgs.SELF_IS_ARG % 'function')) def _checkSubclass(c1, c2): try: return issubclass(c1.classObject, c2.classObject) except (TypeError, AttributeError): return 0 _IGNORE_RETURN_TYPES = ( Stack.TYPE_FUNC_RETURN, Stack.TYPE_ATTRIBUTE, Stack.TYPE_GLOBAL, Stack.TYPE_COMPARISON, Stack.TYPE_UNKNOWN) def _checkReturnWarnings(code) : is_getattr = code.func_code.co_name in ('__getattr__', '__getattribute__') if is_getattr : for line, retval, dummy in code.returnValues : if retval.isNone() : err = msgs.DONT_RETURN_NONE % code.func_code.co_name code.addWarning(err, line+1) # there must be at least 2 real return values to check for consistency returnValuesLen = len(code.returnValues) if returnValuesLen < 2 : return # if the last return is implicit, check if there are non None returns lastReturn = code.returnValues[-1] # Python 2.4 optimizes the dead implicit return out, so we can't # distinguish implicit and explicit "return None" if utils.pythonVersion() < utils.PYTHON_2_4 and \ not code.starts_and_ends_with_finally and \ cfg().checkImplicitReturns and lastReturn[1].isImplicitNone(): for line, retval, dummy in code.returnValues[:-1] : if not retval.isNone() : code.addWarning(msgs.IMPLICIT_AND_EXPLICIT_RETURNS, lastReturn[0]+1) break # __get*__ funcs can return different types, don't warn about inconsistency if utils.startswith(code.func_code.co_name, '__get') and \ utils.endswith(code.func_code.co_name, '__') : return returnType, returnData = None, None for line, value, dummy in code.returnValues : if not value.isNone() : valueType = value.getType(code.typeMap) if returnType is None and valueType not in _IGNORE_RETURN_TYPES : returnData = value returnType = valueType continue # always ignore None, None can be returned w/any other type # FIXME: if we stored func return values, we could do better if returnType is not None and not value.isNone() and \ valueType not in _IGNORE_RETURN_TYPES and \ returnData.type not in _IGNORE_RETURN_TYPES : ok = returnType in (type(value.data), valueType) if ok : if returnType == types.TupleType : # FIXME: this isn't perfect, if len == 0 # the length can really be 0 OR unknown # we shouldn't check the lengths for equality # ONLY IF one of the lengths is truly unknown if returnData.length > 0 and value.length > 0: ok = returnData.length == value.length else : ok = _checkSubclass(returnType, valueType) or \ _checkSubclass(valueType, returnType) if not ok : code.addWarning(msgs.INCONSISTENT_RETURN_TYPE, line) def _checkComplex(code, maxValue, value, func, err) : if maxValue and value > maxValue : line = func.function.func_code.co_firstlineno code.addWarning(err % (func.function.__name__, value), line) def _checkCode(code, codeSource) : while code.index < code.maxCode : op, oparg, operand = code.popNextOp() dispatch_func = CodeChecks.DISPATCH[op] if dispatch_func is not None : try : dispatch_func(oparg, operand, codeSource, code) except NotImplementedError : raise NotImplementedError('No DISPATCH member for op %r' % op) def _name_unused(var) : if var in cfg().unusedNames : return 0 for name in cfg().unusedNames : if name != '_' and utils.startswith(var, name) : return 0 return 1 def _checkUnusedParam(var, line, func, code) : if line is not None and line == 0 and _name_unused(var) : if ((cfg().ignoreSelfUnused or var != cfg().methodArgName) and (cfg().varArgumentsUsed or func.varArgName() != var)) : code.addWarning(msgs.UNUSED_PARAMETER % var, code.func_code) def _handleNestedCode(func_code, code, codeSource): nested = not (codeSource.main or codeSource.in_class) if func_code.co_name == utils.LAMBDA or nested: utils.debug(' handling nested code %s under %r', func_code.co_name, codeSource.func) varnames = None if nested and func_code.co_name != utils.LAMBDA: varnames = func_code.co_varnames + \ codeSource.calling_code[-1].function.func_code.co_varnames # save the original return value and restore after checking returnValues = code.returnValues # we don't want suppressions from nested code to bleed into the # containing code block, or the next nested code on the same level utils.pushConfig() code.init(function.create_fake(func_code.co_name, func_code, {}, varnames)) _checkCode(code, codeSource) utils.popConfig() code.returnValues = returnValues def _findUnreachableCode(code) : # code after RETURN or RAISE is unreachable unless there's a branch to it unreachable = {} terminals = code.returnValues[:-1] + code.raiseValues terminals.sort(lambda a, b: cmp(a[2], b[2])) for line, dummy, i in terminals : if not code.branches.has_key(i) : unreachable[i] = line # find the index of the last return lastLine = lastItem = lastIndex = None if code.returnValues: lastLine, lastItem, lastIndex = code.returnValues[-1] if len(code.returnValues) >= 2 : lastIndex = code.returnValues[-2][2] if code.raiseValues : lastIndex = max(lastIndex, code.raiseValues[-1][2]) # remove last return if it's unreachable AND implicit if unreachable.get(lastIndex) == lastLine and lastItem and \ lastItem.isImplicitNone(): del code.returnValues[-1] del unreachable[lastIndex] if cfg().unreachableCode : for index in unreachable.keys() : try : if not OP.JUMP_FORWARD(ord(code.bytes[index])) : code.addWarning(msgs.CODE_UNREACHABLE, unreachable[index]) except IndexError : pass def _checkFunction(module, func, c = None, main = 0, in_class = 0) : """ Return a list of Warnings found in a function/method. @type module: L{pychecker.checker.PyCheckerModule} """ # always push a new config object, so we can pop at end of function utils.pushConfig() code = CodeChecks.Code() code.init(func) if main: for key in func.function.func_globals.keys(): code.unusedLocals[key] = -1 codeSource = CodeChecks.CodeSource(module, func, c, main, in_class, code) try : _checkCode(code, codeSource) if not in_class : _findUnreachableCode(code) # handle lambdas and nested functions codeSource.calling_code.append(func) for key in code.codeOrder: func_code = code.codeObjects[key] _handleNestedCode(func_code, code, codeSource) del codeSource.calling_code[-1] except (SystemExit, KeyboardInterrupt) : exc_type, exc_value, exc_tb = sys.exc_info() raise exc_type, exc_value except : exc_type, exc_value, exc_tb = sys.exc_info() exc_list = traceback.format_exception(exc_type, exc_value, exc_tb) for index in range(0, len(exc_list)) : exc_list[index] = string.replace(exc_list[index], "\n", "\n\t") code.addWarning(msgs.CHECKER_BROKEN % string.join(exc_list, "")) if cfg().checkReturnValues : _checkReturnWarnings(code) if cfg().localVariablesUsed : for var, line in code.unusedLocals.items() : if line is not None and line > 0 and _name_unused(var) : code.addWarning(msgs.UNUSED_LOCAL % var, line) if cfg().argumentsUsed : op = code.getFirstOp() if not (OP.RAISE_VARARGS(op) or OP.RETURN_VALUE(op)) : for var, line in code.unusedLocals.items() : _checkUnusedParam(var, line, func, code) # Check code complexity: # loops should be counted as one branch, but there are typically 3 # branches in byte code to setup a loop, so subtract off 2/3's of them # / 2 to approximate real branches branches = (len(code.branches.keys()) - (2 * code.loops)) / 2 lines = (code.getLineNum() - code.func_code.co_firstlineno) returns = len(code.returnValues) if not main and not in_class : args = code.func_code.co_argcount localCount = len(code.func_code.co_varnames) - args _checkComplex(code, cfg().maxArgs, args, func, msgs.TOO_MANY_ARGS) _checkComplex(code, cfg().maxLocals, localCount, func, msgs.TOO_MANY_LOCALS) _checkComplex(code, cfg().maxLines, lines, func, msgs.FUNC_TOO_LONG) _checkComplex(code, cfg().maxReturns, returns, func, msgs.TOO_MANY_RETURNS) _checkComplex(code, cfg().maxBranches, branches, func, msgs.TOO_MANY_BRANCHES) if not (main or in_class) : utils.popConfig() func.returnValues = code.returnValues # FIXME: I don't think code.codeObjects.values() ever gets used, # but if it does, and needs to be in order, then use code.codeOrder here. return (code.warnings, code.globalRefs, code.functionsCalled, code.codeObjects.values(), code.returnValues) def _getUnused(module, globalRefs, dict, msg, filterPrefix = None) : "Return a list of warnings for unused globals" warnings = [] for ref in dict.keys() : check = not filterPrefix or utils.startswith(ref, filterPrefix) if check and globalRefs.get(ref) == None : lineInfo = module.moduleLineNums.get(ref) if lineInfo: warnings.append(Warning(lineInfo[0], lineInfo[1], msg % ref)) return warnings def _get_func_info(method) : try: fc = getattr(method.im_func, 'func_code', None) if fc is not None : return fc.co_filename, fc.co_firstlineno except AttributeError: # if the object derives from any object in 2.2, # the builtin methods are wrapper_descriptors and # have no im_func attr pass return None, None _DOT_INIT = '.' + utils.INIT def _baseInitCalled(classInitInfo, base, functionsCalled) : baseInit = getattr(base, utils.INIT, None) if baseInit is None or _get_func_info(baseInit) == classInitInfo : return 1 initName = utils.safestr(base) + _DOT_INIT if functionsCalled.has_key(initName) : return 1 # ok, do this the hard way, there may be aliases, so check here names = string.split(initName, '.') # first look in our list of PyCheckerModules moduleName = names[0] moduleDir = os.path.dirname(classInitInfo[0]) pcmodule = pcmodules.getPCModule(moduleName, moduleDir) if pcmodule: obj = pcmodule.module else: # fall back to looking in sys.modules try: # i think this can raise an exception if the module is a library # (.so) obj = sys.modules[names[0]] except KeyError: return 1 for i in range(1, len(names)) : obj = getattr(obj, names[i], None) if obj is None: return 0 if functionsCalled.has_key(string.join(names[i:], '.')) : return 1 return 0 def _checkBaseClassInit(moduleFilename, c, func_code, funcInfo) : """ Return a list of warnings that occur for each base class whose __init__() is not called @param funcInfo: triple of functions called, code objects, return values @type funcInfo: triple """ warnings = [] functionsCalled, _, returnValues = funcInfo for line, stackItem, dummy in returnValues : if stackItem.data != None : if not stackItem.isNone() or cfg().returnNoneFromInit : warn = Warning(func_code, line, msgs.RETURN_FROM_INIT) warnings.append(warn) classInit = getattr(c.classObject, utils.INIT, None) if cfg().baseClassInitted and classInit is not None : classInitInfo = _get_func_info(classInit) for base in getattr(c.classObject, '__bases__', None) or (): if not _baseInitCalled(classInitInfo, base, functionsCalled): warn = Warning(moduleFilename, func_code, msgs.BASE_CLASS_NOT_INIT % utils.safestr(base)) warnings.append(warn) return warnings def _checkOverridenMethods(func, baseClasses, warnings) : for baseClass in baseClasses : if func.func_name != utils.INIT and \ not function.same_signature(func, baseClass) : err = msgs.METHOD_SIGNATURE_MISMATCH % (func.func_name, utils.safestr(baseClass)) warnings.append(Warning(func.func_code, func.func_code, err)) break def _updateFunctionWarnings(module, func, c, warnings, globalRefs, main = 0, in_class = 0) : """ Update function warnings and global references. @type module: L{pychecker.checker.PyCheckerModule} @type func: L{function.Function} """ newWarnings, newGlobalRefs, funcs, codeObjects, returnValues = \ _checkFunction(module, func, c, main, in_class) warnings.extend(newWarnings) globalRefs.update(newGlobalRefs) return funcs, codeObjects, returnValues def getBlackList(moduleList) : blacklist = [] for badBoy in moduleList : if badBoy[-3:] == ".py": badBoy = badBoy[0:-3] try : handle, path, flags = imp.find_module(badBoy) if handle: handle.close() # apparently, imp.find_module can return None, path, (triple) # This happened to me with twisted 2.2.0 in a separate path if path: blacklist.append(normalize_path(path)) except ImportError : pass return blacklist def getStandardLibraries() : """ Return a list of standard libraries. @rtype: list of str or None """ if cfg().ignoreStandardLibrary : try : from distutils import sysconfig std_libs = [ sysconfig.get_python_lib(plat_specific=0), sysconfig.get_python_lib(plat_specific=1) ] ret = [] for std_lib in std_libs: path = os.path.split(std_lib) if path[1] == 'site-packages' : ret.append(path[0]) return ret except ImportError : return None def normalize_path(path): return os.path.normpath(os.path.normcase(path)) def removeWarnings(warnings, blacklist, std_lib, cfg): """ @param blacklist: list of absolute paths not to warn for @type blacklist: str @param std_lib: list of standard library directories @type std_lib: list of str or None """ utils.debug('filtering %d warnings with blacklist', len(warnings)) if std_lib is not None: std_lib = [normalize_path(p) for p in std_lib] for index in range(len(warnings) - 1, -1, -1): filename = normalize_path(warnings[index].file) # the blacklist contains paths to packages and modules we do not # want warnings for # when we find a match, make sure we continue the warnings for loop found = False for path in blacklist: if not found and filename.startswith(path): found = True del warnings[index] if found: continue if std_lib: found = False for path in std_lib: if not found and utils.startswith(filename, path) : found = True del warnings[index] if found: continue elif cfg.only: # ignore files not specified on the cmd line if requested if os.path.abspath(filename) not in cfg.files: del warnings[index] continue # filter by warning/error level if requested if cfg.level and warnings[index].level < cfg.level: del warnings[index] if cfg.limit: # sort by severity first, then normal sort (by file/line) warnings.sort(lambda a, b: cmp(a.level, b.level) or cmp(a, b)) # strip duplicates lastWarning = None for index in range(len(warnings)-1, -1, -1): warning = warnings[index] # remove duplicate warnings if lastWarning is not None and cmp(lastWarning, warning) == 0: del warnings[index] else: lastWarning = warning num_ignored = len(warnings) - cfg.limit if num_ignored > 0: del warnings[:-cfg.limit] msg = msgs.TOO_MANY_WARNINGS % num_ignored warnings.append(Warning('', 0, msg)) utils.debug('kept %d warnings with blacklist', len(warnings)) return warnings class _SuppressionError(Exception) : pass def _updateSuppressions(suppress, warnings) : if not utils.updateCheckerArgs(suppress, 'suppressions', 0, warnings) : utils.popConfig() raise _SuppressionError _CLASS_NAME_RE = re.compile("(\\..+)?") def getSuppression(name, suppressions, warnings): """ @type name: str @type suppressions: tuple of (dict of str -> str, dict of _sre.SRE_Pattern -> str) @type warnings: list of L{Warning.Warning} @returns: the suppression options for the given name @rtype: str """ try: utils.pushConfig() # cheesy hack to deal with new-style classes. i don't see a # better way to get the name, '<' is an invalid identifier, so # we can reliably check it and extract name from: # [.identifier[.identifier]...] matches = _CLASS_NAME_RE.match(name) if matches: # pull out the names and make a complete identifier (ignore None) name = string.join(filter(None, matches.groups()), '') suppress = suppressions[0].get(name, None) if suppress is not None: _updateSuppressions(suppress, warnings) regexList = suppressions[1].keys() regexList.sort() for regex in regexList: match = regex.match(name) if match and match.group() == name: suppress = 1 _updateSuppressions(suppressions[1][regex], warnings) if not suppress : utils.popConfig() return suppress except _SuppressionError : return None def _findFunctionWarnings(module, globalRefs, warnings, suppressions) : """ @type module: L{pychecker.checker.PyCheckerModule} """ for func in module.functions.values() : func_code = func.function.func_code utils.debug("function:", func_code) name = '%s.%s' % (module.moduleName, func.function.__name__) suppress = getSuppression(name, suppressions, warnings) if cfg().noDocFunc and func.function.__doc__ == None : err = msgs.NO_FUNC_DOC % func.function.__name__ # FIXME: is there a good reason why this passes func_code as line ? warnings.append(Warning(module.filename(), func_code, err)) _checkNoSelfArg(func, warnings) _updateFunctionWarnings(module, func, None, warnings, globalRefs) if suppress is not None : utils.popConfig() def _getModuleFromFilename(module, filename): if module.filename() != filename: for m in module.modules.values(): if m.filename() == filename: return m return module # Create object for non-2.2 interpreters, any class object will do try: if object: pass except NameError: object = _SuppressionError # Create property for pre-2.2 interpreters try : if property: pass except NameError: property = None def _findClassWarnings(module, c, class_code, globalRefs, warnings, suppressions) : utils.debug("class:", class_code) try: className = utils.safestr(c.classObject) except TypeError: # goofy __getattr__ return classSuppress = getSuppression(className, suppressions, warnings) baseClasses = c.allBaseClasses() for base in baseClasses : baseModule = utils.safestr(base) if '.' in baseModule : # make sure we handle import x.y.z packages = string.split(baseModule, '.') baseModuleDir = string.join(packages[:-1], '.') globalRefs[baseModuleDir] = baseModule # handle class variables if class_code is not None : func = function.create_fake(c.name, class_code) _updateFunctionWarnings(module, func, c, warnings, globalRefs, 0, 1) filename = module.filename() func_code = None for method in c.methods.values() : if method == None : continue func_code = method.function.func_code utils.debug("class %s: method:" % className, func_code) try: name = utils.safestr(c.classObject) + '.' + method.function.func_name except AttributeError: # func_name may not exist continue methodSuppress = getSuppression(name, suppressions, warnings) if cfg().checkSpecialMethods: funcname = method.function.func_name if funcname[:2] == '__' == funcname[-2:] and \ funcname != '__init__': err = None argCount = python.SPECIAL_METHODS.get(funcname, -1) if argCount != -1: # if the args are None, it can be any # of args if argCount is not None: minArgs = maxArgs = argCount err = CodeChecks.getFunctionArgErr(funcname, func_code.co_argcount, minArgs, maxArgs) else: err = msgs.NOT_SPECIAL_METHOD % funcname if err is not None: warnings.append(Warning(filename, func_code, err)) if cfg().checkOverridenMethods : _checkOverridenMethods(method.function, baseClasses, warnings) if cfg().noDocFunc and method.function.__doc__ == None : err = msgs.NO_FUNC_DOC % method.function.__name__ # FIXME: is there a good reason why this passes func_code as line ? warnings.append(Warning(filename, func_code, err)) _checkSelfArg(method, warnings) tmpModule = _getModuleFromFilename(module, func_code.co_filename) funcInfo = _updateFunctionWarnings(tmpModule, method, c, warnings, globalRefs) if func_code.co_name == utils.INIT : # this is a constructor if utils.INIT in dir(c.classObject) : warns = _checkBaseClassInit(filename, c, func_code, funcInfo) warnings.extend(warns) elif cfg().initDefinedInSubclass : err = msgs.NO_INIT_IN_SUBCLASS % c.name warnings.append(Warning(filename, c.getFirstLine(), err)) if methodSuppress is not None : utils.popConfig() if c.memberRefs and cfg().membersUsed : memberList = c.memberRefs.keys() memberList.sort() err = msgs.UNUSED_MEMBERS % (string.join(memberList, ', '), c.name) warnings.append(Warning(filename, c.getFirstLine(), err)) try: newStyleClass = issubclass(c.classObject, object) except TypeError: # FIXME: perhaps this should warn b/c it may be a class??? newStyleClass = 0 slots = c.statics.get('__slots__') if slots is not None and cfg().slots: lineNum = c.lineNums['__slots__'] if not newStyleClass: err = msgs.USING_SLOTS_IN_CLASSIC_CLASS % c.name warnings.append(Warning(filename, lineNum, err)) elif cfg().emptySlots: try: if len(slots.data) == 0: err = msgs.EMPTY_SLOTS % c.name warnings.append(Warning(filename, lineNum, err)) except AttributeError: # happens when slots is an instance of a class w/o __len__ pass if not newStyleClass and property is not None and cfg().classicProperties: for static in c.statics.keys(): if type(getattr(c.classObject, static, None)) == property: err = msgs.USING_PROPERTIES_IN_CLASSIC_CLASS % (static, c.name) warnings.append(Warning(filename, c.lineNums[static], err)) coerceMethod = c.methods.get('__coerce__') if newStyleClass and coerceMethod: lineNum = coerceMethod.function.func_code.co_firstlineno err = msgs.USING_COERCE_IN_NEW_CLASS % c.name warnings.append(Warning(filename, lineNum, err)) for newClassMethodName in python.NEW_STYLE_CLASS_METHODS: newClassMethod = c.methods.get(newClassMethodName) if not newStyleClass and newClassMethod: lineNum = newClassMethod.function.func_code.co_firstlineno err = msgs.USING_NEW_STYLE_METHOD_IN_OLD_CLASS % (newClassMethodName, c.name) warnings.append(Warning(filename, lineNum, err)) if cfg().noDocClass and c.classObject.__doc__ == None : method = c.methods.get(utils.INIT, None) if method != None : func_code = method.function.func_code # FIXME: check to make sure this is in our file, # not a base class file??? err = msgs.NO_CLASS_DOC % c.classObject.__name__ warnings.append(Warning(filename, func_code, err)) # we have to do this here, b/c checkFunction doesn't popConfig for classes # this allows us to have __pychecker__ apply to all methods # when defined at class scope if class_code is not None : utils.popConfig() if classSuppress is not None : utils.popConfig() def find(moduleList, initialCfg, suppressions=None): "Return a list of warnings found in the module list" if suppressions is None : suppressions = {}, {} utils.initConfig(initialCfg) utils.debug('Finding warnings in %d modules' % len(moduleList)) warnings = [] before = 0 for module in moduleList : if module.moduleName in cfg().blacklist : continue modSuppress = getSuppression(module.moduleName, suppressions, warnings) globalRefs, classCodes = {}, {} # mainCode can be null if there was a syntax error if module.mainCode != None : utils.debug("module:", module) before = len(warnings) funcInfo = _updateFunctionWarnings(module, module.mainCode, None, warnings, globalRefs, 1) if before != len(warnings): utils.debug("module: %r __main__ triggered %d warnings", module, len(warnings) - before) for code in funcInfo[1] : classCodes[code.co_name] = code before = len(warnings) _findFunctionWarnings(module, globalRefs, warnings, suppressions) if before != len(warnings): utils.debug("module: %r functions triggered %d warnings", module, len(warnings) - before) before = len(warnings) for c in module.classes.values(): _findClassWarnings(module, c, classCodes.get(c.name), globalRefs, warnings, suppressions) if before != len(warnings): utils.debug("module: %r classes triggered %d warnings", module, len(warnings) - before) if cfg().noDocModule and \ module.module != None and module.module.__doc__ == None: warnings.append(Warning(module.filename(), 1, msgs.NO_MODULE_DOC)) utils.debug("module: %r module doc triggered 1 warning") before = len(warnings) if cfg().allVariablesUsed or cfg().privateVariableUsed: prefix = None if not cfg().allVariablesUsed: prefix = "_" for ignoreVar in cfg().variablesToIgnore + cfg().unusedNames: globalRefs[ignoreVar] = ignoreVar warnings.extend(_getUnused(module, globalRefs, module.variables, msgs.VAR_NOT_USED, prefix)) if before != len(warnings): utils.debug("module: %r unused variables triggered %d warnings", module, len(warnings) - before) before = len(warnings) if cfg().importUsed: if module.moduleName != utils.INIT or cfg().packageImportUsed: # always ignore readline module, if [raw_]input() is used if globalRefs.has_key('input') or \ globalRefs.has_key('raw_input'): globalRefs['readline'] = 0 warnings.extend(_getUnused(module, globalRefs, module.modules, msgs.IMPORT_NOT_USED)) if before != len(warnings): utils.debug("module: %r unused imports triggered %d warnings", module, len(warnings) - before) # we have to do this here, b/c checkFunction doesn't popConfig for # classes this allows us to have __pychecker__ apply to all methods # when defined at class scope if module.mainCode != None: utils.popConfig() if modSuppress is not None: utils.popConfig() std_lib = None if cfg().ignoreStandardLibrary: std_lib = getStandardLibraries() ret = removeWarnings(warnings, getBlackList(cfg().blacklist), std_lib, cfg()) utils.debug('Found %d warnings in %d modules' % (len(ret), len(moduleList))) return ret if 0: # if you want to test w/psyco, include this import psyco psyco.bind(_checkCode) pychecker-0.8.19/MAINTAINERS0000644000076400007640000000752211512043726014652 0ustar thomasthomas Neal Norwitz nnorwitz@gmail.com Eric C. Newton ecn@metaslash.com Other Contributors: Barry Scott barry@scottb.demon.co.uk Fix Base class not init spurious warning Other misc suggestions and fixes Shakeeb Alireza bug reports Andy Anderson many bug reports and fixes Erwin S. Andreasen bug reports Anthony Baxter bug reports Alexander Belchenko bug reports and fixes Jeff Bellegarde bug reports and fixes Andrew Bennetts suggestion: check for modifying def arg values Riaan Booysen bug reports, suggestions, etc. John Bowe bug reports, suggestions, etc. Mike Brown bug report Gary Capell bug reports and refactoring suggestions Loris Caren testing, etc. Charles Cazabon bug report Nicolas Chauvat setup.py Allan Crooks bug reports Jeff Collins checker.findModule Tom Culliton bug reports and suggestions André Dahlqvist bug reports Walter Doerwald bug reports and fixes Mika Eloranta bug reports Jeff Epler bug reports Mark Favas bug reports Erik Max Francis bug reports and suggestions Dan Fandrich bug reports Alexandre Fayolle bug reports Jeremy Fincher bug reports Horst Gassner bug reports and suggestions Stuart D. Gathman bug reports and suggestions Geoff Gerrietts bug reports Johannes Gijsbers bug reports Hartmut Goebel patches Joel Gould bug reports greep bug reports John-Mark Gurney bug reports Thomas Heller bug reports and suggestions Raymond Hettinger suggestions Tim Hochberg bug reports Eric Huss bug reports Ben Hutchings bug reports Jeremy Hylton bug reports and suggestions Garth T Kidd bug reports and suggestions Bastian Kleineidam patches Brad Knotwell bug reports John Machin bug reports Jonathan Mark bug reports and suggestions Evelyn Mitchell bug reports Skip Montanaro bug reports and suggestions Michele Moore many suggestions Jon Nelson fix setup.py, add MANIFEST.in Jason Orendorff suggestions Ian Parker suggestions Fernando Pérez bug reports Kenneth Pronovici debian maintainer, bug reports, patches, setup.py Eric S. Raymond bug reports and suggestions Terry Reedy suggestions Jon Ribbens bugs, bugs, bugs Guido van Rossum bug reports and suggestions Jim Rutledge bug reports and suggestions Rich Salz bug reports and suggestions John Shue patches, etc. Stephan A. Terre bug reports Lucio Torre suggestions Miloslav Trmac bug reports and patches Sheng-Te Tsao bug reports and suggestions Kevin Turner bug report Joe VanAndel bug reports Greg Ward bug reports, patches, etc Barry Warsaw bug reports Matt Wilson bug reports and fixes Andy Wingo bug reports and fixes Thomas Vander Stichele bug reports, fixes, documentation pychecker-0.8.19/pychecker.doap0000664000076400007640000001214411512045711015771 0ustar thomasthomas PyChecker pychecker 2001-04-07 PyChecker is a tool for finding bugs in python source code. PyChecker is a tool for finding bugs in python source code. It finds problems that are typically caught by a compiler for less dynamic languages, like C and C++. It is similar to lint. Because of the dynamic nature of python, some warnings may be incorrect; however, spurious warnings should be fairly infrequent. PyChecker works in a combination of ways. First, it imports each module. If there is an import error, the module cannot be processed. The import provides some basic information about the module. The code for each function, class, and method is checked for possible problems. Types of problems that can be found include: No global found (e.g., using a module without importing it) Passing the wrong number of parameters to functions/methods/constructors Passing the wrong number of parameters to builtin functions and methods Using format strings that don't match arguments Using class methods and attributes that don't exist Changing signature when overriding a method Redefining a function/class/method in the same scope Using a variable before setting it self not the first parameter to a method Unused globals and locals (module or variable) Unused function/method arguments (can ignore self) No doc strings in modules, classes, functions, and methods python Neil Norwitz Thomas Vander Stichele 0.8.19 HEAD Two Seven 2011-01-08 Add support for Python 2.7 Fixed SF Bug [ 2209631 ]: New pcmodules.py module, modulePath code causes import error Fixed SF Bug [ 1565876 ]: pychecker does not allow lambda with setattr and const Fixed SF Bug [ 1564614 ]: pychecker -q on 64-bit ignores the platform-specific stdlib Fixed SF Bug [ 1563572 ]: order of checking files affects errors reported PyChecker was only checking one of each set of modules with the same name; so it will now catch many more warnings it was missing before. Fixed SF Bug [ 1563495 ]: couldn't find real module does not respect blacklist Fixed SF Bug [ 1563494 ]: pychecker tracebacks when importing zope.interface.declaration Add unittest-based testsuite. Warn about missing opcodes. Added implementations for the following opcodes: BUILD_SLICE, DELETE_SLICE3, DUP_TOPX, JUMP_IF_FALSE/TRUE, JUMP_IF_FALSE/TRUE_OR_POP, MAP_ADD, PRINT_NEWLINE_TO, SET_ADD, SETUP_WITH, SLICE0, STORE_MAP, STORE_SLICE+0, STORE_SLICE2, WITH_CLEANUP, YIELD_VALUE 0.8.18 HEAD 2008-08-17 Fixed SF Bug [ 1827412 ] pychecker.bat only handles 9 cmdline args Fix warning about comparison checking with bool false alert for 'in' and 'not in' Add warning for using __set__, __get__, and __delete__ in an old-style class Fix spurious warning about __set__, __get__, and __delete__ not being special methods. Fix inability to disable Warning about constant setattr()/getattr() Fix spurious warning on min/max not accepting kwarg of key in Python 2.5 Add some __special__ pickling methods that were missing Add --missingattrs option to ignore some (but not all) attribute names Fix crash when using keyword arguments with builtin methods Add --keepgoing option to ignore import errors Add --findevil option to make it easier to find objects that crash the interpreter Add support for Python 2.5 pychecker-0.8.19/ChangeLog0000600000076400007640000010417211512044520014707 0ustar thomasthomas=== release 0.8.19 === 2011-01-08 Thomas Vander Stichele * setup.py: * NEWS: Releasing 0.8.19, "Two Seven" * README: Note that we support 2.2 - 2.7 * MANIFEST.in: Adding pychecker.doap 2011-01-08 Thomas Vander Stichele * pychecker.doap: * setup.py: Add a DOAP file. 2011-01-07 Thomas Vander Stichele * pychecker/utils.py: debug suppressions detected. * test_check.sh: Do a unified diff. Reverse order, so that + lines show us what the new output is. 2011-01-07 Thomas Vander Stichele * test_expected/test76: For some reason line numbers for warnings shifted from 6 to 7. Neither seems correct. Adjust expected output for now since it's not more wrong than before. * TODO: Add a note to investigate this later. 2011-01-07 Thomas Vander Stichele * pychecker/warn.py: Push config before processing a nested function. This fixes the new test_nestedsuppression.py * test/expected/test_nestedsuppression__objattrs: * test/test_suppressions.py: Add the new test to the test suite, with four expected warnings. 2011-01-07 Thomas Vander Stichele * pychecker/CodeChecks.py: add Code.codeOrder attribute, keeping track of the order in which codeObjects is added to. add addCodeObject method. * pychecker/warn.py: Go through codeObjects in order. Fixing the ordering moves the warning in the previous test from containerFirst to containerSecond, which makes more sense, but still shows suppression bleed. 2011-01-07 Thomas Vander Stichele * test/input/test_nestedsuppression.py: Add a test that shows that suppressions in nested code bleed over into their sibling code blocks and parent. Curiously, in my case it warned correctly about containerFirst, but did not warn in containerSecond. Debug log indicates that in containerSecond, the second() function was parsed (and suppression was added) before first(). 2011-01-07 Thomas Vander Stichele * pychecker/CodeChecks.py: Document CodeChecks.Code.codeObjects 2011-01-07 Thomas Vander Stichele * pychecker/warn.py: Identify nested code better. 2011-01-07 Thomas Vander Stichele * pychecker/warn.py: Give us stats on how many warnings were triggered processing each kind of block. 2011-01-07 Thomas Vander Stichele * pychecker/Config.py: Process command line args before config files. This gives a -d option a change to debug the processing of config files. Add debugging of loaded suppressions. 2011-01-07 Thomas Vander Stichele * pychecker/warn.py: Debug filtering the warnings with the blacklist. * pychecker/pcmodules.py: Document Class.ignoreAttrs. * pychecker/checker.py: getWarnings only used by TKInter; so don't untils.debug there too. * pychecker/Stack.py: Clean up and document getType a little. There are unneeded if's there complicating the code. * pychecker/CodeChecks.py: typemap documenting, and pep-8-ifying. 2011-01-06 Thomas Vander Stichele * test/test_pychecker_function.py: Before 2.5, .0 for co_varnames was [outmost-iterable]. Go figure. 2011-01-06 Thomas Vander Stichele * test_expected/test103-2.2: * test_expected/test103-2.4: * test_expected/test103 (deleted): Test 103 uses syntax that only works from 2.4 on. Break up expected results in broken pre-2.4 and working 2.4 and later. 2011-01-06 Thomas Vander Stichele * test_todo/test78.py: * test_input/test78.py (deleted): Move a test that breaks on python 2.6 and older. 2011-01-06 Thomas Vander Stichele * test_todo/import44.py: * test_todo/test34.py: * test_todo/test44.py: * test_todo/test70.py: * test_todo/test71.py: * test_todo/test77.py: * test_input/test44.py (deleted): * test_input/test34.py (deleted): * test_input/test77.py (deleted): * test_input/test71.py (deleted): * test_input/test70.py (deleted): * test_input/import44.py (deleted): Move known broken tests to todo. 2011-01-06 Thomas Vander Stichele * pychecker/CodeChecks.py: Debug every warning when it gets created. Prefix all opcode debugging with DIS, makes it easier to find. 2011-01-06 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/Warning.py: * pychecker/options.py: * pychecker/pcmodules.py: * pychecker/utils.py: * pychecker/warn.py: Clean up a bunch of pychecker warnings. 2011-01-06 Thomas Vander Stichele * pychecker/Config.py: * pychecker/OP.py: * pychecker/OptionTypes.py: * pychecker/Stack.py: * pychecker/Warning.py: * pychecker/checker.py: * pychecker/function.py: * pychecker/msgs.py: * pychecker/options.py: * pychecker/pcmodules.py: * pychecker/printer.py: * pychecker/python.py: * pychecker/utils.py: * pychecker/warn.py: Add Python mode lines. Remove shebang lines where not needed. 2011-01-06 Thomas Vander Stichele * pychecker/checker.py: Extract PyCheckerModule, Variable and Class... * pychecker/pcmodules.py: ... and move it here. This allows us importing a module that has PyCheckerModule without all sorts of side effects. * pychecker/CodeChecks.py: * pychecker/utils.py: Follow up on changes. 2011-01-06 Thomas Vander Stichele * pychecker/utils.py: Make utils.debug output something slightly nicer. 2011-01-06 Thomas Vander Stichele * pychecker/warn.py: Make sure that every disassembly has a header saying what it is (module/class/class method/function) 2011-01-06 Thomas Vander Stichele * pychecker/CodeChecks.py: Add docstrings. Fix some inconsistent casing. More PEP-8 adherence. * pychecker/checker.py: All of the above, plus add PyCheckerModule.getToken() which can be used later to handle token conflicts. 2011-01-06 Thomas Vander Stichele * pychecker/checker.py: Make output of NOT PROCESSED UNABLE TO IMPORT report filename similarly to other warnings. * test_expected/test58-2.3: * test_expected/test77: * test_expected/test8: * test_expected/test89: Adjust expected output to match. 2011-01-06 Thomas Vander Stichele * pychecker/warn.py: Add debug output to list number of modules and number of warnings found. 2011-01-05 Thomas Vander Stichele * pychecker/CodeChecks.py: Add support for opcode 99, DUP_TOPX * test/expected/test_DUP_TOPX: * test/input/test_DUP_TOPX.py: * test/test_pychecker_CodeChecks.py: Add a test for it. 2010-12-29 Thomas Vander Stichele * pychecker/checker.py: Rework confusing addMethod method such that instead of mixing method and methodName and doing confusing checks and asserts, the name is always passed, and optionally the method. In the third case where it was used, a name was always passed, hence the second argument wasn't even used; so drop it. 2010-12-29 Thomas Vander Stichele * TODO: Add a TODO item. * test/test_pychecker_function.py: Add a unit test for function.Function Add a surprising result for co_varnames for generators. 2010-12-29 Thomas Vander Stichele * pychecker/checker.py: * pychecker/function.py: * pychecker/pcmodules.py: * pychecker/warn.py: Documentation, comments, and PEP-8. 2010-12-29 Thomas Vander Stichele * test/test_global.py: Add a test for globals, same as test13. * test/input/test_global.py: Test it without and with -g as the test input says. * test/expected/test_global: * test/expected/test_global_g: Expected outputs in both cases. 2010-12-29 Thomas Vander Stichele * test/common.py: Add underscored arguments to expected file name, so that we can run the same input file with more than one argument. * test/expected/test_zope_interface: * test/expected/test_zope_interface_q: Renamed now that args are encoded in filename. 2010-12-29 Thomas Vander Stichele * TODO: * test_expected/test13: For some reason the expected output was warning about the second occurrence of each global, not the first. So the expected output was wrong. Even in 2.2 the behaviour was the same. So, regenerate expected output. Verified it passes from 2.2 to 2.7. 2010-12-29 Thomas Vander Stichele * pychecker/checker.py: In 2.2 and older, __file__ might not be set on the current module. 2010-12-28 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement BUILD_SLICE, opcode 133. Now dis can be pychecked. Not sure what to push on stack for non-const slices though. 2010-12-28 Thomas Vander Stichele * pychecker/Stack.py: Document and pep-8-ify. 2010-12-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement PRINT_NEWLINE_TO taking a cue from PRINT_ITEM_TO. Also triggered by twisted.trial.unittest. 2010-12-20 Thomas Vander Stichele * pychecker/CodeChecks.py: * test/input/test_STORE_SLICE_PLUS_0.py: * test/test_pychecker_CodeChecks.py: Add support for STORE_SLICE+0, as triggered by twisted.trial.unittest, plus a test. 2010-12-20 Thomas Vander Stichele * test/common.py: Make it clear when we actually generate an expected output file the first time. 2010-12-20 Thomas Vander Stichele * pychecker/CodeChecks.py: Add WITH_CLEANUP implementation for the case where TOS is None. Now can check unittest.case 2010-12-20 Thomas Vander Stichele * pychecker/CodeChecks.py: Add SETUP_WITH implementation, similar to SETUP_FINALLY. Fixes one of the two unimplemented opcodes warnings about unittest.case 2010-12-19 Thomas Vander Stichele * pychecker/CodeChecks.py: Mark SETUP_WITH and BUILD_SET as unimplemented. Makes unit tests pass again. 2010-12-19 Thomas Vander Stichele * pychecker/CodeChecks.py: Instead of peaking two intstructions ahead past POP_TOP for LOAD_CONST, with the new POP/JUMP instructions, peak only one ahead. Fixes test 85 for 2.7. 2010-12-18 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement support for SET_ADD, MAP_ADD and the new style LIST_APPEND. 2010-12-18 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement support for JUMP_IF_FALSE/TRUE and JUMP_IF_FALSE/TRUE_OR_POP. Brings 2.7 test failures from 33 to 7. Compared to 2.6, the only difference is 85 now failing and 78 passing. 2010-12-18 Thomas Vander Stichele * pychecker/CodeChecks.py: Rearrange opcodes for 2.7, without implementing new or changed opcodes. Brings 2.7 test failures from 54 to 33. 2010-12-18 Thomas Vander Stichele * HACKING: Add notes on updating to newer Python version. * pychecker/OP.py: Adapt to opcode changes and additions. Brings 2.7 test failures from 69 to 54. 2010-12-16 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/checker.py: * pychecker/function.py: * pychecker/warn.py: Rename PyCheckerModule's main_code to mainCode to be consistent with other instance variable names. More PEP-8-ifying. More docstrings. 2010-12-16 Thomas Vander Stichele * pychecker/checker.py: PEP-8-ify more. Document _filterDir. Add _getModuleTokens similar to _getClassTokens. 2010-12-16 Thomas Vander Stichele * pychecker/checker.py: Make function PEP-8. Comment and restructure. 2010-12-16 Thomas Vander Stichele * pychecker/checker.py: Make code more PEP-8. If we get pcmodule, reuse it on the next line. 2010-12-15 Thomas Vander Stichele * pychecker/utils.py: Add constant for 2.7 2010-12-04 Thomas Vander Stichele * doc/opcodes/opcodes-2.3: * doc/opcodes/opcodes-2.4: * doc/opcodes/opcodes-2.5: * doc/opcodes/opcodes-2.6: * doc/opcodes/opcodes-2.7: * scripts/opcodes.py: Add a script to list opcodes, and record the results for 5 python versions. 2010-12-04 Thomas Vander Stichele * test_check.sh: Fix simple typo so it reports number of failed tests. 2009-10-01 Thomas Vander Stichele * TODO: Add notes about failing test. * test_input/test71.py: comment the lines that really should be unreacheable. They're not the lines the pre-2.5 results actually hit. 2009-10-01 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/OP.py: Document. 2009-10-01 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/utils.py: From 2.4 to 2.5, the name for generator code objects changed. In 2.4, lib/compile.c has the function compile_generator_expression with: c->c_name = ""; In 2.5, lib/compile.c has the function compiler_genexp with: if (!name) { name = PyString_FromString(""); So adapt to this change by adding another utils string, and comparing against it in _LODE_CONST. Fixes test 103 on 2.5/2.6 2009-09-30 Thomas Vander Stichele * test_check.sh: Mangle output from warnings for system libraries so that we replace the system path with [system path] much like pychecker does. Allows us to consistently check against expected output without relying on where python is installed. * test_expected/test88-2.4: Change to use [system path]. Fixes test 88 for python 2.4 2009-09-30 Thomas Vander Stichele * test_expected/test87-2.5: Since Python 2.5, disassembly is able to pinpoint the exact line number of multiline statements. So update line numbers to the correct source line. Fixes test 87 for 2.5 and later. 2009-09-30 Thomas Vander Stichele * pychecker/CodeChecks.py: More documentation. 2009-09-30 Thomas Vander Stichele * test_expected/test53-2.5: Similarly, ~~ is now optimized away; in 2.4, we see: >>> dis.dis(test53.x) 5 0 LOAD_CONST 1 (10) 3 UNARY_INVERT 4 UNARY_INVERT 5 STORE_FAST 0 (i) In 2.5, the two UNARY_INVERT are gone. "Fixes" test 53 on 2.5 and 2.6 2009-09-30 Thomas Vander Stichele * test_expected/test48-2.5: Similarly, the following piece of opcode is now optimized away since 2.5: >>> dis.dis(test_input.test48.abc) 57 16 LOAD_CONST 1 (5) 19 POP_TOP "Fixes" test 48 on 2.5 and 2.6 2009-09-30 Thomas Vander Stichele * test_expected/test22-2.5: From Python 2.5 "What's new" at http://www.python.org/doc/2.5/whatsnew/other-lang.html: "The code generator's peephole optimizer now performs simple constant folding in expressions. If you write something like a = 2+3, the code generator will do the arithmetic and produce code corresponding to a = 5. (Proposed and implemented by Raymond Hettinger.)" Verified from debug info that this actually happens; the code generator now transforms the statement on line 79 to 1, and line 81 to 0. So, remove these warnings from the expected output as they can't be triggered anymore. Fixes test 22 for Python 2.5 2009-09-30 Thomas Vander Stichele * test_input/test88.py: gopherlib has been removed in 2.6, so wrap it inside a try/except. * test_expected/test88: * test_expected/test88-2.4: * test_expected/test88-2.5: Update the line numbers since they've all changed. * test_expected/test88-2.6: Add an expected output, which is now different since the deprecation warning from the module itself is now gone. Fixes test 88. 2009-09-30 Thomas Vander Stichele * pychecker/CodeChecks.py: Since Python 2.6, BUILD_MAP can take a non-zero argument. This argument is the size of the dictionary to pre-size with, but the opcode doesn't actually consume this number of items from the stack; adding items to the dict comes later on the stack. So, the factory function makeDict should be called with an index of 0 always. Fixes tests 26, 36, 59 on Python 2.6. 2009-09-30 Thomas Vander Stichele * pychecker/CodeChecks.py: Add docs and comments on some methods before fixing test26 on Python 2.6 2009-09-30 Thomas Vander Stichele * test_check.sh: Use mktemp to create a temporary directory. Makes testsuite robust against parallel execution. First buglet caught by buildbot. 2009-07-29 Thomas Vander Stichele * test_check.sh: Return the number of failed tests, similar to make check. This makes sure tools like buildbot see a failure. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement more opcodes, this time triggered by checking pychecker itself. pycheckering pychecker is a sobering experience, and bad PR. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement STORE_SLICE2, which is triggered by anaconda. 2009-06-27 Thomas Vander Stichele * pychecker/checker.py: moduleDir can be '' which is None, in the case where the module being tested lives in the current directory. So always compare with is/is not None. Fixes https://sourceforge.net/tracker/index.php? func=detail&aid=2209631&group_id=24686&atid=382217 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/warn.py: Note where test44 seems to fail. Can't figure out how to fix it yet though. Add more comments. Reorder a function to be more clear to follow. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement DELETE_SLICE3. ./test_check.sh no longer triggers dispatch errors. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement SLICE0 and YIELD_VALUE. 2009-06-27 Thomas Vander Stichele * bin/pychecker: Adding a script to be used as the pychecker binary when running uninstalled. * misc/pychecker-uninstalled: Add a script to run pychecker uninstalled. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Implement STORE_MAP dispatcher. Fixes test/test_dict.py 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/checker.py: * pychecker/utils.py: Further documentation and commenting. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/Warning.py: * pychecker/utils.py: * pychecker/warn.py: Add comments and docstrings as I learn. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: * pychecker/warn.py: raise NotImplementedError where we actually have the opcode. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Add opcodes from 2.5 and 2.6 2009-06-27 Thomas Vander Stichele * test/test_stdlib.py: For now, return OK if we can't import zope.interface, since there is no skip support in the standard unittest. 2009-06-27 Thomas Vander Stichele * HACKING: Add note on setting PYTHONPATH for running tests. * test/common.py: Also make trial work for Twisted 1.3.0 which we use for Python 2.3 testing. 2009-06-27 Thomas Vander Stichele * pychecker/CodeChecks.py: Add _empty and _unimplemented as opcode dispatchers. Add all missing opcodes for python 2.4 * pychecker/warn.py: For now, print a NotImplementedError if there are still missing opcodes. This should be changed to a raise when we've added all opcodes from released python versions. * test/main.py: Add new tests for pychecker.CodeChecks * test/test_pychecker_CodeChecks.py: Add a test to make sure we have all opcodes handled. 2009-06-26 Thomas Vander Stichele * HACKING: Add a note on trial as an option. * test/expected/test_dict: * test/expected/test_getmodule: * test/expected/test_zope_interface: Make everything relative to the test dir * test/common.py: Make 'trial test' run similarly to 'python test/main.py' Diff from expected to output, so the + are what the test has. 2009-06-26 Thomas Vander Stichele * HACKING: Add notes about the testsuite. * test/expected/test_dict: * test/input/test_dict.py: * test/main.py: * test/test_dict.py: Add a test for someDict.keys() in a function which fails in python 2.6, but not earlier. 2009-06-21 Thomas Vander Stichele * test_expected/test3-2.4: Fix expected output to include [system path] like the others. Fixes the test on 2.4. 2009-06-21 Thomas Vander Stichele * test_expected/test34-2.4: Fix expected output to include [system path] like the others. Fixes the test on 2.4. 2009-06-20 Thomas Vander Stichele * scripts/delete-duplicate-expected: Also catch when -2.4 and -2.6 are the same, and there's no -2.5 2009-06-20 Thomas Vander Stichele * test_expected/test88-2.5: Add a 2.5-specific version since whrandom really is gone now. 2009-06-20 Thomas Vander Stichele * pychecker/Warning.py: remove sys.path list members from the warning's file path, so that we don't need different expected test outputs for different versions when nothing important has changed. * pychecker/checker.py: remove sys.path entries by default. This could be made configurable if people would still want to see the old behaviour that lists the full path for some errors. * test_expected/test3-2.3: * test_expected/test3-2.5: * test_expected/test3-2.6: * test_expected/test34-2.2: * test_expected/test34-2.3: * test_expected/test34-2.5: * test_expected/test34-2.6: Adapt the expected output to the new [system path] output. Removes some hardcoded neils in the process, and fixes 3 and 34 on my F9 python 2.5 version. 2009-06-20 Thomas Vander Stichele * test_check.sh: Instead of comparing to an exact match for test_expected for our Python version, allow comparing against the newest expected version that is equal to or below our Python version. Avoids needing to add output for test 17 on python 2.5, and fixes test 17 in my python 2.5 test run. 2008-07-29 Thomas Vander Stichele * test/common.py: * test/test_module.py: * test/test_stdlib.py: Switch to mixedCase as Neil prefers. 2008-07-14 Thomas Vander Stichele * pychecker/checker.py: Don't pollute sys.path by inserting every moduleDir into it. This allows projects to have for example a gtk.py file in a package that does not conflict with the gtk system library. Instead, pollute it right before loading the module, then clean up immediately after. 2008-07-14 Thomas Vander Stichele * setup.py: If installing to a staging root, don't include this path in the install_lib dir. * setup.cfg: Set optimize = 1 to make rpm not complain about installed but missing .pyo files. Fixes python setup.py bdist_rpm 2008-07-14 Thomas Vander Stichele * pychecker/checker.py: Make sure args passed for checking now create PyCheckerModule instances with moduleDir set properly. This triggers the new code behaviour where modules are stored as a moduleName, moduleDir tuple in pcmodules.py, making sure same-named modules get treated separately. New test suite now passes again. Fixes #1563572. 2008-07-14 Thomas Vander Stichele * test/input/getmodule/A/C.py: * test/input/getmodule/A/__init__.py: * test/input/getmodule/B/C.py: * test/input/getmodule/B/__init__.py: * test/input/getmodule/__init__.py: * test/input/test_getmodule.py: * test/test_module.py: * test/main.py: Add a test that shows how warnings in modules with the same name, but different directories, shadow each other. See http://sourceforge.net/tracker/index.php?func=detail&aid=1563572&group_id=24686&atid=382217 2008-07-14 Thomas Vander Stichele * test/common.py: add check_multiple test method that allows checking more than one argument. * test/expected/test_zope_interface: update expected string 2008-07-14 Thomas Vander Stichele * pychecker/pcmodules.py: New module; replaces checker._allModules Allows us to track and differentiate between modules with the same name but from different paths. * pychecker/checker.py: Use it instead of _allModules * pychecker/warn.py: Also use it in the code that checks if a base class's __init__ was called, instead of only checking sys.modules, which only works if we pollute sys.modules (which is what we're trying to fix) 2008-07-13 Thomas Vander Stichele * pychecker/checker.py: Instantiate Class with the PycheckerModule instance instead of the real module; this allows us to see and act on presence of moduleDir. 2008-07-13 Thomas Vander Stichele * pychecker/checker.py: Change _findModule to take an optional moduleDir keyword argument. Use it in setupMainCode Make sibling imports work by temporarily putting moduleDir on sys.path 2008-07-13 Thomas Vander Stichele * pychecker/checker.py: Add self.moduleDir to filename() for PyCheckerModule. 2008-07-13 Thomas Vander Stichele * pychecker/checker.py: Refactor code such that we can pass a moduleDir keyword argument to PyCheckerModule. This will allow us to discern between modules with the same name, but in a different directory. Change allModules to be a dict of (moduleName, moduleDir) -> module No behaviour change yet. 2008-07-13 Thomas Vander Stichele * pychecker/checker.py: When showing what we're processing, show module name and file name. * test_expected/test1: * test_expected/test1-2.3: * test_expected/test1-2.4: * test_expected/test1-2.5: * test_expected/test10: * test_expected/test100: * test_expected/test101: * test_expected/test11: * test_expected/test12: * test_expected/test13: * test_expected/test14: * test_expected/test15: * test_expected/test16: * test_expected/test17: * test_expected/test17-2.4: * test_expected/test18: * test_expected/test19: * test_expected/test2: * test_expected/test20: * test_expected/test20-2.4: * test_expected/test20-2.5: * test_expected/test21: * test_expected/test22: * test_expected/test23: * test_expected/test24: * test_expected/test25: * test_expected/test26: * test_expected/test27: * test_expected/test27-2.2: * test_expected/test27-2.3: * test_expected/test27-2.4: * test_expected/test27-2.5: * test_expected/test28: * test_expected/test29: * test_expected/test3: * test_expected/test3-2.2: * test_expected/test3-2.3: * test_expected/test3-2.4: * test_expected/test3-2.5: * test_expected/test30: * test_expected/test31: * test_expected/test32: * test_expected/test33: * test_expected/test34: * test_expected/test34-2.2: * test_expected/test34-2.3: * test_expected/test34-2.4: * test_expected/test34-2.5: * test_expected/test35: * test_expected/test36: * test_expected/test37: * test_expected/test38: * test_expected/test39: * test_expected/test39-2.2: * test_expected/test39-2.3: * test_expected/test39-2.4: * test_expected/test39-2.5: * test_expected/test4: * test_expected/test40: * test_expected/test41: * test_expected/test42: * test_expected/test43: * test_expected/test44: * test_expected/test45: * test_expected/test46: * test_expected/test47: * test_expected/test48: * test_expected/test49: * test_expected/test5: * test_expected/test50: * test_expected/test51: * test_expected/test52: * test_expected/test53: * test_expected/test54: * test_expected/test55: * test_expected/test56: * test_expected/test57: * test_expected/test58: * test_expected/test58-2.2: * test_expected/test58-2.3: * test_expected/test58-2.4: * test_expected/test58-2.5: * test_expected/test59: * test_expected/test6: * test_expected/test60: * test_expected/test61: * test_expected/test62: * test_expected/test63: * test_expected/test64: * test_expected/test65: * test_expected/test66: * test_expected/test67: * test_expected/test68: * test_expected/test68-2.4: * test_expected/test68-2.5: * test_expected/test69: * test_expected/test7: * test_expected/test70: * test_expected/test71: * test_expected/test71-2.4: * test_expected/test71-2.5: * test_expected/test72: * test_expected/test73: * test_expected/test74: * test_expected/test74-2.4: * test_expected/test74-2.5: * test_expected/test75: * test_expected/test75-2.2: * test_expected/test75-2.3: * test_expected/test75-2.4: * test_expected/test75-2.5: * test_expected/test76: * test_expected/test77: * test_expected/test78: * test_expected/test79: * test_expected/test8: * test_expected/test80: * test_expected/test80-2.2: * test_expected/test80-2.3: * test_expected/test80-2.4: * test_expected/test80-2.5: * test_expected/test81: * test_expected/test82: * test_expected/test83: * test_expected/test84: * test_expected/test85: * test_expected/test86: * test_expected/test87: * test_expected/test88: * test_expected/test88-2.4: * test_expected/test89: * test_expected/test89-2.2: * test_expected/test89-2.3: * test_expected/test89-2.4: * test_expected/test89-2.5: * test_expected/test9: * test_expected/test90: * test_expected/test92: * test_expected/test93: * test_expected/test94: * test_expected/test95: * test_expected/test96: * test_expected/test97: * test_expected/test98: * test_expected/test99: Fix all expected test output for this change, without any regressions. 2008-07-13 Thomas Vander Stichele * pychecker/checker.py: Document and change getModules such that, for each argument that is a file, it returns a moduleName, moduleDir tuple so that modules with the same name can still be treated separately. Change callers to follow. First step towards fixing the bug where modules with the same name shadow each other's pychecker warnings. 2008-07-13 Thomas Vander Stichele * test/common.py: Document method. 2008-07-12 Thomas Vander Stichele * pychecker/warn.py: Change getStandardLibrary to getStandardLibraries (a list), so we can handle both the arch-invariant and arch-specific python site-packages. This makes sure that -q/--stdlib works the same way on 32-bit and 64-bit systems. Fixes #1564614, and makes the new test suite pass. (Try it with python test/main.py) 2008-07-12 Thomas Vander Stichele * test/test_stdlib.py: Rename and document test. 2008-07-12 Thomas Vander Stichele * test/common.py: * test/expected/test_zope_interface: * test/input/test_zope_interface.py: * test/main.py: * test/test_stdlib.py: Add a first stab at a unittest-based testsuite. Add a test for bug #382217 that I wanted to fix that can't be tested in the current test suite. 2008-07-04 Thomas Vander Stichele * test_input/test86.py: Add a test for except KeyboardInterrupt, which was fixed by Neil in pychecker/CodeChecks.py: 1.170 on 23-Apr-07. 2008-07-02 Thomas Vander Stichele * pychecker/CodeChecks.py: Allow using setattr with a static argument inside lambda calls, where the alternative is a syntax error since you're not allowed to do an assignment expression. Fixes #1565876. 2008-07-01 Thomas Vander Stichele * pychecker/checker.py: Revert IDLE patch which breaks test42.py See #2007203. 2008-07-01 Thomas Vander Stichele * pychecker/checker.py: * pychecker/warn.py: Respect the blacklist of modules when warning about classes for which the module cannot be found. * test_check.sh: * test_expected/test101: * test_input/test101.py: Add a test for it. Fixes #1563495. 2008-07-01 Thomas Vander Stichele * test_expected/test100: * test_input/test100.py: Actually add the tests. 2008-06-30 Thomas Vander Stichele * pychecker/checker.py: If an object looks like a class object because it has __bases__ but it does not have __names__, make up a name. Fixes #1563494. * test_check.sh: Add a test that previously failed. 2008-06-30 Thomas Vander Stichele * MANIFEST.in: * CHANGELOG: * NEWS: Moved CHANGELOG to NEWS since it really contains release news. Created real ChangeLog. pychecker-0.8.19/setup.cfg0000644000076400007640000000032511036700641014765 0ustar thomasthomas[bdist_rpm] doc_files = README ChangeLog COPYRIGHT KNOWN_BUGS MAINTAINERS NEWS pycheckrc TODO # adding this so that bdist_rpm does not complain about installed but # unpackaged .pyo files [install] optimize = 1 pychecker-0.8.19/PKG-INFO0000664000076400007640000000176411512046402014250 0ustar thomasthomasMetadata-Version: 1.0 Name: pychecker Version: 0.8.19 Summary: Python source code checking tool Home-page: http://pychecker.sourceforge.net/ Author: Neal Norwitz Author-email: nnorwitz@gmail.com License: BSD-like Description: PyChecker is a tool for finding bugs in python source code. It finds problems that are typically caught by a compiler for less dynamic languages, like C and C++. Because of the dynamic nature of Python, some warnings may be incorrect; however, spurious warnings should be fairly infrequent. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Debuggers Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: Software Development :: Testing pychecker-0.8.19/pycheckrc0000664000076400007640000001444610330070160015045 0ustar thomasthomas# Sample defaults file for PyChecker 0.8.17 # This file should be called: .pycheckrc # It should be placed in your home directory (value of $HOME). # If $HOME is not set, it will look in the current directory. # bool: only warn about files passed on the command line only = 0 # int: the maximum number of warnings to be displayed limit = 10 # bool: warnings for Doc Strings noDocModule = 0 noDocClass = 0 noDocFunc = 0 # bool: when checking if class data members (attributes) are set # check all members or __init__() only onlyCheckInitForMembers = 0 # bool: warn when all module variables are not used (including private vars) allVariablesUsed = 0 # bool: produce warnings for each occurrence of a warning for global (xxx) reportAllGlobals = 0 # bool: warn when private module variables are not used (_var) privateVariableUsed = 1 # bool: warn when imports are not used importUsed = 1 # bool: warn when import and from ... import are used for same module mixImport = 1 # bool: warn when imports are not used in __init__.py packageImportUsed = 1 # bool: warn when a module reimports another module (import & from/import) moduleImportErrors = 1 # bool: warn when modules import themselves reimportSelf = 1 # bool: warn when local variables are not used localVariablesUsed = 1 # bool: assume a, b, and c are used in this case: a, b, c = func() unusedLocalTuple = 0 # bool: warn when class attributes (data members) are unused membersUsed = 0 # bool: warn when Subclass.__init__ is not called in a subclass baseClassInitted = 1 # bool: warn when Subclass needs to override methods that only throw exceptions abstractClasses = 1 # bool: warn when __init__ is defined in a subclass initDefinedInSubclass = 0 # bool: warn when __init__ returns None returnNoneFromInit = 1 # bool: warn when code is not reachable unreachableCode = 0 # bool: warn when a constant is used in a conditional statement (if '':) constantConditions = 1 # bool: warn when 1 is used in a conditional statement, (if 1: while 1: etc) constant1 = 0 # bool: warn when iterating over a string in a for loop stringIteration = 1 # bool: warn when setting a variable to different types inconsistentTypes = 0 # bool: warn when setting a tuple of variables to a non-sequence (a, b = None) unpackNonSequence = 1 # bool: warn when setting a tuple of variables to the wrong length (a, b = 1,) unpackLength = 1 # bool: warn when using strings exceptions or # other classes not derived from Exception to raise/catch exceptions badExceptions = 1 # bool: warn when statements appear to have no effect noEffect = 1 # bool: warn when using (expr % 1), it has no effect on integers and strings modulo1 = 1 # bool: warn if using (expr is const-literal), # doesn't always work on integers and strings isLiteral = 1 # bool: warn when possibly using string.find() improperly (if s.find():) stringFind = 1 # bool: warn when using a deprecated module or function deprecated = 1 # bool: warn when the class attribute does not exist classAttrExists = 1 # bool: warn when calling an attribute not a method callingAttribute = 0 # bool: warn when using named arguments: func(a=1, b=2), where def func(a, b): # def func2(a, b, **kw): doesn't generate a warning namedArgs = 0 # str: name of 'self' parameter methodArgName = 'self' # list of str: names of first parameter to classmethods classmethodArgNames = ['cls', 'klass'] # bool: warn when method/function arguments are unused argumentsUsed = 1 # bool: ignore if self is unused in methods ignoreSelfUnused = 0 # bool: warn if functions/classes/methods names are redefined in same scope redefiningFunction = 1 # bool: check if an overriden method has the same signature # as base class method (__init__() methods are not checked) checkOverridenMethods = 1 # bool: check if a special (reserved) method has the correct signature # and is known (these are methods that begin and end with __ checkSpecialMethods = 1 # int: warnings for code complexity, max value before generating a warning maxLines = 200 maxBranches = 50 maxReturns = 10 maxArgs = 10 maxLocals = 40 maxReferences = 5 # bool: ignore all warnings from standard library components # (this includes anything under the standard library, eg, site-packages) ignoreStandardLibrary = 0 # list of strings: ignore unused locals/arguments if name is one of unusedNames = [ '_', 'empty', 'unused', 'dummy', ] # list of strings: ignore warnings generated from these modules blacklist = [ 'Tkinter', 'wxPython', 'gtk', 'GTK', 'GDK', ] # list of strings: ignore global variables not used if name is one of variablesToIgnore = [ '__all__', '__version__', '__copyright__', ] # bool: print the PyChecker parse of modules, classes, etc. printParse = 0 # bool: turn debugging of PyChecker on debug = 0 # bool: check that attributes of objects exist checkObjectAttrs = 1 # bool: various warnings about incorrect usage of __slots__ slots = 1 # bool: check if __slots__ is empty emptySlots = 1 # bool: check for using properties in classic classes classicProperties = 1 # bool: check for integer division (may be problem between Python versions) intDivide = 1 # bool: check if local variables shadow a global variable with same name shadows = 1 # bool: check if local variables shadow a builtin variable with same name shadowBuiltins = 1 # bool: check if input() is used, which is a security problem, use raw_input() usesInput = 1 # bool: check if using a constant string to getattr()/setattr() constAttr = 1 # bool: check for using +variable, since it is almost always has no effect unaryPositive = 1 # bool: check for modifying a parameter with a default value # (value must be: list, dict, instance) # modifying the value may have undesirable/unexpected side-effects modifyDefaultValue = 1 # bool: check if the exec statement is used (possible security problem) usesExec = 0 # bool: check consistent return values checkReturnValues = 1 # bool: check if using implict and explicit return values checkImplicitReturns = 1 # dict: suppress warnings, key is module.class.method or module.function # value is a string of command line arguments (can omit -- for long args) # { 'module1': 'no-namedargs maxlines=0', # 'module2.my_func': 'argsused', # 'module3.my_class': 'no-initreturn', } suppressions = {} # dict: suppress warnings where keys can be regular expressions suppressionRegexs = {} pychecker-0.8.19/VERSION0000664000076400007640000000000711217147674014227 0ustar thomasthomas0.8.18 pychecker-0.8.19/NEWS0000644000076400007640000005553311511444547013666 0ustar thomasthomasVersion 0.8.19 - 8 January 2011 * Add support for Python 2.7 * Fixed SF Bug [ 2209631 ]: New pcmodules.py module, modulePath code causes import error * Fixed SF Bug [ 1565876 ]: pychecker does not allow lambda with setattr and const * Fixed SF Bug [ 1564614 ]: pychecker -q on 64-bit ignores the platform-specific stdlib * Fixed SF Bug [ 1563572 ]: order of checking files affects errors reported PyChecker was only checking one of each set of modules with the same name; so it will now catch many more warnings it was missing before. * Fixed SF Bug [ 1563495 ]: couldn't find real module does not respect blacklist * Fixed SF Bug [ 1563494 ]: pychecker tracebacks when importing zope.interface.declaration * Add unittest-based testsuite. * Warn about missing opcodes. * Added implementations for the following opcodes: BUILD_SLICE, DELETE_SLICE3, DUP_TOPX, JUMP_IF_FALSE/TRUE, JUMP_IF_FALSE/TRUE_OR_POP, MAP_ADD, PRINT_NEWLINE_TO, SET_ADD, SETUP_WITH, SLICE0, STORE_MAP, STORE_SLICE+0, STORE_SLICE2, WITH_CLEANUP, YIELD_VALUE Version 0.8.18 - 17 August 2008 * Fixed SF Bug [ 1827412 ] pychecker.bat only handles 9 cmdline args * Fix warning about comparison checking with bool false alert for 'in' and 'not in' * Add warning for using __set__, __get__, and __delete__ in an old-style class * Fix spurious warning about __set__, __get__, and __delete__ not being special methods. * Fix inability to disable Warning about constant setattr()/getattr() * Fix spurious warning on min/max not accepting kwarg of key in Python 2.5 * Add some __special__ pickling methods that were missing * Add --missingattrs option to ignore some (but not all) attribute names * Fix crash when using keyword arguments with builtin methods * Add --keepgoing option to ignore import errors * Add --findevil option to make it easier to find objects that crash the interpreter * Add support for Python 2.5 Version 0.8.17 - 3 February 2006 * Fix spurious warning for Statement with no effect using bit shifts * Add -#/--limit command line option to set the max # of warnings to show * Remove broken command line options: -e/--errors, --complexity * Add -e/--level command line options which allows the error level to be specified: error, security, warning, unused, deprecated, style. These names map to numbers: 90, 90, 70, 50, 40, 10 (error == security). Specifying a value means all levels equal to or greater than it. * Add --only option which displays warnings only for files specified on the command line * Add --evil option for users to prevent the interpreter from crashing due to broken C extensions * Fix wrong file name when warning about returning values from __init__ functions. (#1291116) * Fix a few more glitches with setup.py. * Suppress warning about integer division when the code is: int(x / y) * Add code to skip testing objects from extension modules that are known to crash the interpreter. Currently, the list includes old versions of matplotlib.axes.BinOpType and wx.TheClipboard. * Support ROT_THREE and ROT_FOUR opcodes Version 0.8.16 - 4 September 2005 * Fix problems installing on Windows and running setup.py build Version 0.8.15 - 31 August 2005 * Fix spurious warning about catching string exceptions * Don't barf if there is # -*- encoding: ... -*- lines and unicode strings * setup.py was rewritten to honor --root, --home, etc options * Fix internal error on processing nested scopes * Fix constant tuples in Python 2.4 * Don't warn about implicit/explicit returns in Python 2.4, we can't tell * Fix crash when __slots__ was an instance w/o __len__ * Fix bug that declared {}.pop to only take one argument, it takes 1 or 2 * Fix spurious warning when using tuples for exceptions * Fix spurious warning / * Fix spurious warnings for sets module about __cmp__, __hash__ * Changed abstract check to require raising NotImplementedError rather than raising any error * Fix spurious warnings in Python 2.4 for Using is (not) None warnings * Fix spurious warnings for some instances of No class attribute found * Fix spurious warnings for implicit returns when using nested functions Version 0.8.14 - 6 June 2004 * Fix spurious warning in Python 2.3+ when doing a,b = 1,2 * Add warning to check for "if s.find(str):" this should be "if s.find(str) >= 0:" when s is a string * Fix spurious warning when using augmented assignment (e.g., x += x) * Fix spurious warning when doing x = not x * Fix spurious warning for invalid arg count when calling a staticmethod * Fix spurious warning for setting a variable to itself when: x, y, z = x * Fix spurious warning when doing some binary operations: self.a ^ self.b * Fix crash in STORE_ATTR with some list comprehensions in Python 2.2 e.g., a.a = [x for x in range(2) if x > 1] * Support Tk 8.4 and above in the GUI (change col= to column=) Version 0.8.13 - 18 November 2003 * Add -s/--shadowbuiltin option to warn when overriding a builtin * Add warning when assigning a variable to itself * Add warning when dividing a variable by itself * Add warnings when using a bit-wise operator with the same variable (a & a) * Add warning when passing a constant string to getattr()/setattr() * Add --special option to check for __special__ (reserved) method names and that their signature (argument count) is correct * Add warning for using __getattribute__ in an old-style class * Suppress 'self as first argument' warning for static and class methods * Add --classmethodargs option to specify first argument name for class methods Version 0.8.12 - 23 December 2002 * Add --isliteral option to warn when using is/is not against literal (e.g., XXX is 5 YYY is not 'abcdef', etc) * Add --modulo1 option to warn when using (expr % 1), which is constant when expr is an integer or string * Add -4/--noeffect option to disable warnings for 'Statement has no effect' * self is not first argument warning can be disabled with -S/--self '' * Don't print duplicate warnings when importing pychecker * Fix other spurious warnings when importing pychecker * Fix bug for spurious invalid arguments when passing a dict inline * Fix bug for spurious Statement has no effect when print >> x, ... , * Add @option_file command line argument to read options from a file (used on platforms which can't have too many options, ie Windows) * Fix several crashes * Line numbers should be close for Python 2.3 and if run on optimized code Version 0.8.11 - 06 June 2002 * Improve error message for syntax errors from user files * Fix pychecker.bat so it should work now * Add a warning for using __coerce__ in new-style classes * Add --deprecated option for using deprecated modules or functions * Add a warning for using functions with security problems (os.t[e]mpnam) * Add a warning for comparing against True/False or defining True/False * Add --badexcept option to warn when using string exceptions or classes not derived from Exception to raise/catch exceptions * Fix spurious warnings from using (test and 'true' or 'false) Version 0.8.10 - 20 March 2002 * Add --unpack option to warn when unpacking a non-sequence * Add --unpacklen option to warn when unpacking sequence of wrong size * Add --changetypes option to warn when setting a variable to different types * Add --stringiter option to warn when iterating over a string * Add --input option to warn when using input() * Fix crash with checking properties or deriving from objects (2.2 only) * Fix crash with nested scopes and lambdas * Fix spurious warnings for constant conditionals when using ('%s' % value) * Fix spurious warnings for unused identifiers caused by from XXX import * * Add more information when module cannot be imported * Fix spurious warnings for implicit returns when using while 1: * Fix spurious warnings for implicit returns when using try/finally: * Fix spurious warning with globals that start w/__ * Fix spurious warnings for modifying default arguments when calling {}.get(), {}.has_key(), [].index(), [].count(), etc. * Fix spurious warnings in Python 1.5.2 when using from/import Version 0.8.9 - 02 February 2002 * Add -3/--properties warning when using properties with classic classes * Add more warnings for statements with no effect * Fix crash due to import module problems * Fix crash with nested scopes * Fix spurious warnings about module attributes and importing * Fix spurious warnings in Python 2.2 when using builtin classes (eg, socket) * Fix spurious warning for format string problem when using % at module scope * Fix spurious warning for implicit returns when doing while 1: * Fix spurious warning for inconsistent return types when objects are subclasses * Don't warn about inconsistent return types from __getattr[ibute]__ * Always assume readline module is used if input/raw_input is used Version 0.8.8 - 13 January 2002 * Add -F/--config option to specify pycheckrc file to use Always read $HOME/.pycheckrc, .pycheckrc, and -F options in that order * Add -0/--abstract option to warn that subclass should override a base class whose method(s) only raise exceptions * Add -6/--exec option to warn when using the exec statement * Add -7/--slots option to warn about __slots__ usage problems * Add --emptyslots option to warn about empty __slots__ * Add check if __getattr[ibute]__ returns None, should raise an exception * Allow pychecker to be imported (in your code do: import pychecker.check) * Using vars() for format string argument works like locals() * Make unusedNames a prefix, so emptyVal, unusedVal, etc are also ignored * Fix -a/--initattr warning to actually warn when attributes are set outside of __init__(), but not in __init__() * Fix case where an implicit return did not generate a warning * Fix spurious warnings when using nested scopes, code should be checked now * Fix spurious warnings for unreachable code (not enough to enable, yet) * Fix spurious warnings for integer division from: (x + 100.0) / 10 * Fix spurious warnings for implicit returns when raise in except: clause * Fix various spurious warnings when using Python 2.2 * Fix spurious warning for overridden method mismatch when using exec * Fix spurious warning when doing len(filter(lambda x: ..., ...)) * Fix spurious warnings from some from ... import ... and deriving classes Version 0.8.7 - 05 January 2002 * Add -2/--constcond option to warn if using a constant in a conditional statement (if '': ; while 'str': ; etc) * Add -1/--constant1 option to warn when using if 1: or while 1: etc. * Add -8/--unreachable option to warn about unreachable code * Add -9/--members option to warn about unused data members * Add -w/--shadow for local variable shadowing global variable * Add warning statements with no effect (load var, but do nothing) * Handle string multiplication (helps format strings) code like this doesn't generate a warning now: '%d ' * 3 % (1, 2, 3) * Fix -C/--implicitreturns option so it works, turn it on by default * Fix spurious warning when accessing 'static' class members that are methods (e.g. class C(B): __super_init = B.__init__) (still a problem w/2.2) * Fix some strange spurious warnings and exceptions * Handle nested scopes better, includes fixing a crash and eliminating some spurious warnings Version 0.8.6 - 16 November 2001 * Add -5/--maxrefs for maximum # of identifier references (Law of Demeter) * Fix problem where user defined classes weren't checked for valid attributes * Allow use of __pychecker__ in class scope * Fix a lot of global function arg counts for Python 2.2 (many constructors can take no arguments now) * Fix spurious warning (Function return types are inconsistent) when multiple returns w/a constant & a local variable of same type * Fix spurious warning for format strings when using a dict local variable * Rename pychecker.sh script to pychecker on Unix Version 0.8.5 - 17 October 2001 * Add check for using builtin function/method const (None) return value (e.g., not_a_new_list = [].sort()) * Add check for builtin object method calls for right # args (e.g., [].count(), {}.keys(), file.seek(0)) * Add check for object method calls for right # args (calling object when type is known, ie instatiated locally) * Add check for modifying a parameter that has a default value (e.g., def func(mutable = []): mutable.append(0)) * Add check for using future keywords (e.g., yield) * Add check for using unary positive on variables (e.g., +x) * Add check for recursive calls to __repr__ implementations (`self`) * Add -X/--reimport option to turn off various module reimport warnings * Add -V/--version option for printing PyChecker version * Fix some spurious warnings for inconsistent return value types * Fix spurious warning from calling functions like: zip(*args) * Fix -F/--rcfile option so it really works Version 0.8.4 - 24 September 2001 * Allow warning suppressions to be specified as regular expressions * Add -z/--no-varargsused to ignore *args for functions w/variable args * Add warning checks when deleting variables * Add check for except Error1, Error2 : # should be except (Error1, Error2) : * Fix spurious warning when using parameter as dict for format string * Fix spurious warning when using lambda in __init__ * Add check that pychecker/checker.py is same version as other files * Get setup.py to work on windows, etc. if don't have /tmp Version 0.8.3 - 12 August 2001 * Fix internal errors when doing % on non-strings and other format problems * Fix spurious warning when using a constant {} with a format mapping Version 0.8.2 - 12 August 2001 * Check format strings even if using global & local constants * Add check that [].append() only takes one argument * Add check that # parameters are correct for builtin functions * Add warnings for --, ++, ~~: "Operator (%s) doesn't exist, statement has no effect" * Add -Q/--quiet to be real quiet, only output warning msgs, nothing else * Add -y/--classattr config option (warning was not configurable before) (warn if class attribute doesn't exist) * Add -x/--miximport config option (warning was not configurable before) (warn if mixing: import/from ... import) * Add -u/--callinit config option (warning was not configurable before) (warn if Subclass.__init__() not called) * Add constants together on stack when get + to avoid some spurious warnings * Fix method and attribute checks for None and Ellipsis * Fix spurious warnings when doing a local import and use module in lambda * Fix spurious warnings when object attribute has same name as class * Fix Object (x) has no attribute warnings for Python 2.2a1 built-in types * Change default behaviour to not warn about missing doc strings Version 0.8.1 - 9 August 2001 * Fix internal error when referencing a list constant: [1,2,3][1] * Fix internal error for Python 1.5 not catching unicode syntax error * Fix deprecation warning for Python 2.2a1 with xrange * Fix spurious warning (No module attribute) when doing import x.y as y * Fix spurious warning (Base __init__() not called) when using *args or **kw * Fix 'No module attribute' warning when doing: import foo.bar as bar * Spell overridden write Version 0.8 - 6 August 2001 * Add check for accessing list as list[1,2], should be slice 1:2 * Add -J/maxargs to warn when using too many arguments * Add -K/maxlocals to warn when using too many local variables * Add -D/intdivide to warn when using integer division * Add -O/objattrs to warn when using object.attribute that doesn't exist * Add -M/reimportself to warn when a module imports itself * Add -E/unusednames to provide a list of unused names to ignore (default is: [ '_', 'empty', 'unused' ]) * Major refactoring of warn.py -> utils, msgs, Warning, CodeChecks * Fix spurious warnings for No class attribute for dynamic classes * Fix spurious warnings when using objects from blacklisted modules (Instantiating object with arguments, but no constructor, etc) * Fix spurious warnings when using lambdas * Fix spurious warning (No global Y) when using: from X import Y (problem in python 1.5/1.6 only) Version 0.7.5 - 22 July 2001 * Suppress warnings on a per module/function/class/method basis with new suppressions = {} in .pycheckrc * Suppress warnings by setting __pychecker__ in source code * Change long argument behaviour --arg sets arg to true, --no-arg sets arg to false (also works for warning suppression) * Add -U/--reuseattr check if function/class/method names are reused * Add -T/--argsused check for unused method/function arguments * Add -G/--selfused ignore if self is unused in a method (requires --argsused) * Add -q/--stdlib to disable warnings from the standard library * Add -o/--override warning when a method has different signature than one being overridden in a base class * Add -F/--rcfile to generate a .pycheckrc file * Fix checking files in standard library before local file * Fix spurious warning when using from X import Y and imports in methods (Module (m) re-imported) * Fix spurious warning when doing: from X import Y, Z * Fix spurious warning when deriving from Exception() and instantiating object with multiple arguments * Fix method argument checks when calling base class methods * Fix error msg to base constructors (msg count was wrong) * Fix access to builtin module attributes (e.g., sys.exc_value) generating 'No attribute warnings' * Fix tests (forgot to add : after line number in expected results) Version 0.7 - 16 July 2001 * Improve import warning messages, add from checks * checker.py -h prints defaults after processing .pycheckrc file * Add config option -k/--pkgimport to disable unused imports from __init__.py * Add warning for variable used before being set * Improve format string checks/warnings * Check arguments to constructors * Check that self is first arg to base constructor * Add -e/--errors option to only warn about likely errors * Make 'self' configurable as the first argument to methods * Add check that there is a c'tor when instantiating an object and passing arguments * Add config option (-N/--initreturn) to turn off warnings when returning None from __init__() * Fix internal error with python 2.1 which defines a new op: LOAD_DEREF * Check in lambda functions for module/variable use * Fix inability to evaluate { 1: 'a' } inline, led to incorrect __init__() not called warnings * Fix exception when class overrides __special__() methods & raise exception * Fix check in format strings when using '%*g %*.*g', etc * Add check for static class attributes * Fix checking of module attributes * Fix wrong filename in 'Base class (xxx) __init__() not called' when doing a from X import * * Fix 'No attribute found' for very dynamic classes (may also work for classes that use __getattr__) Version 0.6.1 - 27 June 2001 * Fix bug which caused an exception from some import code * Fix bug in determining if there is an implicit return Version 0.6 - 25 June 2001 * Check format strings: "%s %s %s" % (v1, v2, v3, v4) for arg counts * Warn when format strings do: '%(var) %(var2)' * Fix Local variable (xxx) not used, when have: "%(xxx)s" % locals() * Warn when local variable (xxx) doesn't exist and have: "%(xxx)s" % locals() * Install script in /usr/local/bin to invoke PyChecker * Don't produce unused global warnings when using a module in parameters * Don't produce unused global warnings when using a module in class variables * Add check when using method as an attribute (if self.method and x == y:) * Add check for right # of args to object construction * Add check for right # of args to function calls in other modules * Check for returning a value from __init__ * Fix using from XX import YY ; from XX import ZZ causing re-import warning * Fix UNABLE TO IMPORT errors for files that don't end with a newline * Support for checking consistent return values -- not complete produces too many false positives Version 0.5 - 29 May 2001 * Catch internal errors "gracefully" and turn into a warning * Add checking of most module scoped code * Add pychecker subdir to imports to prevent filename conflicts * Don't produce unused local variable warning if variable name == '_' * Add -g/--allglobals option to report all global warnings, not just first * Add -V/--varlist option to selectively ignore variable not used warnings * Add test script and expected results * Print all instructions when using debug (-d/--debug) * Overhaul internal stack handling so we can look for more problems * Fix glob'ing problems (all args after glob were ignored) * Fix spurious Base class __init__ not called * Fix exception on code like: ['xxx'].index('xxx') * Fix exception on code like: func(kw=(a < b)) * Fix line numbers for import statements Version 0.4 - 23 April 2001 * Add .pycheckrc file processing to specify options (like on command line) * Add new warning if module.Attribute doesn't exist * Add new warning: Module (%s) re-imported locally * Add glob'ing support for windows * Handle apply(BaseClass.__init__(self, args)) * Fix command line handling so you can pass module, package, or filename * Fix **kwArgs warning if named parameter is not first * Don't exit from checker when import checker from interpreter Version 0.3 - 17 April 2001 * Fix some checker crashes (oops) * Add warnings for code complexity (lines/branches/returns per function) * Add more configuration options * Don't produce spurious warning for: x(y, { 'a': 'b' }) * Fix warnings that indicate they are from a base class file, rather than real file * Fix warnings for **kwArgs not allowed, but using named args * Add configuration option for warning when using attribute as a function (off by default, old behaviour was on) Version 0.2.5 - 12 April 2001 * Add back support for Python 1.5.2 (again) (I sure like 2.0 more with the [ for ] and string methods.) * Add new warning for unused local variables * Add command line switches Version 0.2 - 10 April 2001 * Move tests into a sub-directory so import test doesn't import pychecker/test * Add more test files * Add '.' to the python path so it doesn't need to be done in env't * Print a warning at the end for each file that couldn't be import'ed * Improve stack handling to improve error handling * Try to get base class __init__ checking to work for both: import X.Y from X import Y Version 0.1.1 - 8 April 2001 * Add support for Python 1.5.2 Version 0.1 - 7 April 2001 * Initial release pychecker-0.8.19/COPYRIGHT0000664000076400007640000000272007263624557014464 0ustar thomasthomasCopyright (c) 2000-2001, MetaSlash Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither name of MetaSlash Inc. nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pychecker-0.8.19/README0000644000076400007640000001436011512043517014031 0ustar thomasthomasPyChecker is a tool for finding bugs in python source code. It finds problems that are typically caught by a compiler for less dynamic languages, like C and C++. It is similar to lint. Because of the dynamic nature of python, some warnings may be incorrect; however, spurious warnings should be fairly infrequent. PyChecker works in a combination of ways. First, it imports each module. If there is an import error, the module cannot be processed. The import provides some basic information about the module. The code for each function, class, and method is checked for possible problems. Types of problems that can be found include: * No global found (e.g., using a module without importing it) * Passing the wrong number of parameters to functions/methods/constructors * Passing the wrong number of parameters to builtin functions & methods * Using format strings that don't match arguments * Using class methods and attributes that don't exist * Changing signature when overriding a method * Redefining a function/class/method in the same scope * Using a variable before setting it * self not the first parameter to a method * Unused globals and locals (module or variable) * Unused function/method arguments (can ignore self) * No doc strings in modules, classes, functions, and methods Using PyChecker --------------- To use PyChecker, pass the python source files you want to check on the command line: pychecker file1.py file2.py ... Note: On Windows, use pychecker.bat. You may also need to add python/scripts to your PATH. pychecker and pychecker.bat will only exist if pychecker has been installed. To install, do: python setup.py install Note: If you haven't installed pychecker, it can be run by doing: python pychecker/checker.py An alternate way to use PyChecker is to import it in your code. See 'Importing PyChecker' below for more details. If there are import dependencies in your source files, you should import those files first on the command line in order to get as many files checked as possible. PyChecker works with Python 2.0 through 2.7. Some features don't work on earlier versions of Python. PyChecker is tested with Python 2.2 through 2.7 using buildbot. You can use the test files as examples: pychecker [options] test_input/*.py If you want to change the default behaviour, you can pass command line options or define a .pycheckrc file. For an example, look at pycheckrc. To show the available options, do: pychecker -h Some of the most common options are: --only only warn about files passed on the command line [off] -#, --limit the maximum number of warnings to be displayed [10] -s, --shadowbuiltin check if a variable shadows a builtin [on] -q, --stdlib ignore warnings from files under standard library [off] -T, --argsused unused method/function arguments [on] There is a simple GUI which is not maintained much. It is good for showing all the options and also allows you to run pychecker. To run options, you will need to start it manually: python pychecker/options.py If you want to suppress warnings on a module/function/class/method, you can define a suppressions dictionary in .pycheckrc. Examples of keys are: 'module', 'module.function', 'module.class', 'module.class.method', etc. You can also define suppressions in your code by doing: __pychecker__ = 'no-namedargs maxreturns=0 unusednames=foo,bar' The format for __pychecker__ values and values in the suppressions dictionary are the same. Dashes (--) are optional when preceding long option names. Importing PyChecker ------------------- You can import PyChecker in your code's main module, by doing: import pychecker.checker This will allow each module imported after PyChecker to be checked (other than the main module). NOTE: Modules imported before PyChecker will not be checked. Warnings will be displayed on stdout (ie, PyChecker uses print). Since you can't pass command line parameters, you can do: os.environ['PYCHECKER'] = 'command line options here' This is equivalent of setting PYCHECKER in the shell environment: PYCHECKER='no-namedargs maxreturns=0' /path/to/your/program If you want to disable the warnings (and processing done by PyChecker), prior to importing PyChecker, do: os.environ['PYCHECKER_DISABLED'] = 1 This is equivalent of setting PYCHECKER_DISABLED in the shell environment: PYCHECKER_DISABLED=1 /path/to/your/program Internal Errors --------------- If you find a bug in PyChecker, meaning you see something like: pychecker myfile.py myfile.py:13 INTERNAL ERROR -- STOPPED PROCESSING FUNCTION -- Traceback (most recent call last): File "./pychecker/warn.py", line 364, in _checkFunction stack, oparg, lastLineNum) File "./pychecker/warn.py", line 195, in _handleFunctionCall kwArgs.append(stack[i].data) IndexError: list index out of range Please post a bug in the SourceForge Tracker (https://sourceforge.net/tracker/?atid=382217&group_id=24686&func=browse) or send mail indicating the version of PyChecker, *your source file* which broke PyChecker (myfile.py in the example above), and the traceback. It is very helpful to provide a simple test case to demonstrate the problem. It helps to have the entire file and all the dependencies if you cannot produce a simple test case. But if you can't provide a test case nor the file(s), I may be able to figure out the problem with just the line which broke PyChecker (myfile.py:13 in the example above). Good Luck! As always, feedback is greatly appreciated. Buildbot -------- Pychecker is tested on each commit using Buildbot. See http://build.fluendo.com:8200/ IRC --- Feel free to ask questions on #pychecker on irc.freenode.org Our friendly buildbot is there too. Projects using PyChecker ------------------------ Pychecker is regularly run on the following projects: * moap * morituri * savon * flumotion Before each release these projects get checked to test that PyChecker works. If your project uses PyChecker too, let us know so we can add it here and to our release checklist. Neal pychecker-list@lists.sourceforge.net PyChecker can be found on SourceForge at: http://pychecker.sourceforge.net/ http://sourceforge.net/projects/pychecker