--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/Plugins/CheckerPlugins/CodeStyleChecker/Security/SecurityNodeVisitor.py Mon Jun 08 08:17:14 2020 +0200 @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an AST node visitor for security checks. +""" + +import ast + +from . import SecurityUtils +from .SecurityContext import SecurityContext + + +class SecurityNodeVisitor(object): + """ + Class implementing an AST node visitor for security checks. + """ + def __init__(self, checker, secCheckers, filename): + self.__checker = checker + self.__securityCheckers = secCheckers + + self.seen = 0 + self.depth = 0 + self.filename = filename + self.imports = set() + self.import_aliases = {} + + # in some cases we can't determine a qualified name + try: + self.namespace = SecurityUtils.getModuleQualnameFromPath(filename) + except SecurityUtils.InvalidModulePath: + self.namespace = "" + + def __runChecks(self, checkType): + """ + Private method to run all enabled checks for a given check type. + """ + if checkType in self.__securityCheckers: + for check in self.__securityCheckers[checkType]: + check(self.__checker.reportError, + SecurityContext(self.__context), + self.__checker.getConfig()) + + def visit_ClassDef(self, node): + """ + Public method defining a visitor for AST ClassDef nodes. + + Add class name to current namespace for all descendants. + + @param node reference to the node being inspected + @type ast.ClassDef + """ + # For all child nodes, add this class name to current namespace + self.namespace = SecurityUtils.namespacePathJoin( + self.namespace, node.name) + + def visit_FunctionDef(self, node): + """ + Public method defining a visitor for AST FunctionDef nodes. + + Add relevant information about the node to the context for use in tests + which inspect function definitions. Add the function name to the + current namespace for all descendants. + + @param node reference to the node being inspected + @type ast.FunctionDef + """ + self.__context['function'] = node + qualname = SecurityUtils.namespacePathJoin(self.namespace, node.name) + name = qualname.split('.')[-1] + self.__context['qualname'] = qualname + self.__context['name'] = name + + # For all child nodes and any tests run, add this function name to + # current namespace + self.namespace = SecurityUtils.namespacePathJoin( + self.namespace, node.name) + + self.__runChecks("FunctionDef") + + def visit_Call(self, node): + """ + Public method defining a visitor for AST Call nodes. + + Add relevant information about the node to the context for use in tests + which inspect function calls. + + @param node reference to the node being inspected + @type ast.Call + """ + self.__context['call'] = node + qualname = SecurityUtils.getCallName(node, self.import_aliases) + name = qualname.split('.')[-1] + self.__context['qualname'] = qualname + self.__context['name'] = name + self.__runChecks("Call") + + def __preVisit(self, node): + """ + Private method to set up a context for the visit method. + + @param node node to base the context on + @type ast.AST + """ + self.__context = {} + self.__context['imports'] = self.imports + self.__context['import_aliases'] = self.import_aliases + + if hasattr(node, 'lineno'): + self.__context['lineno'] = node.lineno +## +## if node.lineno in self.nosec_lines: +## LOG.debug("skipped, nosec") +## self.metrics.note_nosec() +## return False + + self.__context['node'] = node + self.__context['linerange'] = SecurityUtils.linerange_fix(node) + self.__context['filename'] = self.filename + + self.seen += 1 + self.depth += 1 + + return True + + def visit(self, node): + """ + Public method to inspected an AST node. + + @param node AST node to be inspected + @type ast.AST + """ + name = node.__class__.__name__ + method = 'visit_' + name + visitor = getattr(self, method, None) + if visitor is not None: + visitor(node) + else: + self.__runChecks(name) + + def __postVisit(self, node): + """ + Private method to clean up after a node was visited. + + @param node AST node that was visited + @type ast.AST + """ + self.depth -= 1 + # Clean up post-recursion stuff that gets setup in the visit methods + # for these node types. + if isinstance(node, (ast.FunctionDef, ast.ClassDef)): + self.namespace = SecurityUtils.namespacePathSplit( + self.namespace)[0] + + def generic_visit(self, node): + """ + Public method to drive the node visitor. + + @param node node to be inspected + @type ast.AST + """ + for _, value in ast.iter_fields(node): + if isinstance(value, list): + maxIndex = len(value) - 1 + for index, item in enumerate(value): + if isinstance(item, ast.AST): + if index < maxIndex: + item._securitySibling = value[index + 1] + else: + item._securitySibling = None + item._securityParent = node + + if self.__preVisit(item): + self.visit(item) + self.generic_visit(item) + self.__postVisit(item) + + elif isinstance(value, ast.AST): + value._securitySibling = None + value._securityParent = node + if self.__preVisit(value): + self.visit(value) + self.generic_visit(value) + self.__postVisit(value)