mirror of
https://github.com/isledecomp/isle.git
synced 2024-11-26 01:28:30 -05:00
bc5ca621a4
* Add ncc tool * Add symlink * Fixes * Try this * Try this * Try this * Try this * Add include path * Update style * Update style * Add more rules * Fix style * Update styles * Fix name parameter * Fix MxParam p * Fix m_unk0x pattern * Allow 4 digits for relative hex * Add missing offset * Fix some parameters * Fix some vtables * Fix more vtables * Update rules, fixes * More fixes * More fixes * More fixes * More fixes * More fixes * More fixes * More fixes * Fix last issue * Update readme * Update readme * Update CONTRIBUTING.md * Fix annotations * Rename * Update CONTRIBUTING.md * Update README.md
661 lines
30 KiB
Python
661 lines
30 KiB
Python
#!/usr/bin/env python
|
|
|
|
# MIT License
|
|
#
|
|
# Copyright (c) 2018 Nithin Nellikunnu (nithin.nn@gmail.com)
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
import logging
|
|
import argparse
|
|
import yaml
|
|
import re
|
|
import sys
|
|
import difflib
|
|
import os
|
|
import fnmatch
|
|
from clang.cindex import Index
|
|
from clang.cindex import CursorKind
|
|
from clang.cindex import StorageClass
|
|
from clang.cindex import TypeKind
|
|
from clang.cindex import Config
|
|
|
|
|
|
# Clang cursor kind to ncc Defined cursor map
|
|
default_rules_db = {}
|
|
clang_to_user_map = {}
|
|
special_kind = {CursorKind.STRUCT_DECL: 1, CursorKind.CLASS_DECL: 1}
|
|
file_extensions = [".c", ".cpp", ".h", ".hpp"]
|
|
|
|
|
|
class Rule(object):
|
|
def __init__(self, name, clang_kind, parent_kind=None, pattern_str='^.*$'):
|
|
self.name = name
|
|
self.clang_kind = clang_kind
|
|
self.parent_kind = parent_kind
|
|
self.pattern_str = pattern_str
|
|
self.pattern = re.compile(pattern_str)
|
|
self.includes = []
|
|
self.excludes = []
|
|
|
|
def evaluate(self, node, scope=None):
|
|
if not self.pattern.match(node.spelling):
|
|
fmt = '{}:{}:{}: "{}" does not match "{}" associated with {}\n'
|
|
msg = fmt.format(node.location.file.name, node.location.line, node.location.column,
|
|
node.displayname, self.pattern_str, self.name)
|
|
sys.stderr.write(msg)
|
|
return False
|
|
return True
|
|
|
|
|
|
class ScopePrefixRule(object):
|
|
def __init__(self, pattern_obj):
|
|
self.name = "ScopePrefixRule"
|
|
self.rule_names = ["Global", "Static", "ClassMember", "StructMember"]
|
|
self.global_prefix = ""
|
|
self.static_prefix = ""
|
|
self.class_member_prefix = ""
|
|
self.struct_member_prefix = ""
|
|
|
|
try:
|
|
for key, value in pattern_obj.items():
|
|
if key == "Global":
|
|
self.global_prefix = value
|
|
elif key == "Static":
|
|
self.static_prefix = value
|
|
elif key == "ClassMember":
|
|
self.class_member_prefix = value
|
|
elif key == "StructMember":
|
|
self.struct_member_prefix = value
|
|
else:
|
|
raise ValueError(key)
|
|
except ValueError as e:
|
|
sys.stderr.write('{} is not a valid rule name\n'.format(e.message))
|
|
fixit = difflib.get_close_matches(e.message, self.rule_names, n=1, cutoff=0.8)
|
|
if fixit:
|
|
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
|
|
sys.exit(1)
|
|
|
|
|
|
class DataTypePrefixRule(object):
|
|
def __init__(self, pattern_obj):
|
|
self.name = "DataTypePrefix"
|
|
self.rule_names = ["String", "Integer", "Bool", "Pointer"]
|
|
self.string_prefix = ""
|
|
|
|
try:
|
|
for key, value in pattern_obj.items():
|
|
if key == "String":
|
|
self.string_prefix = value
|
|
elif key == "Integer":
|
|
self.integer_prefix = value
|
|
elif key == "Bool":
|
|
self.bool_prefix = value
|
|
elif key == "Pointer":
|
|
self.pointer_prefix = value
|
|
else:
|
|
raise ValueError(key)
|
|
except ValueError as e:
|
|
sys.stderr.write('{} is not a valid rule name\n'.format(e.message))
|
|
fixit = difflib.get_close_matches(e.message, self.rule_names, n=1, cutoff=0.8)
|
|
if fixit:
|
|
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
|
|
sys.exit(1)
|
|
|
|
|
|
class VariableNameRule(object):
|
|
def __init__(self, pattern_obj=None):
|
|
self.name = "VariableName"
|
|
self.pattern_str = "^.*$"
|
|
self.rule_names = ["ScopePrefix", "DataTypePrefix", "Pattern"]
|
|
self.scope_prefix_rule = None
|
|
self.datatype_prefix_rule = None
|
|
|
|
try:
|
|
for key, value in pattern_obj.items():
|
|
if key == "ScopePrefix":
|
|
self.scope_prefix_rule = ScopePrefixRule(value)
|
|
elif key == "DataTypePrefix":
|
|
self.datatype_prefix_rule = DataTypePrefixRule(value)
|
|
elif key == "Pattern":
|
|
self.pattern_str = value
|
|
else:
|
|
raise ValueError(key)
|
|
except ValueError as e:
|
|
sys.stderr.write('{} is not a valid rule name\n'.format(e.message))
|
|
fixit = difflib.get_close_matches(e.message, self.rule_names, n=1, cutoff=0.8)
|
|
if fixit:
|
|
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
|
|
sys.exit(1)
|
|
except re.error as e:
|
|
sys.stderr.write('{} is not a valid pattern \n'.format(e.message))
|
|
sys.exit(1)
|
|
|
|
def get_scope_prefix(self, node, scope=None):
|
|
if node.storage_class == StorageClass.STATIC:
|
|
return self.scope_prefix_rule.static_prefix
|
|
elif (scope is None) and (node.storage_class == StorageClass.EXTERN or
|
|
node.storage_class == StorageClass.NONE):
|
|
return self.scope_prefix_rule.global_prefix
|
|
elif (scope is CursorKind.CLASS_DECL) or (scope is CursorKind.CLASS_TEMPLATE):
|
|
return self.scope_prefix_rule.class_member_prefix
|
|
elif (scope is CursorKind.STRUCT_DECL):
|
|
return self.scope_prefix_rule.struct_member_prefix
|
|
return ""
|
|
|
|
def get_datatype_prefix(self, node):
|
|
if node.type.kind is TypeKind.ELABORATED:
|
|
if node.type.spelling.startswith('std::string'):
|
|
return self.datatype_prefix_rule.string_prefix
|
|
elif (node.type.spelling.startswith('std::unique_ptr') or
|
|
node.type.spelling.startswith("std::shared_ptr")):
|
|
return self.datatype_prefix_rule.pointer_prefix
|
|
elif node.type.kind is TypeKind.POINTER:
|
|
return self.datatype_prefix_rule.pointer_prefix
|
|
else:
|
|
if node.type.spelling == "int":
|
|
return self.datatype_prefix_rule.integer_prefix
|
|
elif node.type.spelling.startswith('bool'):
|
|
return self.datatype_prefix_rule.bool_prefix
|
|
return ""
|
|
|
|
def evaluate(self, node, scope=None):
|
|
pattern_str = self.pattern_str
|
|
scope_prefix = self.get_scope_prefix(node, scope)
|
|
datatype_prefix = self.get_datatype_prefix(node)
|
|
|
|
pattern_str = pattern_str[0] + scope_prefix + datatype_prefix + pattern_str[1:]
|
|
|
|
pattern = re.compile(pattern_str)
|
|
if not pattern.match(node.spelling):
|
|
fmt = '{}:{}:{}: "{}" does not have the pattern {} associated with Variable name\n'
|
|
msg = fmt.format(node.location.file.name, node.location.line, node.location.column,
|
|
node.displayname, pattern_str)
|
|
sys.stderr.write(msg)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
# All supported rules
|
|
default_rules_db["StructName"] = Rule("StructName", CursorKind.STRUCT_DECL)
|
|
default_rules_db["UnionName"] = Rule("UnionName", CursorKind.UNION_DECL)
|
|
default_rules_db["ClassName"] = Rule("ClassName", CursorKind.CLASS_DECL)
|
|
default_rules_db["EnumName"] = Rule("EnumName", CursorKind.ENUM_DECL)
|
|
default_rules_db["EnumConstantName"] = Rule("EnumConstantName", CursorKind.ENUM_CONSTANT_DECL)
|
|
default_rules_db["FunctionName"] = Rule("FunctionName", CursorKind.FUNCTION_DECL)
|
|
default_rules_db["ParameterName"] = Rule("ParameterName", CursorKind.PARM_DECL)
|
|
default_rules_db["TypedefName"] = Rule("TypedefName", CursorKind.TYPEDEF_DECL)
|
|
default_rules_db["CppMethod"] = Rule("CppMethod", CursorKind.CXX_METHOD)
|
|
default_rules_db["Namespace"] = Rule("Namespace", CursorKind.NAMESPACE)
|
|
default_rules_db["ConversionFunction"] = Rule("ConversionFunction", CursorKind.CONVERSION_FUNCTION)
|
|
default_rules_db["TemplateTypeParameter"] = Rule(
|
|
"TemplateTypeParameter", CursorKind.TEMPLATE_TYPE_PARAMETER)
|
|
default_rules_db["TemplateNonTypeParameter"] = Rule(
|
|
"TemplateNonTypeParameter", CursorKind.TEMPLATE_NON_TYPE_PARAMETER)
|
|
default_rules_db["TemplateTemplateParameter"] = Rule(
|
|
"TemplateTemplateParameter", CursorKind.TEMPLATE_TEMPLATE_PARAMETER)
|
|
default_rules_db["FunctionTemplate"] = Rule("FunctionTemplate", CursorKind.FUNCTION_TEMPLATE)
|
|
default_rules_db["ClassTemplate"] = Rule("ClassTemplate", CursorKind.CLASS_TEMPLATE)
|
|
default_rules_db["ClassTemplatePartialSpecialization"] = Rule(
|
|
"ClassTemplatePartialSpecialization", CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION)
|
|
default_rules_db["NamespaceAlias"] = Rule("NamespaceAlias", CursorKind.NAMESPACE_ALIAS)
|
|
default_rules_db["UsingDirective"] = Rule("UsingDirective", CursorKind.USING_DIRECTIVE)
|
|
default_rules_db["UsingDeclaration"] = Rule("UsingDeclaration", CursorKind.USING_DECLARATION)
|
|
default_rules_db["TypeAliasName"] = Rule("TypeAliasName", CursorKind.TYPE_ALIAS_DECL)
|
|
default_rules_db["ClassAccessSpecifier"] = Rule(
|
|
"ClassAccessSpecifier", CursorKind.CXX_ACCESS_SPEC_DECL)
|
|
default_rules_db["TypeReference"] = Rule("TypeReference", CursorKind.TYPE_REF)
|
|
default_rules_db["CxxBaseSpecifier"] = Rule("CxxBaseSpecifier", CursorKind.CXX_BASE_SPECIFIER)
|
|
default_rules_db["TemplateReference"] = Rule("TemplateReference", CursorKind.TEMPLATE_REF)
|
|
default_rules_db["NamespaceReference"] = Rule("NamespaceReference", CursorKind.NAMESPACE_REF)
|
|
default_rules_db["MemberReference"] = Rule("MemberReference", CursorKind.MEMBER_REF)
|
|
default_rules_db["LabelReference"] = Rule("LabelReference", CursorKind.LABEL_REF)
|
|
default_rules_db["OverloadedDeclarationReference"] = Rule(
|
|
"OverloadedDeclarationReference", CursorKind.OVERLOADED_DECL_REF)
|
|
default_rules_db["VariableReference"] = Rule("VariableReference", CursorKind.VARIABLE_REF)
|
|
default_rules_db["InvalidFile"] = Rule("InvalidFile", CursorKind.INVALID_FILE)
|
|
default_rules_db["NoDeclarationFound"] = Rule("NoDeclarationFound", CursorKind.NO_DECL_FOUND)
|
|
default_rules_db["NotImplemented"] = Rule("NotImplemented", CursorKind.NOT_IMPLEMENTED)
|
|
default_rules_db["InvalidCode"] = Rule("InvalidCode", CursorKind.INVALID_CODE)
|
|
default_rules_db["UnexposedExpression"] = Rule("UnexposedExpression", CursorKind.UNEXPOSED_EXPR)
|
|
default_rules_db["DeclarationReferenceExpression"] = Rule(
|
|
"DeclarationReferenceExpression", CursorKind.DECL_REF_EXPR)
|
|
default_rules_db["MemberReferenceExpression"] = Rule(
|
|
"MemberReferenceExpression", CursorKind.MEMBER_REF_EXPR)
|
|
default_rules_db["CallExpression"] = Rule("CallExpression", CursorKind.CALL_EXPR)
|
|
default_rules_db["BlockExpression"] = Rule("BlockExpression", CursorKind.BLOCK_EXPR)
|
|
default_rules_db["IntegerLiteral"] = Rule("IntegerLiteral", CursorKind.INTEGER_LITERAL)
|
|
default_rules_db["FloatingLiteral"] = Rule("FloatingLiteral", CursorKind.FLOATING_LITERAL)
|
|
default_rules_db["ImaginaryLiteral"] = Rule("ImaginaryLiteral", CursorKind.IMAGINARY_LITERAL)
|
|
default_rules_db["StringLiteral"] = Rule("StringLiteral", CursorKind.STRING_LITERAL)
|
|
default_rules_db["CharacterLiteral"] = Rule("CharacterLiteral", CursorKind.CHARACTER_LITERAL)
|
|
default_rules_db["ParenExpression"] = Rule("ParenExpression", CursorKind.PAREN_EXPR)
|
|
default_rules_db["UnaryOperator"] = Rule("UnaryOperator", CursorKind.UNARY_OPERATOR)
|
|
default_rules_db["ArraySubscriptExpression"] = Rule(
|
|
"ArraySubscriptExpression", CursorKind.ARRAY_SUBSCRIPT_EXPR)
|
|
default_rules_db["BinaryOperator"] = Rule("BinaryOperator", CursorKind.BINARY_OPERATOR)
|
|
default_rules_db["CompoundAssignmentOperator"] = Rule(
|
|
"CompoundAssignmentOperator", CursorKind.COMPOUND_ASSIGNMENT_OPERATOR)
|
|
default_rules_db["ConditionalOperator"] = Rule(
|
|
"ConditionalOperator", CursorKind.CONDITIONAL_OPERATOR)
|
|
default_rules_db["CstyleCastExpression"] = Rule(
|
|
"CstyleCastExpression", CursorKind.CSTYLE_CAST_EXPR)
|
|
default_rules_db["CompoundLiteralExpression"] = Rule(
|
|
"CompoundLiteralExpression", CursorKind.COMPOUND_LITERAL_EXPR)
|
|
default_rules_db["InitListExpression"] = Rule("InitListExpression", CursorKind.INIT_LIST_EXPR)
|
|
default_rules_db["AddrLabelExpression"] = Rule("AddrLabelExpression", CursorKind.ADDR_LABEL_EXPR)
|
|
default_rules_db["StatementExpression"] = Rule("StatementExpression", CursorKind.StmtExpr)
|
|
default_rules_db["GenericSelectionExpression"] = Rule(
|
|
"GenericSelectionExpression", CursorKind.GENERIC_SELECTION_EXPR)
|
|
default_rules_db["GnuNullExpression"] = Rule("GnuNullExpression", CursorKind.GNU_NULL_EXPR)
|
|
default_rules_db["CxxStaticCastExpression"] = Rule(
|
|
"CxxStaticCastExpression", CursorKind.CXX_STATIC_CAST_EXPR)
|
|
default_rules_db["CxxDynamicCastExpression"] = Rule(
|
|
"CxxDynamicCastExpression", CursorKind.CXX_DYNAMIC_CAST_EXPR)
|
|
default_rules_db["CxxReinterpretCastExpression"] = Rule(
|
|
"CxxReinterpretCastExpression", CursorKind.CXX_REINTERPRET_CAST_EXPR)
|
|
default_rules_db["CxxConstCastExpression"] = Rule(
|
|
"CxxConstCastExpression", CursorKind.CXX_CONST_CAST_EXPR)
|
|
default_rules_db["CxxFunctionalCastExpression"] = Rule(
|
|
"CxxFunctionalCastExpression", CursorKind.CXX_FUNCTIONAL_CAST_EXPR)
|
|
default_rules_db["CxxTypeidExpression"] = Rule("CxxTypeidExpression", CursorKind.CXX_TYPEID_EXPR)
|
|
default_rules_db["CxxBoolLiteralExpression"] = Rule(
|
|
"CxxBoolLiteralExpression", CursorKind.CXX_BOOL_LITERAL_EXPR)
|
|
default_rules_db["CxxNullPointerLiteralExpression"] = Rule(
|
|
"CxxNullPointerLiteralExpression", CursorKind.CXX_NULL_PTR_LITERAL_EXPR)
|
|
default_rules_db["CxxThisExpression"] = Rule("CxxThisExpression", CursorKind.CXX_THIS_EXPR)
|
|
default_rules_db["CxxThrowExpression"] = Rule("CxxThrowExpression", CursorKind.CXX_THROW_EXPR)
|
|
default_rules_db["CxxNewExpression"] = Rule("CxxNewExpression", CursorKind.CXX_NEW_EXPR)
|
|
default_rules_db["CxxDeleteExpression"] = Rule("CxxDeleteExpression", CursorKind.CXX_DELETE_EXPR)
|
|
default_rules_db["CxxUnaryExpression"] = Rule("CxxUnaryExpression", CursorKind.CXX_UNARY_EXPR)
|
|
default_rules_db["PackExpansionExpression"] = Rule(
|
|
"PackExpansionExpression", CursorKind.PACK_EXPANSION_EXPR)
|
|
default_rules_db["SizeOfPackExpression"] = Rule(
|
|
"SizeOfPackExpression", CursorKind.SIZE_OF_PACK_EXPR)
|
|
default_rules_db["LambdaExpression"] = Rule("LambdaExpression", CursorKind.LAMBDA_EXPR)
|
|
default_rules_db["ObjectBoolLiteralExpression"] = Rule(
|
|
"ObjectBoolLiteralExpression", CursorKind.OBJ_BOOL_LITERAL_EXPR)
|
|
default_rules_db["ObjectSelfExpression"] = Rule("ObjectSelfExpression", CursorKind.OBJ_SELF_EXPR)
|
|
default_rules_db["UnexposedStatement"] = Rule("UnexposedStatement", CursorKind.UNEXPOSED_STMT)
|
|
default_rules_db["LabelStatement"] = Rule("LabelStatement", CursorKind.LABEL_STMT)
|
|
default_rules_db["CompoundStatement"] = Rule("CompoundStatement", CursorKind.COMPOUND_STMT)
|
|
default_rules_db["CaseStatement"] = Rule("CaseStatement", CursorKind.CASE_STMT)
|
|
default_rules_db["DefaultStatement"] = Rule("DefaultStatement", CursorKind.DEFAULT_STMT)
|
|
default_rules_db["IfStatement"] = Rule("IfStatement", CursorKind.IF_STMT)
|
|
default_rules_db["SwitchStatement"] = Rule("SwitchStatement", CursorKind.SWITCH_STMT)
|
|
default_rules_db["WhileStatement"] = Rule("WhileStatement", CursorKind.WHILE_STMT)
|
|
default_rules_db["DoStatement"] = Rule("DoStatement", CursorKind.DO_STMT)
|
|
default_rules_db["ForStatement"] = Rule("ForStatement", CursorKind.FOR_STMT)
|
|
default_rules_db["GotoStatement"] = Rule("GotoStatement", CursorKind.GOTO_STMT)
|
|
default_rules_db["IndirectGotoStatement"] = Rule(
|
|
"IndirectGotoStatement", CursorKind.INDIRECT_GOTO_STMT)
|
|
default_rules_db["ContinueStatement"] = Rule("ContinueStatement", CursorKind.CONTINUE_STMT)
|
|
default_rules_db["BreakStatement"] = Rule("BreakStatement", CursorKind.BREAK_STMT)
|
|
default_rules_db["ReturnStatement"] = Rule("ReturnStatement", CursorKind.RETURN_STMT)
|
|
default_rules_db["AsmStatement"] = Rule("AsmStatement", CursorKind.ASM_STMT)
|
|
default_rules_db["CxxCatchStatement"] = Rule("CxxCatchStatement", CursorKind.CXX_CATCH_STMT)
|
|
default_rules_db["CxxTryStatement"] = Rule("CxxTryStatement", CursorKind.CXX_TRY_STMT)
|
|
default_rules_db["CxxForRangeStatement"] = Rule(
|
|
"CxxForRangeStatement", CursorKind.CXX_FOR_RANGE_STMT)
|
|
default_rules_db["MsAsmStatement"] = Rule("MsAsmStatement", CursorKind.MS_ASM_STMT)
|
|
default_rules_db["NullStatement"] = Rule("NullStatement", CursorKind.NULL_STMT)
|
|
default_rules_db["DeclarationStatement"] = Rule("DeclarationStatement", CursorKind.DECL_STMT)
|
|
default_rules_db["TranslationUnit"] = Rule("TranslationUnit", CursorKind.TRANSLATION_UNIT)
|
|
default_rules_db["UnexposedAttribute"] = Rule("UnexposedAttribute", CursorKind.UNEXPOSED_ATTR)
|
|
default_rules_db["CxxFinalAttribute"] = Rule("CxxFinalAttribute", CursorKind.CXX_FINAL_ATTR)
|
|
default_rules_db["CxxOverrideAttribute"] = Rule(
|
|
"CxxOverrideAttribute", CursorKind.CXX_OVERRIDE_ATTR)
|
|
default_rules_db["AnnotateAttribute"] = Rule("AnnotateAttribute", CursorKind.ANNOTATE_ATTR)
|
|
default_rules_db["AsmLabelAttribute"] = Rule("AsmLabelAttribute", CursorKind.ASM_LABEL_ATTR)
|
|
default_rules_db["PackedAttribute"] = Rule("PackedAttribute", CursorKind.PACKED_ATTR)
|
|
default_rules_db["PureAttribute"] = Rule("PureAttribute", CursorKind.PURE_ATTR)
|
|
default_rules_db["ConstAttribute"] = Rule("ConstAttribute", CursorKind.CONST_ATTR)
|
|
default_rules_db["NoduplicateAttribute"] = Rule(
|
|
"NoduplicateAttribute", CursorKind.NODUPLICATE_ATTR)
|
|
default_rules_db["PreprocessingDirective"] = Rule(
|
|
"PreprocessingDirective", CursorKind.PREPROCESSING_DIRECTIVE)
|
|
default_rules_db["MacroDefinition"] = Rule("MacroDefinition", CursorKind.MACRO_DEFINITION)
|
|
default_rules_db["MacroInstantiation"] = Rule("MacroInstantiation", CursorKind.MACRO_INSTANTIATION)
|
|
default_rules_db["InclusionDirective"] = Rule("InclusionDirective", CursorKind.INCLUSION_DIRECTIVE)
|
|
default_rules_db["TypeAliasTeplateDeclaration"] = Rule(
|
|
"TypeAliasTeplateDeclaration", CursorKind.TYPE_ALIAS_TEMPLATE_DECL)
|
|
|
|
# Reverse lookup map. The parse identifies Clang cursor kinds, which must be mapped
|
|
# to user defined types
|
|
for key, value in default_rules_db.items():
|
|
clang_to_user_map[value.clang_kind] = key
|
|
default_rules_db["VariableName"] = Rule("VariableName", CursorKind.VAR_DECL)
|
|
clang_to_user_map[CursorKind.FIELD_DECL] = "VariableName"
|
|
clang_to_user_map[CursorKind.VAR_DECL] = "VariableName"
|
|
|
|
|
|
class AstNodeStack(object):
|
|
def __init__(self):
|
|
self.stack = []
|
|
|
|
def pop(self):
|
|
return self.stack.pop()
|
|
|
|
def push(self, kind):
|
|
self.stack.append(kind)
|
|
|
|
def peek(self):
|
|
if len(self.stack) > 0:
|
|
return self.stack[-1]
|
|
return None
|
|
|
|
|
|
class Options:
|
|
def __init__(self):
|
|
self.args = None
|
|
self._style_file = None
|
|
self.file_exclusions = None
|
|
self._skip_file = None
|
|
|
|
self.parser = argparse.ArgumentParser(
|
|
prog="ncc.py",
|
|
description="ncc is a development tool to help programmers "
|
|
"write C/C++ code that adheres to adhere some naming conventions. It automates the "
|
|
"process of checking C code to spare humans of this boring "
|
|
"(but important) task. This makes it ideal for projects that want "
|
|
"to enforce a coding standard.")
|
|
|
|
self.parser.add_argument('--recurse', action='store_true', dest="recurse",
|
|
help="Read all files under each directory, recursively")
|
|
|
|
self.parser.add_argument('--style', dest="style_file",
|
|
help="Read rules from the specified file. If the user does not"
|
|
"provide a style file ncc will use all style rules. To print"
|
|
"all style rules use --dump option")
|
|
|
|
self.parser.add_argument('--include', dest='include', nargs="+", help="User defined "
|
|
"header file path, this is same as -I argument to the compiler")
|
|
|
|
self.parser.add_argument('--definition', dest='definition', nargs="+", help="User specified "
|
|
"definitions, this is same as -D argument to the compiler")
|
|
|
|
self.parser.add_argument('--dump', dest='dump', action='store_true',
|
|
help="Dump all available options")
|
|
|
|
self.parser.add_argument('--output', dest='output', help="output file name where"
|
|
"naming convenction vialoations will be stored")
|
|
|
|
self.parser.add_argument('--filetype', dest='filetype', help="File extentions type"
|
|
"that are applicable for naming convection validation")
|
|
|
|
self.parser.add_argument('--clang-lib', dest='clang_lib',
|
|
help="Custom location of clang library")
|
|
|
|
self.parser.add_argument('--exclude', dest='exclude', nargs="+", help="Skip files "
|
|
"matching the pattern specified from recursive searches. It "
|
|
"matches a specified pattern according to the rules used by "
|
|
"the Unix shell")
|
|
|
|
self.parser.add_argument('--skip', '-s', dest="skip_file",
|
|
help="Read list of items to ignore during the check. "
|
|
"User can use the skip file to specify character sequences that should "
|
|
"be ignored by ncc")
|
|
|
|
# self.parser.add_argument('--exclude-dir', dest='exclude_dir', help="Skip the directories"
|
|
# "matching the pattern specified")
|
|
|
|
self.parser.add_argument('--path', dest='path', nargs="+",
|
|
help="Path of file or directory")
|
|
|
|
def parse_cmd_line(self):
|
|
self.args = self.parser.parse_args()
|
|
|
|
if self.args.dump:
|
|
self.dump_all_rules()
|
|
|
|
if self.args.style_file:
|
|
self._style_file = self.args.style_file
|
|
if not os.path.exists(self._style_file):
|
|
sys.stderr.write("Style file '{}' not found!\n".format(self._style_file))
|
|
sys.exit(1)
|
|
|
|
if self.args.skip_file:
|
|
self._skip_file = self.args.skip_file
|
|
if not os.path.exists(self._skip_file):
|
|
sys.stderr.write("Skip file '{}' not found!\n".format(self._skip_file))
|
|
|
|
def dump_all_rules(self):
|
|
print("----------------------------------------------------------")
|
|
print("{:<35} | {}".format("Rule Name", "Pattern"))
|
|
print("----------------------------------------------------------")
|
|
for (key, value) in default_rules_db.items():
|
|
print("{:<35} : {}".format(key, value.pattern_str))
|
|
|
|
class SkipDb(object):
|
|
def __init__(self, skip_file=None):
|
|
self.__skip_db = {}
|
|
|
|
if skip_file:
|
|
self.build_skip_db(skip_file)
|
|
|
|
def build_skip_db(self, skip_file):
|
|
with open(skip_file) as stylefile:
|
|
style_rules = yaml.safe_load(stylefile)
|
|
for (skip_string, skip_comment) in style_rules.items():
|
|
self.__skip_db[skip_string] = skip_comment
|
|
|
|
def check_skip_db(self, input_query):
|
|
if input_query in self.__skip_db.keys():
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
class RulesDb(object):
|
|
def __init__(self, style_file=None):
|
|
self.__rule_db = {}
|
|
self.__clang_db = {}
|
|
|
|
if style_file:
|
|
self.build_rules_db(style_file)
|
|
else:
|
|
self.__rule_db = default_rules_db
|
|
self.__clang_db = clang_to_user_map
|
|
|
|
def build_rules_db(self, style_file):
|
|
with open(style_file) as stylefile:
|
|
style_rules = yaml.safe_load(stylefile)
|
|
|
|
for (rule_name, pattern_str) in style_rules.items():
|
|
try:
|
|
clang_kind = default_rules_db[rule_name].clang_kind
|
|
if clang_kind:
|
|
if rule_name == "VariableName":
|
|
self.__rule_db[rule_name] = VariableNameRule(pattern_str)
|
|
self.__clang_db[CursorKind.FIELD_DECL] = rule_name
|
|
self.__clang_db[CursorKind.VAR_DECL] = rule_name
|
|
else:
|
|
self.__rule_db[rule_name] = default_rules_db[rule_name]
|
|
self.__rule_db[rule_name].pattern_str = pattern_str
|
|
self.__rule_db[rule_name].pattern = re.compile(pattern_str)
|
|
self.__clang_db[clang_kind] = rule_name
|
|
|
|
except KeyError as e:
|
|
sys.stderr.write('{} is not a valid C/C++ construct name\n'.format(e.message))
|
|
fixit = difflib.get_close_matches(e.message, default_rules_db.keys(),
|
|
n=1, cutoff=0.8)
|
|
if fixit:
|
|
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
|
|
sys.exit(1)
|
|
except re.error as e:
|
|
sys.stderr.write('"{}" pattern {} has {} \n'.
|
|
format(rule_name, pattern_str, e.message))
|
|
sys.exit(1)
|
|
|
|
def is_rule_enabled(self, kind):
|
|
if self.__clang_db.get(kind):
|
|
return True
|
|
return False
|
|
|
|
def get_rule_names(self, kind):
|
|
"""
|
|
Multiple user defined rules can be configured against one type of ClangKind
|
|
For e.g. ClassMemberVariable, StructMemberVariable are types of FIELD_DECL
|
|
"""
|
|
return self.__clang_db.get(kind)
|
|
|
|
def get_rule(self, rule_name):
|
|
return self.__rule_db.get(rule_name)
|
|
|
|
|
|
class Validator(object):
|
|
def __init__(self, rule_db, filename, options, skip_db=None):
|
|
self.filename = filename
|
|
self.rule_db = rule_db
|
|
self.skip_db = skip_db
|
|
self.options = options
|
|
self.node_stack = AstNodeStack()
|
|
|
|
index = Index.create()
|
|
args = []
|
|
args.append('-x')
|
|
args.append('c++')
|
|
args.append('-D_GLIBCXX_USE_CXX11_ABI=0')
|
|
if self.options.args.definition:
|
|
for item in self.options.args.definition:
|
|
defintion = r'-D' + item
|
|
args.append(defintion)
|
|
if self.options.args.include:
|
|
for item in self.options.args.include:
|
|
inc = r'-I' + item
|
|
args.append(inc)
|
|
self.cursor = index.parse(filename, args).cursor
|
|
|
|
def validate(self):
|
|
return self.check(self.cursor)
|
|
|
|
def check(self, node):
|
|
"""
|
|
Recursively visit all nodes of the AST and match against the patter provided by
|
|
the user. Return the total number of errors caught in the file
|
|
"""
|
|
errors = 0
|
|
for child in node.get_children():
|
|
if self.is_local(child, self.filename):
|
|
|
|
# This is the case when typedef of struct is causing double reporting of error
|
|
# TODO: Find a better way to handle it
|
|
parent = self.node_stack.peek()
|
|
if (parent and parent == CursorKind.TYPEDEF_DECL and
|
|
child.kind == CursorKind.STRUCT_DECL):
|
|
return 0
|
|
|
|
errors += self.evaluate(child)
|
|
|
|
# Members struct, class, and unions must be treated differently.
|
|
# So parent ast node information is pushed in to the stack.
|
|
# Once all its children are validated pop it out of the stack
|
|
self.node_stack.push(child.kind)
|
|
errors += self.check(child)
|
|
self.node_stack.pop()
|
|
|
|
return errors
|
|
|
|
def evaluate(self, node):
|
|
"""
|
|
get the node's rule and match the pattern. Report and error if pattern
|
|
matching fails
|
|
"""
|
|
if not self.rule_db.is_rule_enabled(node.kind):
|
|
return 0
|
|
|
|
# If the pattern is in the skip list, ignore it
|
|
if self.skip_db.check_skip_db(node.displayname):
|
|
return 0
|
|
|
|
rule_name = self.rule_db.get_rule_names(node.kind)
|
|
rule = self.rule_db.get_rule(rule_name)
|
|
if rule.evaluate(node, self.node_stack.peek()) is False:
|
|
return 1
|
|
return 0
|
|
|
|
def is_local(self, node, filename):
|
|
""" Returns True is node belongs to the file being validated and not an include file """
|
|
if node.location.file and node.location.file.name in filename:
|
|
return True
|
|
return False
|
|
|
|
|
|
def do_validate(options, filename):
|
|
"""
|
|
Returns true if the file should be validated
|
|
- Check if its a c/c++ file
|
|
- Check if the file is not excluded
|
|
"""
|
|
path, extension = os.path.splitext(filename)
|
|
if extension not in file_extensions:
|
|
return False
|
|
|
|
if options.args.exclude:
|
|
for item in options.args.exclude:
|
|
if fnmatch.fnmatch(filename, item):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s',
|
|
filename='log.txt', filemode='w')
|
|
|
|
""" Parse all command line arguments and validate """
|
|
op = Options()
|
|
op.parse_cmd_line()
|
|
|
|
if op.args.path is None:
|
|
sys.exit(1)
|
|
|
|
if op.args.clang_lib:
|
|
Config.set_library_file(op.args.clang_lib)
|
|
|
|
""" Creating the rules database """
|
|
rules_db = RulesDb(op._style_file)
|
|
|
|
""" Creating the skip database """
|
|
skip_db = SkipDb(op._skip_file)
|
|
|
|
""" Check the source code against the configured rules """
|
|
errors = 0
|
|
for path in op.args.path:
|
|
if os.path.isfile(path):
|
|
if do_validate(op, path):
|
|
v = Validator(rules_db, path, op, skip_db)
|
|
errors += v.validate()
|
|
elif os.path.isdir(path):
|
|
for (root, subdirs, files) in os.walk(path):
|
|
for filename in files:
|
|
path = root + '/' + filename
|
|
if do_validate(op, path):
|
|
v = Validator(rules_db, path, op, skip_db)
|
|
errors += v.validate()
|
|
|
|
if not op.args.recurse:
|
|
break
|
|
else:
|
|
sys.stderr.write("File '{}' not found!\n".format(path))
|
|
sys.exit(1)
|
|
|
|
if errors:
|
|
print("Total number of errors = {}".format(errors))
|
|
sys.exit(1)
|