Skip to main content

Source code file content

Revision: 2948

17477111 resync manpages from docs group
» Project Revision History

» Checkout URL

pkg-gate / src / tests / multiplatform.py

Size: 7452 bytes, 1 line
#!/usr/bin/python
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
from logilab import astng

from pylint.interfaces import IASTNGChecker
from pylint.checkers import BaseChecker
from logilab.common.modutils import is_standard_module, get_module_part

class MultiPlatformAPIChecker(BaseChecker):
    """
    This class implements a pylint extension which checks for the use
    of APIs which are not portable across operating systems.

    This class is a "Visitor" class which defines methods that are called
    during the static parsing of python source code that is being checked.
    These callbacks are called when the parser is visiting or leaving certain
    python language constructs.  In this case, we are interested when
    a CallFunc language element is visited (indicating a function call), and
    the follow-on calls to the GetAttr language element, and the Name
    element.  These abstractions are used to denote items in the python
    language syntax, and are defined at

    http://docs.python.org/lib/module-compiler.ast.html

    The callbacks in this class keep track of the names involved in the
    parsing, and when a complete function call is formed, and the
    "leave" callback is called (see the leave_callfunc method below), the
    fully-qualified method call is checked against the list of non-portable
    APIs.  If an API is called which is in this list, a pylint error is
    reported.

    Care is taken to allow for import statements which may re-define the
    python library module names within an importing module.  For example,
    if there is an import such as "import os as foo", then a call to
    foo.symlink, this would not be caught with a traditional 'grep'-style
    checker.  This is caught using this class. """

    #
    # The list of APIs that are not available on all modern, supported
    # platforms.
    #
    VERBOTEN = [
        'getpass.getuser', 'os.chown', 'os.chroot', 'os.confstr',
        'os.confstr_names', 'os.ctermid', 'os.fchdir',
        'os.fdatasync', 'os.fork', 'os.forkpty', 'os.fpathconf',
        'os.fstatvfs', 'os.ftruncate', 'os.getegid', 'os.geteuid',
        'os.getgid', 'os.getgroups', 'os.getlogin', 'os.getpgid',
        'os.getpgrp', 'os.getppid', 'os.getsid', 'os.getuid',
        'os.isatty', 'os.kill', 'os.killpg', 'os.lchown', 'os.link',
        'os.linkmknod', 'os.lstat', 'os.major', 'os.makedev',
        'os.minor', 'os.mkfifo', 'os.nice', 'os.openpty',
        'os.pathconf', 'os.pathconf_names', 'os.plock',
        'os.readlink', 'os.setegid', 'os.seteuid', 'os.setgid',
        'os.setgroups', 'os.setpgid', 'os.setpgrp', 'os.setregid',
        'os.setreuid', 'os.setsid', 'os.setuid', 'os.spawnl',
        'os.spawnle', 'os.spawnlp', 'os.spawnlpe', 'os.spawnv',
        'os.spawnve', 'os.spawnvp', 'os.spawnvpe', 'os.startfile',
        'os.statvfs', 'os.symlink', 'os.sysconf',
        'os.sysconf_names', 'os.tcgetpgrp', 'os.tcsetpgrp',
        'os.ttyname', 'os.uname', 'os.wait3', 'os.wait4',
        'signal.alarm', 'signal.getsignal', 'signal.signal',
        'socket.fromfd', 'socket.inet_ntop', 'socket.inet_pton',
        'socket.socketpair', 'sys.getdlopenflags',
        'sys.getwindowsversion', 'sys.setdlopenflags',
        'thread.stack_size', 'time.tzset', 'fcntl.fcntl', 'fcntl.ioctl',
        'fcntl.flock', 'fcntl.lockf',
    ]
    
    # The list of package prefixes that are allowed to call VERBOTEN APIs
    ALLOWED = [
        'pkg.portable',
    ]

    #
    # Messages to show when checking detects an error
    #
    msgs = {
    'E0900': ('Imported Non-Portable API (%s)' ,
              'Used when a non-portable API is imported.'),
    'E0901': ('Non-portable API used (%s)',
              'Used when a non-portable API is called.'),
    }

    __implements__ = IASTNGChecker
    name = 'multiplatform'
    options = ()

    imported_modules={}
    calledfuncname=[]
    calledfuncstack=[]

    # this is important so that your checker is executed before others
    priority = -1 

    def leave_getattr(self, node):
        self.calledfuncname.append(node.attrname)

    def leave_name(self, node):
        self.calledfuncname=[node.name]

    def visit_callfunc(self, node):
        self.calledfuncstack.append(self.calledfuncname)
        self.calledfuncname=[]

    def leave_callfunc(self, node):
        self._check_verboten_call(node, self.calledfuncname)
        self.calledfuncname=self.calledfuncstack.pop()

    def visit_import(self, node):
        """triggered when an import statement is seen"""
        for name, alias in node.names:
            if alias == None:
                alias = name
            self._check_verboten_import(node, name)
            self.imported_modules.update({alias: name})

    def visit_from(self, node):
        """triggered when an import statement is seen"""
        basename = node.modname
        for name, alias in node.names:
            fullname = '%s.%s' % (basename, name)
            self._check_verboten_import(node, fullname)
            if fullname.find('.') > -1:
                try:
                    fullname = get_module_part(fullname,
                                               context_file=node.root().file)
                except ImportError, ex:
                    # this is checked elsewhere in pylint (F0401)
                    continue
            if alias == None:
                alias = fullname         
            self.imported_modules.update({alias: fullname})

    def _is_allowed(self, node):
        for p in self.ALLOWED:
            if node.root().name.startswith(p):
                return True
        return False

    def _check_verboten_import(self, node, name):
        if self._is_allowed(node):
            return
        if name in self.VERBOTEN:
            self.add_message('E0900', args=(name), node=node)

    def _unalias(self, name):
        for i,e in enumerate(name):
            fullname = '.'.join(name[:i])
            if fullname in self.imported_modules:
                alias = self.imported_modules.get(fullname)
                return alias.split('.') +  name[i:]
        return name

    def _check_verboten_call(self,node, name):
        if self._is_allowed(node):
            return
        name = self._unalias(name)
        for i,e in enumerate(name):
            fullname = '.'.join(name[:i + 1])
            if fullname in self.VERBOTEN:
                self.add_message('E0901', args=(fullname), node=node)

def register(linter):
    """required method to auto register this checker"""
    linter.register_checker(MultiPlatformAPIChecker(linter))

 
 
Close
loading
Please Confirm
Close