diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0fbeb422..160497d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,9 +13,23 @@ jobs: fail-fast: false matrix: toolchain: - - { name: 'MSVC', shell: 'sh', setup-cmake: true, setup-ninja: true, setup-msvc: true } + - { name: 'MSVC', shell: 'sh', setup-cmake: true, setup-ninja: true, setup-msvc: true } + # TODO: Add back eventually, but exclude 3rdparty code from warnings as errors + # - { name: 'msys2 mingw32', shell: 'msys2 {0}', msystem: mingw32, msys-env: mingw-w64-i686, clang-tidy: true, werror: true } + # - { name: 'msys2 clang32', shell: 'msys2 {0}', msystem: clang32, msys-env: mingw-w64-clang-i686, clang-tidy: true, werror: true, no-dx5-libs: true } steps: + - name: Set up MSYS2 + if: ${{ !!matrix.toolchain.msystem }} + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.toolchain.msystem }} + install: >- + ${{ matrix.toolchain.msys-env }}-cc + ${{ matrix.toolchain.msys-env }}-cmake + ${{ matrix.toolchain.msys-env }}-ninja + ${{ matrix.toolchain.msys-env }}-clang-tools-extra + - name: Setup cmake if: matrix.toolchain.setup-cmake uses: jwlawson/actions-setup-cmake@v1.13 diff --git a/.github/workflows/naming.yml b/.github/workflows/naming.yml new file mode 100644 index 00000000..3afed885 --- /dev/null +++ b/.github/workflows/naming.yml @@ -0,0 +1,43 @@ +name: Naming + +on: [push, pull_request] + +jobs: + ncc: + name: 'C++' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install LLVM and Clang + uses: KyleMayes/install-llvm-action@v1 + with: + version: "16" + + - name: Install python libraries + run: | + pip install -r tools/requirements.txt + + - name: Run ncc + run: | + action_headers=$(find LEGO1/lego/legoomni/include/actions \ + -name '*.h' -print0 | xargs -0 echo) + + python3 tools/ncc/ncc.py \ + --clang-lib ${{ env.LLVM_PATH }}/lib/libclang.so \ + --recurse \ + --style tools/ncc/ncc.style \ + --skip tools/ncc/skip.yml \ + --definition WINAPI FAR BOOL CALLBACK HWND__=HWND \ + --include \ + util \ + LEGO1 \ + LEGO1/omni/include \ + LEGO1/lego/legoomni/include \ + LEGO1/lego/sources \ + --exclude \ + LEGO1/omni/include/flic.h \ + LEGO1/omni/src/video/flic.cpp \ + $action_headers \ + --path LEGO1/omni LEGO1/lego/legoomni diff --git a/README.md b/README.md index d01cd48d..e23e2796 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ To achieve our goal of platform independence, we need to replace any Windows-onl | [Smacker](https://github.com/isledecomp/isle/tree/master/3rdparty/smacker) | [libsmacker](https://github.com/foxtacles/libsmacker) | ✅ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable%20%22%2F%2F%20%5Blibrary%3Alibsmacker%5D%22&type=code) | | Filesystem | C standard library | ❌ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3Afilesystem%5D%22&type=code) | | Threads, Mutexes (Synchronization) | [SDL3](https://www.libsdl.org/) | ❌ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3Asynchronization%5D%22&type=code) | -| Keyboard, Mouse, Joystick (Input) | [SDL3](https://www.libsdl.org/) | ❌ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3Ainput%5D%22&type=code) | +| Keyboard, Mouse, Joystick, DirectInput (Input) | [SDL3](https://www.libsdl.org/) | ❌ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3Ainput%5D%22&type=code) | | WinMM, DirectSound (Audio) | [SDL3](https://www.libsdl.org/) | ❌ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3Aaudio%5D%22&type=code) | | DirectDraw (2D video) | [SDL3](https://www.libsdl.org/) | ❌ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3A2d%5D%22&type=code) | | Direct3D (3D video) | [SDL3](https://www.libsdl.org/), OpenGL ES (**TBD**) | ❌ | [Open issues](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3A3d%5D%22&type=code) | diff --git a/tools/ncc/ncc.py b/tools/ncc/ncc.py new file mode 100644 index 00000000..89ad5dbc --- /dev/null +++ b/tools/ncc/ncc.py @@ -0,0 +1,661 @@ +#!/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) diff --git a/tools/ncc/ncc.style b/tools/ncc/ncc.style new file mode 100644 index 00000000..4ddb4f57 --- /dev/null +++ b/tools/ncc/ncc.style @@ -0,0 +1,21 @@ +ClassName: '^[A-Z][a-zA-Z0-9]+$' +CppMethod: '^operator|^FUN_[a-f0-9]{8}$|^VTable0x[a-f0-9]{1,8}$|^(?!VTable)[A-Z][a-zA-Z0-9]+$' +EnumName: '^\(unnamed|^[A-Z][a-zA-Z0-9]+$' +EnumConstantName: '^(c_|e_)[a-z][a-zA-Z0-9]*$' +FunctionName: '^operator|^FUN_[a-f0-9]{8}$|^VTable0x[a-f0-9]{1,8}$|^(?!VTable)[A-Z][a-zA-Z0-9]+$' +ParameterName: '^p_(unk0x[a-f0-9]{1,8}$|(?!unk)[a-z][a-zA-Z0-9]*)$|^$' +StructName: '^\(anon|^\(unnamed|^[A-Z][a-zA-Z0-9]+$' +TypedefName: '^[A-Z][a-zA-Z0-9]+$' +UnionName: '^\(anon|^[A-Z][a-zA-Z0-9]+$' +VariableName: + ScopePrefix: + Global: 'g_' + Static: 'g_' + ClassMember: 'm_' + StructMember: 'm_' + DataTypePrefix: + String: '' + Integer: '' + Bool: '' + Pointer: '' + Pattern: '^(unk0x[a-f0-9]{1,8}$|(?!unk)[a-z][a-zA-Z0-9]*|str[a-zA-Z0-9_]*)$' diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml new file mode 100644 index 00000000..46229e07 --- /dev/null +++ b/tools/ncc/skip.yml @@ -0,0 +1,32 @@ +configureLegoAnimationManager(MxS32): 'DLL exported function' +configureLegoBuildingManager(MxS32): 'DLL exported function' +configureLegoModelPresenter(MxS32): 'DLL exported function' +configureLegoPartPresenter(MxS32, MxS32): 'DLL exported function' +configureLegoROI(int): 'DLL exported function' +configureLegoWorldPresenter(MxS32): 'DLL exported function' +GetNoCD_SourceName(): 'DLL exported function' +m_3dView: 'Allow this variable name' +m_3dManager: 'Allow this variable name' +m_16bitPal: 'Allow this variable name' +m_HWDesc: 'Allow this variable name' +m_HELDesc: 'Allow this variable name' +p_HWDesc: 'Allow this variable name' +p_HELDesc: 'Allow this variable name' +e_RAMStream: 'Allow this enum constant' +p_milliseconds: 'Probably a bug with function call' +m_increaseAmount: "Can't currently detect member in union" +m_increaseFactor: "Can't currently detect member in union" +delta_rad: "Allow original naming from 1996" +delta_pos: "Allow original naming from 1996" +rot_mat: "Allow original naming from 1996" +new_pos: "Allow original naming from 1996" +new_dir: "Allow original naming from 1996" +p_AnimTreePtr: "Allow original naming from beta" +m_AnimTreePtr: "Allow original naming from beta" +m_BADuration: "Allow original naming from beta" +m_assAnimP: "Allow original naming from beta" +m_disAnimP: "Allow original naming from beta" +i_activity: "Allow original naming from beta" +i_actor: "Allow original naming from beta" +score: "Allow original naming from beta" +c_LOCATIONS_NUM: "Allow original naming from beta" diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 00000000..a54b9174 --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1,2 @@ +clang==16.* +pyyaml