From e8814543c1ba2414c00eb052d3589dd4a493e0a2 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Thu, 29 Aug 2024 17:46:12 +0200 Subject: [PATCH 01/13] Init: setup unity and code confg file for builtin library - add unity setup in CAN lib, include makefiles take unity from local install path - code check files such like cpp checheck, clang-tidy and so on - format CAN lib with clang-format --- .gitignore | 1 - config/arduinoLibraryTemplate/LICENSE.md | 0 config/arduinoLibraryTemplate/README.md | 0 config/arduinoLibraryTemplate/keywords.txt | 0 config/arduinoLibraryTemplate/library.json | 0 .../arduinoLibraryTemplate/library.properties | 10 + config/arduinoLibraryTemplate/src/dummy.txt | 0 config/clang-format/.clang-format | 26 + config/clang-tidy/.clang-tidy | 54 + config/cppcheck/misra.txt | 647 +++ config/cppcheck/misra_local.py | 4973 +++++++++++++++++ libraries/CAN/Makefile | 81 +- libraries/CAN/Makefile.codechecks.mk | 63 + libraries/CAN/Makefile.test | 4 +- libraries/CAN/src/CANController.cpp | 160 +- libraries/CAN/src/CANXMC.cpp | 504 +- .../CAN/test/unit/src/Test_common_includes.h | 21 + .../CAN/test/unit/src/features/Test_CAN.hpp | 73 + .../unit/src/features/Test_CAN_connected.hpp | 78 + .../unit/src/framework/arduino/Utilities.cpp | 24 + .../unit/src/framework/arduino/Utilities.hpp | 15 + .../framework/arduino/corelibs/Test_CAN.cpp | 27 + .../arduino/corelibs/Test_CAN_connected.cpp | 27 + .../arduino/corelibs/Test_includes.hpp | 18 + .../framework/arduino/corelibs/Test_main.ino | 50 + .../unit/src/framework/arduino/unity_ifx.cpp | 22 + libraries/CAN/test/unit/unity_config.h | 250 + libraries/CAN/test/unit/unity_ifx.h | 27 + 28 files changed, 6790 insertions(+), 365 deletions(-) create mode 100644 config/arduinoLibraryTemplate/LICENSE.md create mode 100644 config/arduinoLibraryTemplate/README.md create mode 100644 config/arduinoLibraryTemplate/keywords.txt create mode 100644 config/arduinoLibraryTemplate/library.json create mode 100644 config/arduinoLibraryTemplate/library.properties create mode 100644 config/arduinoLibraryTemplate/src/dummy.txt create mode 100644 config/clang-format/.clang-format create mode 100644 config/clang-tidy/.clang-tidy create mode 100644 config/cppcheck/misra.txt create mode 100644 config/cppcheck/misra_local.py create mode 100644 libraries/CAN/Makefile.codechecks.mk create mode 100644 libraries/CAN/test/unit/src/Test_common_includes.h create mode 100644 libraries/CAN/test/unit/src/features/Test_CAN.hpp create mode 100644 libraries/CAN/test/unit/src/features/Test_CAN_connected.hpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/Utilities.cpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/Utilities.hpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN.cpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected.cpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_includes.hpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino create mode 100644 libraries/CAN/test/unit/src/framework/arduino/unity_ifx.cpp create mode 100644 libraries/CAN/test/unit/unity_config.h create mode 100644 libraries/CAN/test/unit/unity_ifx.h diff --git a/.gitignore b/.gitignore index 2fbce262..e02e001a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ .vscode pkg_build build -.clang-format **/__pycache__/ \ No newline at end of file diff --git a/config/arduinoLibraryTemplate/LICENSE.md b/config/arduinoLibraryTemplate/LICENSE.md new file mode 100644 index 00000000..e69de29b diff --git a/config/arduinoLibraryTemplate/README.md b/config/arduinoLibraryTemplate/README.md new file mode 100644 index 00000000..e69de29b diff --git a/config/arduinoLibraryTemplate/keywords.txt b/config/arduinoLibraryTemplate/keywords.txt new file mode 100644 index 00000000..e69de29b diff --git a/config/arduinoLibraryTemplate/library.json b/config/arduinoLibraryTemplate/library.json new file mode 100644 index 00000000..e69de29b diff --git a/config/arduinoLibraryTemplate/library.properties b/config/arduinoLibraryTemplate/library.properties new file mode 100644 index 00000000..7ed21a58 --- /dev/null +++ b/config/arduinoLibraryTemplate/library.properties @@ -0,0 +1,10 @@ +name=CAN +version=1.0.0 +author=Infineon Technologies +maintainer=Infineon Technologies +sentence=C++ CAN library +paragraph=C++ CAN library +category=Library +url=https://github.com/Infineon/ +architectures=* +includes=CAN.hpp \ No newline at end of file diff --git a/config/arduinoLibraryTemplate/src/dummy.txt b/config/arduinoLibraryTemplate/src/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/config/clang-format/.clang-format b/config/clang-format/.clang-format new file mode 100644 index 00000000..f4ca94c0 --- /dev/null +++ b/config/clang-format/.clang-format @@ -0,0 +1,26 @@ +--- +# We'll use defaults from the LLVM style, but with 4 columns indentation. +#BasedOnStyle: GNU +BasedOnStyle: LLVM +#BasedOnStyle: Google +#BasedOnStyle: Mozilla +#BasedOnStyle: Chromium +IndentWidth: 4 +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -4 +Standard: c++17 +TabWidth: 2 +UseTab: Never +ColumnLimit: 100 +AlignAfterOpenBracket: Align +BinPackParameters: false +AlignEscapedNewlines: Left +AlwaysBreakTemplateDeclarations: Yes +PackConstructorInitializers: Never +BreakConstructorInitializersBeforeComma: false +IndentPPDirectives: BeforeHash +SortIncludes: Never +SeparateDefinitionBlocks: Always +... diff --git a/config/clang-tidy/.clang-tidy b/config/clang-tidy/.clang-tidy new file mode 100644 index 00000000..337b9017 --- /dev/null +++ b/config/clang-tidy/.clang-tidy @@ -0,0 +1,54 @@ +Checks: 'cppcoreguidelines-*, +performance-*, +modernize-*, +google-*, +misc-* +cert-*, +readability-*, +clang-analyzer-*, +-performance-unnecessary-value-param, +-modernize-use-trailing-return-type, +-google-runtime-references, +-misc-non-private-member-variables-in-classes, +-readability-braces-around-statements, +-google-readability-braces-around-statements, +-cppcoreguidelines-avoid-magic-numbers, +-readability-magic-numbers, +-readability-magic-numbers, +-cppcoreguidelines-pro-type-vararg, +-cppcoreguidelines-pro-bounds-pointer-arithmetic, +-cppcoreguidelines-avoid-c-arrays, +-modernize-avoid-c-arrays, +-cppcoreguidelines-pro-bounds-array-to-pointer-decay, +-readability-named-parameter, +-cert-env33-c +' + + +WarningsAsErrors: '' +HeaderFilterRegex: '.*' +#HeaderFilterRegex: '*spdlog/[^f].*' +AnalyzeTemporaryDtors: false +FormatStyle: none + +CheckOptions: + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' diff --git a/config/cppcheck/misra.txt b/config/cppcheck/misra.txt new file mode 100644 index 00000000..f98f6dd7 --- /dev/null +++ b/config/cppcheck/misra.txt @@ -0,0 +1,647 @@ +Appendix A Summary of guidelines + +Rule 1.1 Required +The program shall contain no violations of the standard C syntax and constraints, and shall not exceed the implementation’s translation limits + +Rule 1.2 Advisory +Language extensions should not be used + +Rule 1.3 Required +There shall be no occurrence of undefined or critical unspecified behaviour + +Rule 1.4 Required +Emergent language features shall not be used + +Rule 1.5 Required +Obsolescent language features shall not be used + +Rule 2.1 Required +A project shall not contain unreachable code + +Rule 2.2 Required +There shall be no dead code + +Rule 2.3 Advisory +A project should not contain unused type declarations + +Rule 2.4 Advisory +A project should not contain unused tag declarations + +Rule 2.5 Advisory +A project should not contain unused macro declarations + +Rule 2.6 Advisory +A function should not contain unused label declarations + +Rule 2.7 Advisory +There should be no unused parameters in functions + +Rule 2.8 Advisory +A project should not contain unused object definitions + +Rule 3.1 Required +The character sequences /* and // shall not be used within a comment + +Rule 3.2 Required +Line-splicing shall not be used in // comments + +Rule 4.1 Required +Octal and hexadecimal escape sequences shall be terminated + +Rule 4.2 Advisory +Trigraphs should not be used + +Dir 4.3 Required +Assembly language shall be encapsulated and isolated + +Dir 4.4 Advisory +Sections of code should not be "commented out" + +Dir 4.5 Advisory +Identifiers in the same name space with overlapping visibility should be typographically unambiguous + +Dir 4.6 Advisory +typedefs that indicate size and signedness should be used in place of the basic numerical types + +Dir 4.7 Required +If a function returns error information, then that error information shall be tested + +Dir 4.8 Advisory +If a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hidden + +Dir 4.9 Advisory +A function should be used in preference to a function-like macro where they are interchangeable + +Dir 4.10 Required +Precautions shall be taken in order to prevent the contents of a header file being included more than once + +Dir 4.11 Required +The validity of values passed to library functions shall be checked + +Dir 4.12 Required +Dynamic memory allocation shall not be used + +Dir 4.13 Advisory +Functions which are designed to provide operations on a resource should be called in an appropriate sequence + +Dir 4.15 Required +Evaluation of floating-point expressions shall not lead to the undetected generation of infinities and NaNs + +Dir 5.1 Required +There shall be no data races between threads + +Dir 5.2 Required +There shall be no deadlocks between threads + +Dir 5.3 Required +There shall be no dynamic thread creation + +Rule 5.1 Required +External identifiers shall be distinct + +Rule 5.2 Required +Identifiers declared in the same scope and name space shall be distinct + +Rule 5.3 Required +An identifier declared in an inner scope shall not hide an identifier declared in an outer scope + +Rule 5.4 Required +Macro identifiers shall be distinct + +Rule 5.5 Required +Identifiers shall be distinct from macro names + +Rule 5.6 Required +A typedef name shall be a unique identifier + +Rule 5.7 Required +A tag name shall be a unique identifier + +Rule 5.8 Required +Identifiers that define objects or functions with external linkage shall be unique + +Rule 5.9 Advisory +Identifiers that define objects or functions with internal linkage should be unique + +Rule 6.1 Required +Bit-fields shall only be declared with an appropriate type + +Rule 6.2 Required +Single-bit named bit fields shall not be of a signed type + +Rule 6.3 Required +A bit field shall not be declared as a member of a union + +Rule 7.1 Required +Octal constants shall not be used + +Rule 7.2 Required +A "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned type + +Rule 7.3 Required +The lowercase character "l" shall not be used in a literal suffix + +Rule 7.4 Required +A string literal shall not be assigned to an object unless the object’s type is "pointer to const-qualified char" + +Rule 7.5 Mandatory +The argument of an integer constant macro shall have an appropriate form + +Rule 7.6 Required +The small integer variants of the minimum-width integer constant macros shall not be used + +Rule 8.1 Required +Types shall be explicitly specified + +Rule 8.2 Required +Function types shall be in prototype form with named parameters + +Rule 8.3 Required +All declarations of an object or function shall use the same names and type qualifiers + +Rule 8.4 Required +A compatible declaration shall be visible when an object or function with external linkage is defined + +Rule 8.5 Required +An external object or function shall be declared once in one and only one file + +Rule 8.6 Required +An identifier with external linkage shall have exactly one external definition + +Rule 8.7 Advisory +Functions and objects should not be defined with external linkage if they are referenced in only one translation unit + +Rule 8.8 Required +The static storage class specifier shall be used in all declarations of objects and functions that have internal linkage + +Rule 8.9 Advisory +An object should be defined at block scope if its identifier only appears in a single function + +Rule 8.10 Required +An inline function shall be declared with the static storage class + +Rule 8.11 Advisory +When an array with external linkage is declared, its size should be explicitly specified + +Rule 8.12 Required +Enumeration member "name" have a not unique implicitly-specified value + +Rule 8.13 Advisory +A pointer should point to a const-qualified type whenever possible + +Rule 8.14 Required +The restrict type qualifier shall not be used + +Rule 8.15 Required +All declarations of an object with an explicit alignment specification shall specify the same alignment + +Rule 8.16 Advisory +The alignment specification of zero should not appear in an object declaration + +Rule 8.17 Advisory +At most one explicit alignment specifier should appear in an object declaration + +Rule 9.1 Mandatory +The value of an object with automatic storage duration shall not be read before it has been set + +Rule 9.2 Required +The initializer for an aggregate or union shall be enclosed in braces + +Rule 9.3 Required +Arrays shall not be partially initialized + +Rule 9.4 Required +An element of an object shall not be initialized more than once + +Rule 9.5 Required +Where designated initializers are used to initialize an array object the size of the array shall be specified explicitly + +Rule 9.6 Required +An initializer using chained designators shall not contain initializers without designators + +Rule 9.7 Mandatory +Atomic objects shall be appropriately initialized before being accessed + +Rule 10.1 Required +Operands shall not be of an inappropriate essential type + +Rule 10.2 Required +Expressions of essentially character type shall not be used inappropriately in addition and subtraction operations + +Rule 10.3 Required +The value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category + +Rule 10.4 Required +Both operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type category + +Rule 10.5 Advisory +The value of an expression should not be cast to an inappropriate essential type + +Rule 10.6 Required +The value of a composite expression shall not be assigned to an object with wider essential type + +Rule 10.7 Required +If a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential type + +Rule 10.8 Required +The value of a composite expression shall not be cast to a different essential type category or a wider essential type + +Rule 11.1 Required +Conversions shall not be performed between a pointer to a function and any other type + +Rule 11.2 Required +Conversions shall not be performed between a pointer to an incomplete type and any other type + +Rule 11.3 Required +A cast shall not be performed between a pointer to object type and a pointer to a different object type + +Rule 11.4 Advisory +A conversion should not be performed between a pointer to object and an integer type + +Rule 11.5 Advisory +A conversion should not be performed from pointer to void into pointer to object + +Rule 11.6 Required +A cast shall not be performed between pointer to void and an arithmetic type + +Rule 11.7 Required +A cast shall not be performed between pointer to object and a noninteger arithmetic type + +Rule 11.8 Required +A cast shall not remove any const or volatile qualification from the type pointed to by a pointer + +Rule 11.9 Required +The macro NULL shall be the only permitted form of integer null pointer constant + +Rule 11.10 Required +The _Atomic qualifier shall not be applied to the incomplete type void + +Rule 12.1 Advisory +The precedence of operators within expressions should be made explicit + +Rule 12.2 Required +The right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operand + +Rule 12.3 Advisory +The comma operator should not be used + +Rule 12.4 Advisory +Evaluation of constant expressions should not lead to unsigned integer wrap-around + +Rule 12.5 Mandatory +The sizeof operator shall not have an operand which is a function parameter declared as “array of type” + +Rule 12.6 Required +Structure and union members of atomic objects shall not be directly accessed + +Rule 13.1 Required +Initializer lists shall not contain persistent side effects + +Rule 13.2 Required +The value of an expression and its persistent side effects shall be the same under all permitted evaluation orders + +Rule 13.3 Advisory +A full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operator + +Rule 13.4 Advisory +The result of an assignment operator should not be used + +Rule 13.5 Required +The right hand operand of a logical && or || operator shall not contain persistent side effects + +Rule 13.6 Mandatory +The operand of the sizeof operator shall not contain any expression which has potential side effects + +Rule 14.1 Required +A loop counter shall not have essentially floating type + +Rule 14.2 Required +A for loop shall be well-formed + +Rule 14.3 Required +Controlling expressions shall not be invariant + +Rule 14.4 Required +The controlling expression of an if statement and the controlling expression of an iteration-statement shall have essentially Boolean type + +Rule 15.1 Advisory +The goto statement should not be used + +Rule 15.2 Required +The goto statement shall jump to a label declared later in the same function + +Rule 15.3 Required +Any label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statement + +Rule 15.4 Advisory +There should be no more than one break or goto statement used to terminate any iteration statement + +Rule 15.5 Advisory +A function should have a single point of exit at the end + +Rule 15.6 Required +The body of an iteration-statement or a selection-statement shall be a compound-statement + +Rule 15.7 Required +All if … else if constructs shall be terminated with an else statement + +Rule 16.1 Required +All switch statements shall be well-formed + +Rule 16.2 Required +A switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statement + +Rule 16.3 Required +An unconditional break statement shall terminate every switch-clause + +Rule 16.4 Required +Every switch statement shall have a default label + +Rule 16.5 Required +A default label shall appear as either the first or the last switch label of a switch statement + +Rule 16.6 Required +Every switch statement shall have at least two switch-clauses + +Rule 16.7 Required +A switch-expression shall not have essentially Boolean type + +Rule 17.1 Required +The features of "stdarg.h" shall not be used + +Rule 17.2 Required +Functions shall not call themselves, either directly or indirectly + +Rule 17.3 Mandatory +A function shall not be declared implicitly + +Rule 17.4 Mandatory +All exit paths from a function with non-void return type shall have an explicit return statement with an expression + +Rule 17.5 Advisory +The function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elements + +Rule 17.6 Mandatory +The declaration of an array parameter shall not contain the static keyword between the [ ] + +Rule 17.7 Required +The value returned by a function having non-void return type shall be used + +Rule 17.8 Advisory +A function parameter should not be modified + +Rule 17.9 Mandatory +A function declared with a _Noreturn function specifier shall not return to its caller + +Rule 17.10 Required +A function declared with a _Noreturn function specifier shall have void return type + +Rule 17.11 Advisory +A function that never returns should be declared with a _Noreturn function specifier + +Rule 17.12 Advisory +A function identifier should only be used with either a preceding &, or with a parenthesized parameter list + +Rule 17.13 Required +A function type shall not be type qualified + +Rule 18.1 Required +A pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operand + +Rule 18.2 Required +Subtraction between pointers shall only be applied to pointers that address elements of the same array + +Rule 18.3 Required +The relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same object + +Rule 18.4 Advisory +The +, -, += and -= operators should not be applied to an expression of pointer type + +Rule 18.5 Advisory +Declarations should contain no more than two levels of pointer nesting + +Rule 18.6 Required +The address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to exist + +Rule 18.7 Required +Flexible array members shall not be declared + +Rule 18.8 Required +Variable-length array types shall not be used + +Rule 18.9 Required +An object with temporary lifetime shall not undergo array-to-pointer conversion + +Rule 18.10 Mandatory +Pointers to variably-modified array types shall not be used + +Rule 19.1 Mandatory +An object shall not be assigned or copied to an overlapping object + +Rule 19.2 Advisory +The union keyword should not be used + +Rule 20.1 Advisory +#include directives should only be preceded by preprocessor directives or comments + +Rule 20.2 Required +The ', " or \ characters and the /* or // character sequences shall not occur in a header file name + +Rule 20.3 Required +The #include directive shall be followed by either a "filename" or "filename" sequence + +Rule 20.4 Required +A macro shall not be defined with the same name as a keyword + +Rule 20.5 Advisory +#undef should not be used + +Rule 20.6 Required +A C macro should only be expanded to a constant, a braced initialiser, a parenthesised expression, a storage class keyword, a type qualifier, or a do-while-zero block + +Rule 20.7 Required +Expressions resulting from the expansion of macro parameters shall be enclosed in parentheses + +Rule 20.8 Required +The controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1 + +Rule 20.9 Required +All identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define’d before evaluation + +Rule 20.10 Advisory +The # and ## preprocessor operators should not be used + +Rule 20.11 Required +A macro parameter immediately following a # operator shall not immediately be followed by a ## operator + +Rule 20.12 Required +A macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operators + +Rule 20.13 Required +A line whose first token is # shall be a valid preprocessing directive + +Rule 20.14 Required +All #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are related + +Rule 21.1 Required +#define and #undef shall not be used on a reserved identifier or reserved macro name + +Rule 21.2 Required +A reserved identifier or macro name shall not be declared + +Rule 21.3 Required +The memory allocation and deallocation functions of "stdlib.h" shall not be used + +Rule 21.4 Required +The standard header file "setjmp.h" shall not be used + +Rule 21.5 Required +The standard header file "signal.h" shall not be used + +Rule 21.6 Required +The Standard Library input/output functions shall not be used + +Rule 21.7 Required +The atof, atoi, atol and atoll functions of "stdlib.h" shall not be used + +Rule 21.8 Required +The Standard Library termination functions of "stdlib.h" shall not be used + +Rule 21.9 Required +The library functions bsearch and qsort of "stdlib.h" shall not be used + +Rule 21.10 Required +The Standard Library time and date functions shall not be used + +Rule 21.11 Required +The standard header file "tgmath.h" shall not be used + +Rule 21.12 Advisory +The exception handling features of "fenv.h" should not be used + +Rule 21.13 Mandatory +Any value passed to a function in "ctype.h" shall be representable as an unsigned char or be the value EOF + +Rule 21.14 Required +The Standard Library function memcmp shall not be used to compare null terminated strings + +Rule 21.15 Required +The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible types + +Rule 21.16 Required +The pointer arguments to the Standard Library function memcmp shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum type + +Rule 21.17 Mandatory +Use of the string handling functions from "string.h" shall not result in accesses beyond the bounds of the objects referenced by their pointer parameters + +Rule 21.18 Mandatory +The size_t argument passed to any function in "string.h" shall have an appropriate value + +Rule 21.19 Mandatory +The pointers returned by the Standard Library functions localeconv, getenv, setlocale or, strerror shall only be used as if they have pointer to const-qualified type + +Rule 21.20 Mandatory +The pointer returned by the Standard Library functions asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale or strerror shall not be used following a subsequent call to the same function + +Rule 21.21 Required +The Standard Library function system of "stdlib.h" shall not be used + +Rule 21.22 Mandatory +All operand arguments to any type-generic macros declared in "tgmath.h" shall have an appropriate essential type + +Rule 21.23 Required +All operand arguments to any multi-argument type-generic macros declared in "tgmath.h" shall have the same standard type + +Rule 21.24 Required +The random number generator functions of "stdlib.h" shall not be used + +Rule 21.25 Required +All memory synchronization operations shall be executed in sequentially consistent order + +Rule 21.26 Required +The Standard Library function mtx_timedlock() shall only be invoked on mutex objects of appropriate mutex type + +Rule 22.1 Required +All resources obtained dynamically by means of Standard Library functions shall be explicitly released + +Rule 22.2 Mandatory +A block of memory shall only be freed if it was allocated by means of a Standard Library function + +Rule 22.3 Required +The same file shall not be open for read and write access at the same time on different streams + +Rule 22.4 Mandatory +There shall be no attempt to write to a stream which has been opened as read-only + +Rule 22.5 Mandatory +A pointer to a FILE object shall not be dereferenced + +Rule 22.6 Mandatory +The value of a pointer to a FILE shall not be used after the associated stream has been closed + +Rule 22.7 Required +The macro EOF shall only be compared with the unmodified return value from any Standard Library function capable of returning EOF + +Rule 22.8 Required +The value of errno shall be set to zero prior to a call to an errno-setting-function + +Rule 22.9 Required +The value of errno shall be tested against zero after calling an errno-setting-function + +Rule 22.10 Required +The value of errno shall only be tested when the last function to be called was an errno-setting-function + +Rule 22.11 Required +A thread that was previously either joined or detached shall not be subsequently joined nor detached + +Rule 22.12 Mandatory +Thread objects, thread synchronization objects, and thread-specific storage pointers shall only be accessed by the appropriate Standard Library functions + +Rule 22.13 Required +Thread objects, thread synchronization objects and thread-specific storage pointers shall have appropriate storage duration + +Rule 22.14 Mandatory +Thread synchronization objects shall be initialized before being accessed + +Rule 22.15 Required +Thread synchronization objects and thread-specific storage pointers shall not be destroyed until after all threads accessing them have terminated + +Rule 22.16 Required +All mutex objects locked by a thread shall be explicitly unlocked by the same thread + +Rule 22.17 Required +No thread shall unlock a mutex or call cnd_wait() or cnd_timedwait() for a mutex it has not locked before + +Rule 22.18 Required +Non-recursive mutexes shall not be recursively locked + +Rule 22.19 Required +A condition variable shall be associated with at most one mutex object + +Rule 22.20 Mandatory +Thread-specific storage pointers shall be created before being accessed + +Rule 23.1 Advisory +A generic selection should only be expanded from a macro + +Rule 23.2 Required +A generic selection that is not expanded from a macro shall not contain potential side effects in the controlling expression + +Rule 23.3 Advisory +A generic selection should contain at least one non-default association + +Rule 23.4 Required +A generic association shall list an appropriate type + +Rule 23.5 Advisory +A generic selection should not depend on implicit pointer type conversion + +Rule 23.6 Required +The controlling expression of a generic selection shall have an essential type that matches its standard type + +Rule 23.7 Advisory +A generic selection that is expanded from a macro should evaluate its argument only once + +Rule 23.8 Required +A default association shall appear as either the first or the last association of a generic selection + diff --git a/config/cppcheck/misra_local.py b/config/cppcheck/misra_local.py new file mode 100644 index 00000000..f05ee632 --- /dev/null +++ b/config/cppcheck/misra_local.py @@ -0,0 +1,4973 @@ +#!/usr/bin/env python3 +# +# MISRA C 2012 checkers +# Partially reused for "MISRA C++ 2008" checking +# +# Example usage of this addon (scan a sourcefile main.cpp) +# cppcheck --dump main.cpp +# python misra.py --rule-texts= main.cpp.dump +# +# Limitations: This addon is released as open source. Rule texts can't be freely +# distributed. https://www.misra.org.uk/forum/viewtopic.php?f=56&t=1189 +# +# The MISRA standard documents may be obtained from https://www.misra.org.uk +# +# Total number of rules: 143 + +from __future__ import print_function + +import cppcheckdata +import itertools +import json +import sys +import re +import os +import argparse +import codecs +import string +import copy + +try: + from itertools import izip as zip +except ImportError: + pass + +import misra_9 + +def grouped(iterable, n): + """s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), (s2n,s2n+1,s2n+2,...s3n-1), ...""" + return zip(*[iter(iterable)] * n) + + +INT_TYPES = ['bool', 'char', 'short', 'int', 'long', 'long long'] + + +STDINT_TYPES = ['%s%d_t' % (n, v) for n, v in itertools.product( + ['int', 'uint', 'int_least', 'uint_least', 'int_fast', 'uint_fast'], + [8, 16, 32, 64])] + + +typeBits = { + 'CHAR': None, + 'SHORT': None, + 'INT': None, + 'LONG': None, + 'LONG_LONG': None, + 'POINTER': None +} + + +def isUnsignedType(ty): + return ty == 'unsigned' or ty.startswith('uint') + + +def simpleMatch(token, pattern): + return cppcheckdata.simpleMatch(token, pattern) + + +def rawlink(rawtoken): + if rawtoken.str == '}': + indent = 0 + while rawtoken: + if rawtoken.str == '}': + indent = indent + 1 + elif rawtoken.str == '{': + indent = indent - 1 + if indent == 0: + break + rawtoken = rawtoken.previous + else: + rawtoken = None + return rawtoken + + +# Identifiers described in Section 7 "Library" of C90 Standard +# Based on ISO/IEC9899:1990 Annex D -- Library summary and +# Annex E -- Implementation limits. +C90_STDLIB_IDENTIFIERS = { + # D.1 Errors + 'errno.h': ['EDOM', 'ERANGE', 'errno'], + # D.2 Common definitions + 'stddef.h': ['NULL', 'offsetof', 'ptrdiff_t', 'size_t', 'wchar_t'], + # D.3 Diagnostics + 'assert.h': ['NDEBUG', 'assert'], + # D.4 Character handling + 'ctype.h': [ + 'isalnum', 'isalpha', 'isblank', 'iscntrl', 'isdigit', + 'isgraph', 'islower', 'isprint', 'ispunct', 'isspace', + 'isupper', 'isxdigit', 'tolower', 'toupper', + ], + # D.5 Localization + 'locale.h': [ + 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', + 'LC_NUMERIC', 'LC_TIME', 'NULL', 'lconv', + 'setlocale', 'localeconv', + ], + # D.6 Mathematics + 'math.h': [ + 'HUGE_VAL', 'acos', 'asin' , 'atan2', 'cos', 'sin', 'tan', 'cosh', + 'sinh', 'tanh', 'exp', 'frexp', 'ldexp', 'log', 'loglO', 'modf', + 'pow', 'sqrt', 'ceil', 'fabs', 'floor', 'fmod', + ], + # D.7 Nonlocal jumps + 'setjmp.h': ['jmp_buf', 'setjmp', 'longjmp'], + # D.8 Signal handling + 'signal.h': [ + 'sig_atomic_t', 'SIG_DFL', 'SIG_ERR', 'SIG_IGN', 'SIGABRT', 'SIGFPE', + 'SIGILL', 'SIGINT', 'SIGSEGV', 'SIGTERM', 'signal', 'raise', + ], + # D.9 Variable arguments + 'stdarg.h': ['va_list', 'va_start', 'va_arg', 'va_end'], + # D.10 Input/output + 'stdio.h': [ + '_IOFBF', '_IOLBF', '_IONBF', 'BUFSIZ', 'EOF', 'FILE', 'FILENAME_MAX', + 'FOPEN_MAX', 'fpos_t', 'L_tmpnam', 'NULL', 'SEEK_CUR', 'SEEK_END', + 'SEEK_SET', 'size_t', 'stderr', 'stdin', 'stdout', 'TMP_MAX', + 'remove', 'rename', 'tmpfile', 'tmpnam', 'fclose', 'fflush', 'fopen', + 'freopen', 'setbuf', 'setvbuf', 'fprintf', 'fscanf', 'printf', + 'scanf', 'sprintf', 'sscanf', 'vfprintf', 'vprintf', 'vsprintf', + 'fgetc', 'fgets', 'fputc', 'fputs', 'getc', 'getchar', 'gets', 'putc', + 'putchar', 'puts', 'ungetc', 'fread', 'fwrite', 'fgetpos', 'fseek', + 'fsetpos', 'rewind', 'clearerr', 'feof', 'ferror', 'perror', + ], + # D.11 General utilities + 'stdlib.h': [ + 'EXIT_FAILURE', 'EXIT_SUCCESS', 'MB_CUR_MAX', 'NULL', 'RAND_MAX', + 'div_t', 'ldiv_t', 'wchar_t', 'atof', 'atoi', 'strtod', 'rand', + 'srand', 'calloc', 'free', 'malloc', 'realloc', 'abort', 'atexit', + 'exit', 'getenv', 'system', 'bsearch', 'qsort', 'abs', 'div', 'ldiv', + 'mblen', 'mbtowc', 'wctomb', 'mbstowcs', 'wcstombs', + ], + # D.12 String handling + 'string.h': [ + 'NULL', 'size_t', 'memcpy', 'memmove', 'strcpy', 'strncpy', 'strcat', + 'strncat', 'memcmp', 'strcmp', 'strcoll', 'strncmp', 'strxfrm', + 'memchr', 'strchr', 'strcspn', 'strpbrk', 'strrchr', 'strspn', + 'strstr', 'strtok', 'memset', 'strerror', 'strlen', + ], + # D.13 Date and time + 'time.h': [ + 'CLK_TCK', 'NULL', 'clock_t', 'time_t', 'size_t', 'tm', 'clock', + 'difftime', 'mktime', 'time', 'asctime', 'ctime', 'gmtime', + 'localtime', 'strftime', + ], + # Annex E: Implementation limits + 'limits.h': [ + 'CHAR_BIT', 'SCHAR_MIN', 'SCHAR_MAX', 'UCHAR_MAX', 'CHAR_MIN', + 'CHAR_MAX', 'MB_LEN_MAX', 'SHRT_MIN', 'SHRT_MAX', 'USHRT_MAX', + 'INT_MIN', 'INT_MAX', 'UINT_MAX', 'LONG_MIN', 'LONG_MAX', 'ULONG_MAX', + ], + 'float.h': [ + 'FLT_ROUNDS', 'FLT_RADIX', 'FLT_MANT_DIG', 'DBL_MANT_DIG', + 'LDBL_MANT_DIG', 'DECIMAL_DIG', 'FLT_DIG', 'DBL_DIG', 'LDBL_DIG', + 'DBL_MIN_EXP', 'LDBL_MIN_EXP', 'FLT_MIN_10_EXP', 'DBL_MIN_10_EXP', + 'LDBL_MIN_10_EXP', 'FLT_MAX_EXP', 'DBL_MAX_EXP', 'LDBL_MAX_EXP', + 'FLT_MAX_10_EXP', 'DBL_MAX_10_EXP', 'LDBL_MAX_10_EXP', 'FLT_MAX', + 'DBL_MAX', 'LDBL_MAX', 'FLT_MIN', 'DBL_MIN', 'LDBL_MIN', + 'FLT_EPSILON', 'DBL_EPSILON', 'LDBL_EPSILON' + ], +} + + +# Identifiers described in Section 7 "Library" of C99 Standard +# Based on ISO/IEC 9899 WF14/N1256 Annex B -- Library summary +C99_STDLIB_IDENTIFIERS = { + # B.1 Diagnostics + 'assert.h': C90_STDLIB_IDENTIFIERS['assert.h'], + # B.2 Complex + 'complex.h': [ + 'complex', 'imaginary', 'I', '_Complex_I', '_Imaginary_I', + 'CX_LIMITED_RANGE', + 'cacos', 'cacosf', 'cacosl', + 'casin', 'casinf', 'casinl', + 'catan', 'catanf', 'catanl', + 'ccos', 'ccosf', 'ccosl', + 'csin', 'csinf', 'csinl', + 'ctan', 'ctanf', 'ctanl', + 'cacosh', 'cacoshf', 'cacoshl', + 'casinh', 'casinhf', 'casinhl', + 'catanh', 'catanhf', 'catanhl', + 'ccosh', 'ccoshf', 'ccoshl', + 'csinh', 'csinhf', 'csinhl', + 'ctanh', 'ctanhf', 'ctanhl', + 'cexp', 'cexpf', 'cexpl', + 'clog', 'clogf', 'clogl', + 'cabs', 'cabsf', 'cabsl', + 'cpow', 'cpowf', 'cpowl', + 'csqrt', 'csqrtf', 'csqrtl', + 'carg', 'cargf', 'cargl', + 'cimag', 'cimagf', 'cimagl', + 'conj', 'conjf', 'conjl', + 'cproj', 'cprojf', 'cprojl', + 'creal', 'crealf', 'creall', + ], + # B.3 Character handling + 'ctype.h': C90_STDLIB_IDENTIFIERS['ctype.h'], + # B.4 Errors + 'errno.h': C90_STDLIB_IDENTIFIERS['errno.h'] + ['EILSEQ'], + # B.5 Floating-point environment + 'fenv.h': [ + 'fenv_t', 'FE_OVERFLOW', 'FE_TOWARDZERO', + 'fexcept_t', 'FE_UNDERFLOW', 'FE_UPWARD', + 'FE_DIVBYZERO', 'FE_ALL_EXCEPT', 'FE_DFL_ENV', + 'FE_INEXACT', 'FE_DOWNWARD', + 'FE_INVALID', 'FE_TONEAREST', + 'FENV_ACCESS', + 'feclearexcept', 'fegetexceptflag', 'fegetround', + 'fesetround', 'fegetenv', 'feholdexcept', + 'fesetenv', 'feupdateenv', + ], + # B.6 Characteristics of floating types + 'float.h': C90_STDLIB_IDENTIFIERS['float.h'] + ['FLT_EVAL_METHOD'], + # B.7 Format conversion of integer types + 'inttypes.h': [ + 'imaxdiv_t', 'imaxabs', 'imaxdiv', 'strtoimax', + 'strtoumax', 'wcstoimax', 'wcstoumax', + ], + # B.8 Alternative spellings + 'iso646.h': [ + 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', + 'or', 'or_eq', 'xor', 'xor_eq', + ], + # B.9 Size of integer types + 'limits.h': C90_STDLIB_IDENTIFIERS['limits.h'] + + ['LLONG_MIN', 'LLONG_MAX', 'ULLONG_MAX'], + # B.10 Localization + 'locale.h': C90_STDLIB_IDENTIFIERS['locale.h'], + # B.11 Mathematics + 'math.h': C90_STDLIB_IDENTIFIERS['math.h'] + [ + 'float_t', 'double_t', 'HUGE_VAL', 'HUGE_VALF', 'HUGE_VALL', + 'INFINITY', 'NAN', 'FP_INFINITE', 'FP_NAN', 'FP_NORMAL', + 'FP_SUBNORMAL', 'FP_ZERO', 'FP_FAST_FMA', 'FP_FAST_FMAF', + 'FP_FAST_FMAL', 'FP_ILOGB0', 'FP_ILOGBNAN', 'MATH_ERRNO', + 'MATH_ERREXCEPT', 'math_errhandling', 'FP_CONTRACT', 'fpclassify', + 'isfinite', 'isinf', 'isnan', 'isnormal', 'signbit', 'acosf', 'acosl', + 'asinf', 'asinl', 'atanf', 'atanl', 'atan2', 'atan2f', 'atan2l', + 'cosf', 'cosl', 'sinf', 'sinl', 'tanf', 'tanl', 'acosh', 'acoshf', + 'acoshl', 'asinh', 'asinhf', 'asinhl', 'atanh', 'atanhf', 'atanhl', + 'cosh', 'coshf', 'coshl', 'sinh', 'sinhf', 'sinhl', 'tanh', 'tanhf', + 'tanhl', 'expf', 'expl', 'exp2', 'exp2f', 'exp2l', 'expm1', 'expm1f', + 'expm1l', 'frexpf', 'frexpl', 'ilogb', 'ilogbf', 'ilogbl', 'float', + 'ldexpl', 'logf', 'logl', 'log10f', 'log10l', 'log1p', 'log1pf', + 'log1pl', 'log2', 'log2f', 'log2l', 'logb', 'logbf', 'logbl', 'modff', + 'modfl', 'scalbn', 'scalbnf', 'scalbnl', 'scalbln', 'scalblnf', + 'scalblnl', 'hypotl', 'powf', 'powl', 'sqrtf', 'sqrtl', 'erf', 'erff', + 'erfl', 'erfc', 'erfcf', 'erfcl', 'lgamma', 'lgammaf', 'lgammal', + 'tgamma', 'tgammaf', 'tgammal', 'ceilf', 'ceill', 'floorf', 'floorl', + 'nearbyint', 'nearbyintf', 'nearbyintl', 'rint', 'rintf', 'rintl', + 'lrint', 'lrintf', 'lrintl', 'llrint', 'llrintf', 'llrintl', 'round', + 'roundf', 'roundl', 'lround', 'lroundf', 'lroundl', 'llround', + 'llroundf', 'llroundl', 'trunc', 'truncf', 'truncl', 'fmodf', 'fmodl', + 'remainder', 'remainderf', 'remainderl', 'remquo', 'remquof', + 'remquol', 'copysign', 'copysignf', 'copysignl', 'nan', 'nanf', + 'nanl', 'nextafter', 'nextafterf', 'nextafterl', 'nexttoward', + 'nexttowardf', 'nexttowardl', 'fdim', 'fdimf', 'fdiml', 'fmax', + 'fmaxf', 'fmaxl', 'fmin', 'fminf', 'fminl', 'fmal', 'isgreater', + 'isgreaterequal', 'isless', 'islessequal', 'islessgreater', + 'isunordered', + ], + # B.12 Nonlocal jumps + 'setjmp.h': C90_STDLIB_IDENTIFIERS['setjmp.h'], + # B.13 Signal handling + 'signal.h': C90_STDLIB_IDENTIFIERS['signal.h'], + # B.14 Variable arguments + 'stdarg.h': C90_STDLIB_IDENTIFIERS['stdarg.h'] + ['va_copy'], + # B.15 Boolean type and values + 'stdbool.h': ['bool', 'true', 'false', '__bool_true_false_are_defined'], + # B.16 Common definitions + 'stddef.h': C90_STDLIB_IDENTIFIERS['stddef.h'], + # B.17 Integer types + 'stdint.h': [ + 'intptr_t', 'uintptr_t', 'intmax_t', 'uintmax_t', 'INTN_MIN', + 'INTN_MAX', 'UINTN_MAX', 'INT_LEASTN_MIN', 'INT_LEASTN_MAX', + 'UINT_LEASTN_MAX', 'INT_FASTN_MIN', 'INT_FASTN_MAX', 'UINT_FASTN_MAX', + 'INTPTR_MIN', 'INTPTR_MAX', 'UINTPTR_MAX', 'INTMAX_MIN', 'INTMAX_MAX', + 'UINTMAX_MAX', 'PTRDIFF_MIN', 'PTRDIFF_MAX', 'SIG_ATOMIC_MIN', + 'SIG_ATOMIC_MAX', 'SIZE_MAX', 'WCHAR_MIN', 'WCHAR_MAX', 'WINT_MIN', + 'WINT_MAX', 'INTN_C', 'UINTN_C', 'INTMAX_C', 'UINTMAX_C', + ] + STDINT_TYPES, + # B.18 Input/output + 'stdio.h': C90_STDLIB_IDENTIFIERS['stdio.h'] + [ + 'mode', 'restrict', 'snprintf', 'vfscanf', 'vscanf', + 'vsnprintf', 'vsscanf', + ], + # B.19 General utilities + 'stdlib.h': C90_STDLIB_IDENTIFIERS['stdlib.h'] + [ + '_Exit', 'labs', 'llabs', 'lldiv', 'lldiv_t', 'strtof', 'strtol', + 'strtold', 'strtoll', 'strtoul', 'strtoull' + ], + # B.20 String handling + 'string.h': C90_STDLIB_IDENTIFIERS['string.h'], + # B.21 Type-generic math + 'tgmath.h': [ + 'acos', 'asin', 'atan', 'acosh', 'asinh', 'atanh', 'cos', 'sin', 'tan', + 'cosh', 'sinh', 'tanh', 'exp', 'log', 'pow', 'sqrt', 'fabs', 'atan2', + 'cbrt', 'ceil', 'copysign', 'erf', 'erfc', 'exp2', 'expm1', 'fdim', + 'floor', 'fma', 'fmax', 'fmin', 'fmod', 'frexp', 'hypot', 'ilogb', + 'ldexp', 'lgamma', 'llrint', 'llround', 'log10', 'log1p', 'log2', + 'logb', 'lrint', 'lround', 'nearbyint', 'nextafter', 'nexttoward', + 'remainder', 'remquo', 'rint', 'round', 'scalbn', 'scalbln', 'tgamma', + 'trunc', 'carg', 'cimag', 'conj', 'cproj', 'creal', + ], + # B.22 Date and time + 'time.h': C90_STDLIB_IDENTIFIERS['time.h'] + ['CLOCKS_PER_SEC'], + # B.23 Extended multibyte/wide character utilities + 'wchar.h': [ + 'wchar_t', 'size_t', 'mbstate_t', 'wint_t', 'tm', 'NULL', 'WCHAR_MAX', + 'WCHAR_MIN', 'WEOF', 'fwprintf', 'fwscanf', 'swprintf', 'swscanf', + 'vfwprintf', 'vfwscanf', 'vswprintf', 'vswscanf', 'vwprintf', + 'vwscanf', 'wprintf', 'wscanf', 'fgetwc', 'fgetws', 'fputwc', 'fputws', + 'fwide', 'getwc', 'getwchar', 'putwc', 'putwchar', 'ungetwc', 'wcstod', + 'wcstof', 'double', 'int', 'long', 'long', 'long', 'wcscpy', 'wcsncpy', + 'wmemcpy', 'wmemmove', 'wcscat', 'wcsncat', 'wcscmp', 'wcscoll', + 'wcsncmp', 'wcsxfrm', 'wmemcmp', 'wcschr', 'wcscspn', 'wcspbrk', + 'wcsrchr', 'wcsspn', 'wcsstr', 'wcstok', 'wmemchr', 'wcslen', + 'wmemset', 'wcsftime', 'btowc', 'wctob', 'mbsinit', 'mbrlen', + 'mbrtowc', 'wcrtomb', 'mbsrtowcs', 'wcsrtombs', + ], +} + + +def isStdLibId(id_, standard='c99'): + id_lists = [] + if standard == 'c89': + id_lists = C90_STDLIB_IDENTIFIERS.values() + elif standard in ('c99', 'c11'): + id_lists = C99_STDLIB_IDENTIFIERS.values() + for l in id_lists: + if id_ in l: + return True + return False + + +# Reserved keywords defined in ISO/IEC9899:1990 -- ch 6.1.1 +C90_KEYWORDS = { + 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', + 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', + 'int', 'long', 'register', 'return', 'short', 'signed', + 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', + 'void', 'volatile', 'while' +} + + +# Reserved keywords defined in ISO/IEC 9899 WF14/N1256 -- ch. 6.4.1 +C99_ADDED_KEYWORDS = { + 'inline', 'restrict', '_Bool', '_Complex', '_Imaginary', + 'bool', 'complex', 'imaginary' +} + +C11_ADDED_KEYWORDS = { + '_Alignas', '_Alignof', '_Atomic', '_Generic', '_Noreturn', + '_Statis_assert', '_Thread_local' , + 'alignas', 'alignof', 'noreturn', 'static_assert' +} + +def isKeyword(keyword, standard='c99'): + kw_set = {} + if standard == 'c89': + kw_set = C90_KEYWORDS + elif standard == 'c99': + kw_set = copy.copy(C90_KEYWORDS) + kw_set.update(C99_ADDED_KEYWORDS) + else: + kw_set = copy.copy(C90_KEYWORDS) + kw_set.update(C99_ADDED_KEYWORDS) + kw_set.update(C11_ADDED_KEYWORDS) + return keyword in kw_set + + +def is_source_file(file): + return file.endswith('.c') + + +def is_header(file): + return file.endswith('.h') + + +def is_errno_setting_function(function_name): + return function_name and \ + function_name in ('ftell', 'fgetpos', 'fsetpos', 'fgetwc', 'fputwc' + 'strtoimax', 'strtoumax', 'strtol', 'strtoul', + 'strtoll', 'strtoull', 'strtof', 'strtod', 'strtold' + 'wcstoimax', 'wcstoumax', 'wcstol', 'wcstoul', + 'wcstoll', 'wcstoull', 'wcstof', 'wcstod', 'wcstold' + 'wcrtomb', 'wcsrtombs', 'mbrtowc') + + +def get_type_conversion_to_from(token): + def get_vartok(expr): + while expr: + if isCast(expr): + if expr.astOperand2 is None: + expr = expr.astOperand1 + else: + expr = expr.astOperand2 + elif expr.str in ('.', '::'): + expr = expr.astOperand2 + elif expr.str == '[': + expr = expr.astOperand1 + else: + break + return expr if (expr and expr.variable) else None + + if isCast(token): + vartok = get_vartok(token) + if vartok: + return (token.next, vartok.variable.typeStartToken) + + elif token.str == '=': + lhs = get_vartok(token.astOperand1) + rhs = get_vartok(token.astOperand2) + if lhs and rhs: + return (lhs.variable.typeStartToken, rhs.variable.typeStartToken) + + return None + + +def is_composite_expr(expr, composite_operator=False): + """MISRA C 2012, section 8.10.3""" + if expr is None: + return False + + if not composite_operator: + if (expr.str in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~')): + return is_composite_expr(expr.astOperand1,True) or is_composite_expr(expr.astOperand2, True) + if expr.str == '?' and simpleMatch(expr.astOperand2, ':'): + colon = expr.astOperand2 + return is_composite_expr(colon.astOperand1,True) or is_composite_expr(colon.astOperand2, True) + return False + + # non constant expression? + if expr.isNumber: + return False + if expr.astOperand1 or expr.astOperand2: + return is_composite_expr(expr.astOperand1,True) or is_composite_expr(expr.astOperand2, True) + return True + + +def getEssentialTypeCategory(expr): + if not expr: + return None + if expr.str == ',': + return getEssentialTypeCategory(expr.astOperand2) + if expr.str in ('<', '<=', '==', '!=', '>=', '>', '&&', '||', '!'): + return 'bool' + if expr.str in ('<<', '>>'): + # TODO this is incomplete + return getEssentialTypeCategory(expr.astOperand1) + if len(expr.str) == 1 and expr.str in '+-*/%&|^': + # TODO this is incomplete + e1 = getEssentialTypeCategory(expr.astOperand1) + e2 = getEssentialTypeCategory(expr.astOperand2) + # print('{0}: {1} {2}'.format(expr.str, e1, e2)) + if e1 and e2 and e1 == e2: + return e1 + if expr.valueType: + return expr.valueType.sign + if expr.valueType and expr.valueType.typeScope and expr.valueType.typeScope.className: + return "enum<" + expr.valueType.typeScope.className + ">" + # Unwrap membership, dereferences and array indexing + vartok = expr + while True: + if simpleMatch(vartok, '[') or (vartok and vartok.str == '*' and vartok.astOperand2 is None): + vartok = vartok.astOperand1 + elif simpleMatch(vartok, '.'): + vartok = vartok.astOperand2 + else: + break + if vartok and vartok.variable: + typeToken = vartok.variable.typeStartToken + while typeToken and typeToken.isName: + if typeToken.str == 'char' and not typeToken.isSigned and not typeToken.isUnsigned: + return 'char' + if typeToken.valueType: + if typeToken.valueType.type == 'bool': + return typeToken.valueType.type + if typeToken.valueType.type in ('float', 'double', 'long double'): + return "float" + if typeToken.valueType.sign: + return typeToken.valueType.sign + typeToken = typeToken.next + + # See Appendix D, section D.6, Character constants + if expr.str[0] == "'" and expr.str[-1] == "'": + if len(expr.str) == 3 or (len(expr.str) == 4 and expr.str[1] == '\\'): + return 'char' + return expr.valueType.sign + + if (expr.isCast and expr.str == "("): + castTok = expr.next + while castTok.isName or castTok.str == "*": + if castTok.str == 'char' and not castTok.isSigned and not castTok.isUnsigned: + return 'char' + castTok = castTok.next + + if expr.valueType: + return expr.valueType.sign + return None + + +def getEssentialCategorylist(operand1, operand2): + if not operand1 or not operand2: + return None, None + if (operand1.str in ('++', '--') or + operand2.str in ('++', '--')): + return None, None + if ((operand1.valueType and operand1.valueType.pointer) or + (operand2.valueType and operand2.valueType.pointer)): + return None, None + e1 = getEssentialTypeCategory(operand1) + e2 = getEssentialTypeCategory(operand2) + return e1, e2 + + +def get_essential_type_from_value(value, is_signed): + if value is None: + return None + for t in ('char', 'short', 'int', 'long', 'long long'): + bits = bitsOfEssentialType(t) + if bits >= 64: + continue + if is_signed: + range_min = -(1 << (bits - 1)) + range_max = (1 << (bits - 1)) - 1 + else: + range_min = 0 + range_max = (1 << bits) - 1 + sign = 'signed' if is_signed else 'unsigned' + if is_signed and value < 0 and value >= range_min: + return '%s %s' % (sign, t) + if value >= 0 and value <= range_max: + return '%s %s' % (sign, t) + return None + +def getEssentialType(expr): + if not expr: + return None + + # See Appendix D, section D.6, Character constants + if expr.str[0] == "'" and expr.str[-1] == "'": + if len(expr.str) == 3 or (len(expr.str) == 4 and expr.str[1] == '\\'): + return 'char' + return '%s %s' % (expr.valueType.sign, expr.valueType.type) + + if expr.variable or isCast(expr): + typeToken = expr.variable.typeStartToken if expr.variable else expr.next + while typeToken and typeToken.isName: + if typeToken.str == 'char' and not typeToken.isSigned and not typeToken.isUnsigned: + return 'char' + typeToken = typeToken.next + if expr.valueType: + if expr.valueType.type == 'bool': + return 'bool' + if expr.valueType.isFloat(): + return expr.valueType.type + if expr.valueType.isIntegral(): + if (expr.valueType.sign is None) and expr.valueType.type == 'char': + return 'char' + return '%s %s' % (expr.valueType.sign, expr.valueType.type) + + elif expr.isNumber: + # Appendix D, D.6 The essential type of literal constants + # Integer constants + if expr.valueType.type == 'bool': + return 'bool' + if expr.valueType.isFloat(): + return expr.valueType.type + if expr.valueType.isIntegral(): + if expr.valueType.type != 'int': + return '%s %s' % (expr.valueType.sign, expr.valueType.type) + return get_essential_type_from_value(expr.getKnownIntValue(), expr.valueType.sign == 'signed') + + elif expr.str in ('<', '<=', '>=', '>', '==', '!=', '&&', '||', '!'): + return 'bool' + + elif expr.astOperand1 and expr.astOperand2 and expr.str in ( + '+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":"): + if expr.astOperand1.valueType and expr.astOperand1.valueType.pointer > 0: + return None + if expr.astOperand2.valueType and expr.astOperand2.valueType.pointer > 0: + return None + e1 = getEssentialType(expr.astOperand1) + e2 = getEssentialType(expr.astOperand2) + if e1 is None or e2 is None: + return None + if is_constant_integer_expression(expr): + sign1 = e1.split(' ')[0] + sign2 = e2.split(' ')[0] + if sign1 == sign2 and sign1 in ('signed', 'unsigned'): + e = get_essential_type_from_value(expr.getKnownIntValue(), sign1 == 'signed') + if e: + return e + if bitsOfEssentialType(e2) >= bitsOfEssentialType(e1): + return e2 + else: + return e1 + + elif expr.str == "~": + e1 = getEssentialType(expr.astOperand1) + return e1 + + return None + + +def bitsOfEssentialType(ty): + if ty is None: + return 0 + last_type = ty.split(' ')[-1] + if last_type == 'Boolean': + return 1 + if last_type == 'char': + return typeBits['CHAR'] + if last_type == 'short': + return typeBits['SHORT'] + if last_type == 'int': + return typeBits['INT'] + if ty.endswith('long long'): + return typeBits['LONG_LONG'] + if last_type == 'long': + return typeBits['LONG'] + for sty in STDINT_TYPES: + if ty == sty: + return int(''.join(filter(str.isdigit, sty))) + return 0 + + +def get_function_pointer_type(tok): + ret = '' + par = 0 + while tok and (tok.isName or tok.str == '*'): + ret += ' ' + tok.str + tok = tok.next + if tok is None or tok.str != '(': + return None + tok = tok.link + if not simpleMatch(tok, ') ('): + return None + ret += '(' + tok = tok.next.next + while tok and (tok.str not in '()'): + if tok.varId is None: + ret += ' ' + tok.str + tok = tok.next + if (tok is None) or tok.str != ')': + return None + return ret[1:] + ')' + +def isCast(expr): + if not expr or expr.str != '(' or not expr.astOperand1 or expr.astOperand2: + return False + if simpleMatch(expr, '( )'): + return False + return True + +def is_constant_integer_expression(expr): + if expr is None: + return False + if expr.isInt: + return True + if not expr.isArithmeticalOp: + return False + if expr.astOperand1 and not is_constant_integer_expression(expr.astOperand1): + return False + if expr.astOperand2 and not is_constant_integer_expression(expr.astOperand2): + return False + return True + +def isFunctionCall(expr, std='c99'): + if not expr: + return False + if expr.str != '(' or not expr.astOperand1: + return False + if expr.astOperand1 != expr.previous: + return False + if isKeyword(expr.astOperand1.str, std): + return False + return True + + +def hasExternalLinkage(var): + return var.isGlobal and not var.isStatic + + +def countSideEffects(expr): + if not expr or expr.str in (',', ';'): + return 0 + ret = 0 + if expr.str in ('++', '--', '='): + ret = 1 + return ret + countSideEffects(expr.astOperand1) + countSideEffects(expr.astOperand2) + + +def getForLoopExpressions(forToken): + if not forToken or forToken.str != 'for': + return None + lpar = forToken.next + if not lpar or lpar.str != '(': + return None + if not lpar.astOperand2 or lpar.astOperand2.str != ';': + return None + if not lpar.astOperand2.astOperand2 or lpar.astOperand2.astOperand2.str != ';': + return None + return [lpar.astOperand2.astOperand1, + lpar.astOperand2.astOperand2.astOperand1, + lpar.astOperand2.astOperand2.astOperand2] + + +def get_function_scope(cfg, func): + if func: + for scope in cfg.scopes: + if scope.function == func: + return scope + return None + + +def is_variable_changed(start_token, end_token, var): + """Check if variable is updated between body_start and body_end""" + tok = start_token + while tok != end_token: + if tok.isAssignmentOp: + vartok = tok.astOperand1 + while vartok.astOperand1: + vartok = vartok.astOperand1 + if vartok and vartok.variable == var: + return True + tok = tok.next + return False + +def getForLoopCounterVariables(forToken, cfg): + """ Return a set of Variable objects defined in ``for`` statement and + satisfy requirements to loop counter term from section 8.14 of MISRA + document. + """ + if not forToken or forToken.str != 'for': + return None + tn = forToken.next + if not tn or tn.str != '(': + return None + vars_defined = set() + vars_initialized = set() + vars_exit = set() + vars_modified = set() + cur_clause = 1 + te = tn.link + while tn and tn != te: + if tn.variable: + if cur_clause == 1 and tn.variable.nameToken == tn: + vars_defined.add(tn.variable) + elif cur_clause == 2: + vars_exit.add(tn.variable) + elif cur_clause == 3: + if tn.next and countSideEffectsRecursive(tn.next) > 0: + vars_modified.add(tn.variable) + elif tn.previous and tn.previous.str in ('++', '--'): + tn_ast = tn.astParent + if tn_ast and tn_ast == tn.previous: + vars_modified.add(tn.variable) + elif tn_ast and tn_ast.str == '.' and tn_ast.astOperand2 and tn_ast.astOperand2.variable: + vars_modified.add(tn_ast.astOperand2.variable) + if cur_clause == 1 and tn.isAssignmentOp: + var_token = tn.astOperand1 + while var_token and var_token.str == '.': + var_token = var_token.astOperand2 + if var_token and var_token.variable: + vars_initialized.add(var_token.variable) + if cur_clause == 1 and tn.isName and tn.next.str == '(': + function_args_in_init = getArguments(tn.next) + function_scope = get_function_scope(cfg, tn.function) + for arg_nr in range(len(function_args_in_init)): + init_arg = function_args_in_init[arg_nr] + if init_arg is None or not init_arg.isUnaryOp('&'): + continue + var_token = init_arg.astOperand1 + while var_token and var_token.str == '.': + var_token = var_token.astOperand2 + if var_token is None or var_token.variable is None: + continue + changed = False + if function_scope is None: + changed = True + elif tn.function is None: + changed = True + else: + function_body_start = function_scope.bodyStart + function_body_end = function_scope.bodyEnd + args = tn.function.argument[arg_nr + 1] + if function_scope is None or is_variable_changed(function_body_start, function_body_end, args): + changed = True + if changed: + vars_initialized.add(var_token.variable) + + if tn.str == ';': + cur_clause += 1 + tn = tn.next + return vars_defined | vars_initialized, vars_exit & vars_modified + + +def findCounterTokens(cond): + if not cond: + return [] + if cond.str in ['&&', '||']: + c = findCounterTokens(cond.astOperand1) + c.extend(findCounterTokens(cond.astOperand2)) + return c + ret = [] + if ((cond.isArithmeticalOp and cond.astOperand1 and cond.astOperand2) or + (cond.isComparisonOp and cond.astOperand1 and cond.astOperand2)): + if cond.astOperand1.isName: + ret.append(cond.astOperand1) + if cond.astOperand2.isName: + ret.append(cond.astOperand2) + if cond.astOperand1.isOp: + ret.extend(findCounterTokens(cond.astOperand1)) + if cond.astOperand2.isOp: + ret.extend(findCounterTokens(cond.astOperand2)) + return ret + + +def isFloatCounterInWhileLoop(whileToken): + if not simpleMatch(whileToken, 'while ('): + return False + lpar = whileToken.next + rpar = lpar.link + counterTokens = findCounterTokens(lpar.astOperand2) + whileBodyStart = None + if simpleMatch(rpar, ') {'): + whileBodyStart = rpar.next + elif simpleMatch(whileToken.previous, '} while') and simpleMatch(whileToken.previous.link.previous, 'do {'): + whileBodyStart = whileToken.previous.link + else: + return False + token = whileBodyStart + while token != whileBodyStart.link: + token = token.next + for counterToken in counterTokens: + if not counterToken.valueType or not counterToken.valueType.isFloat(): + continue + if token.isAssignmentOp and token.astOperand1.str == counterToken.str: + return True + if token.str == counterToken.str and token.astParent and token.astParent.str in ('++', '--'): + return True + return False + + +def countSideEffectsRecursive(expr): + if not expr or expr.str == ';': + return 0 + if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '[': + prev = expr.astOperand1.previous + if prev and (prev.str == '{' or prev.str == '{'): + return countSideEffectsRecursive(expr.astOperand2) + if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '.': + e = expr.astOperand1 + while e and e.str == '.' and e.astOperand2: + e = e.astOperand1 + if e and e.str == '.': + return 0 + if expr.isAssignmentOp or expr.str in {'++', '--'}: + return 1 + # Todo: Check function calls + return countSideEffectsRecursive(expr.astOperand1) + countSideEffectsRecursive(expr.astOperand2) + + +def isBoolExpression(expr): + if not expr: + return False + if expr.valueType and (expr.valueType.type == 'bool' or expr.valueType.bits == 1): + return True + return expr.str in ['!', '==', '!=', '<', '<=', '>', '>=', '&&', '||', '0', '1', 'true', 'false'] + + +def isEnumConstant(expr): + if not expr or not expr.values: + return False + values = expr.values + return len(values) == 1 and values[0].valueKind == 'known' + + +def isConstantExpression(expr): + if expr.isNumber: + return True + if expr.isName and not isEnumConstant(expr): + return False + if simpleMatch(expr.previous, 'sizeof ('): + return True + if expr.astOperand1 and not isConstantExpression(expr.astOperand1): + return False + if expr.astOperand2 and not isConstantExpression(expr.astOperand2): + return False + return True + +def isUnknownConstantExpression(expr): + if expr.isName and not isEnumConstant(expr) and expr.variable is None: + return True + if expr.astOperand1 and isUnknownConstantExpression(expr.astOperand1): + return True + if expr.astOperand2 and isUnknownConstantExpression(expr.astOperand2): + return True + return False + +def isUnsignedInt(expr): + return expr and expr.valueType and expr.valueType.type in ('short', 'int') and expr.valueType.sign == 'unsigned' + + +def getPrecedence(expr): + if not expr: + return 16 + if not expr.astOperand1 or not expr.astOperand2: + return 16 + if expr.str in ('*', '/', '%'): + return 12 + if expr.str in ('+', '-'): + return 11 + if expr.str in ('<<', '>>'): + return 10 + if expr.str in ('<', '>', '<=', '>='): + return 9 + if expr.str in ('==', '!='): + return 8 + if expr.str == '&': + return 7 + if expr.str == '^': + return 6 + if expr.str == '|': + return 5 + if expr.str == '&&': + return 4 + if expr.str == '||': + return 3 + if expr.str in ('?', ':'): + return 2 + if expr.isAssignmentOp: + return 1 + if expr.str == ',': + return 0 + return -1 + + +def findRawLink(token): + tok1 = None + tok2 = None + forward = False + + if token.str in '{([': + tok1 = token.str + tok2 = '})]'['{(['.find(token.str)] + forward = True + elif token.str in '})]': + tok1 = token.str + tok2 = '{(['['})]'.find(token.str)] + forward = False + else: + return None + + # try to find link + indent = 0 + while token: + if token.str == tok1: + indent = indent + 1 + elif token.str == tok2: + if indent <= 1: + return token + indent = indent - 1 + if forward is True: + token = token.next + else: + token = token.previous + + # raw link not found + return None + + +def numberOfParentheses(tok1, tok2): + while tok1 and tok1 != tok2: + if tok1.str == '(' or tok1.str == ')': + return False + tok1 = tok1.next + return tok1 == tok2 + + +def findGotoLabel(gotoToken): + label = gotoToken.next.str + tok = gotoToken.next.next + while tok: + if tok.str == '}' and tok.scope.type == 'Function': + break + if tok.str == label and tok.next.str == ':': + return tok + tok = tok.next + return None + + +def findInclude(directives, header): + for directive in directives: + if directive.str == '#include ' + header: + return directive + return None + + +# Get function arguments +def getArgumentsRecursive(tok, arguments): + if tok is None: + return + if tok.str == ',': + getArgumentsRecursive(tok.astOperand1, arguments) + getArgumentsRecursive(tok.astOperand2, arguments) + else: + arguments.append(tok) + + +def getArguments(ftok): + arguments = [] + getArgumentsRecursive(ftok.astOperand2, arguments) + return arguments + + +def isalnum(c): + return c in string.digits or c in string.ascii_letters + + +def isHexEscapeSequence(symbols): + """Checks that given symbols are valid hex escape sequence. + + hexadecimal-escape-sequence: + \\x hexadecimal-digit + hexadecimal-escape-sequence hexadecimal-digit + + Reference: n1570 6.4.4.4""" + if len(symbols) < 3 or symbols[:2] != '\\x': + return False + return all([s in string.hexdigits for s in symbols[2:]]) + + +def isOctalEscapeSequence(symbols): + r"""Checks that given symbols are valid octal escape sequence: + + octal-escape-sequence: + \ octal-digit + \ octal-digit octal-digit + \ octal-digit octal-digit octal-digit + + Reference: n1570 6.4.4.4""" + if len(symbols) not in range(2, 5) or symbols[0] != '\\': + return False + return all([s in string.octdigits for s in symbols[1:]]) + + +def isSimpleEscapeSequence(symbols): + """Checks that given symbols are simple escape sequence. + Reference: n1570 6.4.4.4""" + if len(symbols) != 2 or symbols[0] != '\\': + return False + return symbols[1] in ("'", '"', '?', '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v') + + +def isTernaryOperator(token): + if not token: + return False + if not token.astOperand2: + return False + return token.str == '?' and token.astOperand2.str == ':' + + +def getTernaryOperandsRecursive(token): + """Returns list of ternary operands including nested ones.""" + if not isTernaryOperator(token): + return [] + result = [] + result += getTernaryOperandsRecursive(token.astOperand2.astOperand1) + if token.astOperand2.astOperand1 and not isTernaryOperator(token.astOperand2.astOperand1): + result += [token.astOperand2.astOperand1] + result += getTernaryOperandsRecursive(token.astOperand2.astOperand2) + if token.astOperand2.astOperand2 and not isTernaryOperator(token.astOperand2.astOperand2): + result += [token.astOperand2.astOperand2] + return result + + +def hasNumericEscapeSequence(symbols): + """Check that given string contains octal or hexadecimal escape sequences.""" + if '\\' not in symbols: + return False + for c, cn in grouped(symbols, 2): + if c == '\\' and cn in ('x' + string.octdigits): + return True + return False + + +def isNoReturnScope(tok): + if tok is None or tok.str != '}': + return False + if tok.previous is None or tok.previous.str != ';': + return False + if simpleMatch(tok.previous.previous, 'break ;'): + return True + prev = tok.previous.previous + while prev and prev.str not in ';{}': + if prev.str in '])': + prev = prev.link + prev = prev.previous + if prev and prev.next.str in ['throw', 'return']: + return True + return False + + +# Return the token which the value is assigned to +def getAssignedVariableToken(vartok): + if not vartok: + return None + parent = vartok.astParent + while parent and parent.isArithmeticalOp: + parent = parent.astParent + if parent and parent.isAssignmentOp: + return parent.astOperand1 + return None + +# If the value is used as a return value, return the function definition +def getFunctionUsingReturnValue(valueToken): + if not valueToken: + return None + if not valueToken.astParent: + return None + operator = valueToken.astParent + if operator.str == 'return': + return operator.scope.function + if operator.isArithmeticalOp: + return getFunctionUsingReturnValue(operator) + return None + +# Return true if the token follows a specific sequence of token str values +def tokenFollowsSequence(token, sequence): + if not token: + return False + for i in reversed(sequence): + prev = token.previous + if not prev: + return False + if prev.str != i: + return False + token = prev + return True + +class Define: + def __init__(self, directive): + self.name = '' + self.args = [] + self.expansionList = '' + + res = re.match(r'#define ([A-Za-z0-9_]+)\(([A-Za-z0-9_, ]+)\)[ ]+(.*)', directive.str) + if res: + self.name = res.group(1) + self.args = res.group(2).strip().split(',') + self.expansionList = res.group(3) + else: + res = re.match(r'#define ([A-Za-z0-9_]+)[ ]+(.*)', directive.str) + if res: + self.name = res.group(1) + self.expansionList = res.group(2) + + def __repr__(self): + attrs = ["name", "args", "expansionList"] + return "{}({})".format( + "Define", + ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) + ) + + +def getAddonRules(): + """Returns dict of MISRA rules handled by this addon.""" + addon_rules = [] + compiled = re.compile(r'.*def[ ]+misra_([0-9]+)_([0-9]+)[(].*') + with open(__file__) as f: + for line in f: + res = compiled.match(line) + if res is None: + continue + addon_rules.append(res.group(1) + '.' + res.group(2)) + return addon_rules + + +def getCppcheckRules(): + """Returns list of rules handled by cppcheck.""" + return ['1.3', # + '2.1', # alwaysFalse, duplicateBreak + '2.2', # alwaysTrue, redundantCondition, redundantAssignment, redundantAssignInSwitch, unreadVariable + '2.6', # unusedLabel + '5.3', # shadowVariable + '8.3', # funcArgNamesDifferent + '8.13', # constPointer + '9.1', # uninitvar + '14.3', # alwaysTrue, alwaysFalse, compareValueOutOfTypeRangeError + '13.2', # unknownEvaluationOrder + '13.6', # sizeofCalculation + '17.4', # missingReturn + '17.5', # argumentSize + '18.1', # pointerOutOfBounds + '18.2', # comparePointers + '18.3', # comparePointers + '18.6', # danglingLifetime + '19.1', # overlappingWriteUnion, overlappingWriteFunction + '20.6', # preprocessorErrorDirective + '21.13', # invalidFunctionArg + '21.17', # bufferAccessOutOfBounds + '21.18', # bufferAccessOutOfBounds + '22.1', # memleak, resourceLeak, memleakOnRealloc, leakReturnValNotUsed, leakNoVarFunctionCall + '22.2', # autovarInvalidDeallocation + '22.3', # incompatibleFileOpen + '22.4', # writeReadOnlyFile + '22.6' # useClosedFile + ] + + +def generateTable(): + # print table + numberOfRules = {} + numberOfRules[1] = 3 + numberOfRules[2] = 7 + numberOfRules[3] = 2 + numberOfRules[4] = 2 + numberOfRules[5] = 9 + numberOfRules[6] = 2 + numberOfRules[7] = 4 + numberOfRules[8] = 14 + numberOfRules[9] = 5 + numberOfRules[10] = 8 + numberOfRules[11] = 9 + numberOfRules[12] = 4 + numberOfRules[13] = 6 + numberOfRules[14] = 4 + numberOfRules[15] = 7 + numberOfRules[16] = 7 + numberOfRules[17] = 8 + numberOfRules[18] = 8 + numberOfRules[19] = 2 + numberOfRules[20] = 14 + numberOfRules[21] = 21 + numberOfRules[22] = 10 + + # Rules that can be checked with compilers: + # compiler = ['1.1', '1.2'] + + addon = getAddonRules() + cppcheck = getCppcheckRules() + for i1 in range(1, 23): + for i2 in range(1, numberOfRules[i1] + 1): + num = str(i1) + '.' + str(i2) + s = '' + if num in addon: + s = 'X (Addon)' + elif num in cppcheck: + s = 'X (Cppcheck)' + num = num + ' ' + print(num[:8] + s) + + +def remove_file_prefix(file_path, prefix): + """ + Remove a file path prefix from a give path. leftover + directory separators at the beginning of a file + after the removal are also stripped. + + Example: + '/remove/this/path/file.c' + with a prefix of: + '/remove/this/path' + becomes: + file.c + """ + result = None + if file_path.startswith(prefix): + result = file_path[len(prefix):] + # Remove any leftover directory separators at the + # beginning + result = result.lstrip('\\/') + else: + result = file_path + return result + + +class Rule(object): + """Class to keep rule text and metadata""" + + MISRA_SEVERITY_LEVELS = ['Required', 'Mandatory', 'Advisory'] + + def __init__(self, num1, num2): + self.num1 = num1 + self.num2 = num2 + self.text = '' + self.misra_severity = '' + + @property + def num(self): + return self.num1 * 100 + self.num2 + + @property + def misra_severity(self): + return self._misra_severity + + @misra_severity.setter + def misra_severity(self, val): + if val in self.MISRA_SEVERITY_LEVELS: + self._misra_severity = val + else: + self._misra_severity = '' + + @property + def cppcheck_severity(self): + return 'style' + + def __repr__(self): + return "%d.%d (%s)" % (self.num1, self.num2, self.misra_severity) + + +class MisraSettings(object): + """Hold settings for misra.py script.""" + + __slots__ = ["verify", "quiet", "show_summary"] + + def __init__(self, args): + """ + :param args: Arguments given by argparse. + """ + self.verify = False + self.quiet = False + self.show_summary = True + + if args.verify: + self.verify = True + if args.cli: + self.quiet = True + self.show_summary = False + if args.quiet: + self.quiet = True + if args.no_summary: + self.show_summary = False + + def __repr__(self): + attrs = ["verify", "quiet", "show_summary", "verify"] + return "{}({})".format( + "MisraSettings", + ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) + ) + + +class MisraChecker: + + def __init__(self, settings, stdversion="c89"): + """ + :param settings: misra.py script settings. + """ + + self.settings = settings + + # Test validation rules lists + self.verify_expected = list() + self.verify_actual = list() + + # List of formatted violation messages + self.violations = dict() + + # if --rule-texts is specified this dictionary + # is loaded with descriptions of each rule + # by rule number (in hundreds). + # ie rule 1.2 becomes 102 + self.ruleTexts = dict() + self.ruleText_filename = None + + # Dictionary of dictionaries for rules to suppress + # Dict1 is keyed by rule number in the hundreds format of + # Major * 100 + minor. ie Rule 5.2 = (5*100) + 2 + # Dict 2 is keyed by filename. An entry of None means suppress globally. + # Each file name entry contains a list of tuples of (lineNumber, symbolName) + # or an item of None which indicates suppress rule for the entire file. + # The line and symbol name tuple may have None as either of its elements but + # should not be None for both. + self.suppressedRules = dict() + + # Prefix to ignore when matching suppression files. + self.filePrefix = None + + # Number of all violations suppressed per rule + self.suppressionStats = dict() + + self.stdversion = stdversion + + self.severity = None + + self.existing_violations = set() + + self._ctu_summary_typedefs = False + self._ctu_summary_tagnames = False + self._ctu_summary_identifiers = False + self._ctu_summary_usage = False + + self.path_premium_addon = None + + def __repr__(self): + attrs = ["settings", "verify_expected", "verify_actual", "violations", + "ruleTexts", "suppressedRules", "filePrefix", + "suppressionStats", "stdversion", "severity"] + return "{}({})".format( + "MisraChecker", + ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) + ) + + def get_num_significant_naming_chars(self, cfg): + if cfg.standards and cfg.standards.c == "c89": + return 31 + else: + return 63 + + def _save_ctu_summary_typedefs(self, dumpfile, typedef_info): + if self._ctu_summary_typedefs: + return + + self._ctu_summary_typedefs = True + + summary = [] + for ti in typedef_info: + summary.append({ 'name': ti.name, 'file': ti.file, 'line': ti.linenr, 'column': ti.column, 'used': ti.used }) + if len(summary) > 0: + cppcheckdata.reportSummary(dumpfile, 'MisraTypedefInfo', summary) + + def _save_ctu_summary_tagnames(self, dumpfile, cfg): + if self._ctu_summary_tagnames: + return + + self._ctu_summary_tagnames = True + + summary = [] + # structs/enums + for scope in cfg.scopes: + if scope.className is None: + continue + if scope.className.startswith('Anonymous'): + continue + if scope.type not in ('Struct', 'Enum'): + continue + used = False + tok = scope.bodyEnd + while tok: + if tok.str == scope.className: + used = True + break + tok = tok.next + summary.append({'name': scope.className, 'used':used, 'file': scope.bodyStart.file, 'line': scope.bodyStart.linenr, 'column': scope.bodyStart.column}) + if len(summary) > 0: + cppcheckdata.reportSummary(dumpfile, 'MisraTagName', summary) + + def _save_ctu_summary_identifiers(self, dumpfile, cfg): + if self._ctu_summary_identifiers: + return + self._ctu_summary_identifiers = True + + external_identifiers = [] + internal_identifiers = [] + local_identifiers = [] + + def identifier(nameToken): + return {'name':nameToken.str, 'file':nameToken.file, 'line':nameToken.linenr, 'column':nameToken.column} + + names = [] + + for var in cfg.variables: + if var.nameToken is None: + continue + if var.access != 'Global': + if var.nameToken.str in names: + continue + names.append(var.nameToken.str) + local_identifiers.append(identifier(var.nameToken)) + elif var.isStatic: + names.append(var.nameToken.str) + i = identifier(var.nameToken) + i['inlinefunc'] = False + internal_identifiers.append(i) + else: + names.append(var.nameToken.str) + i = identifier(var.nameToken) + i['decl'] = var.isExtern + external_identifiers.append(i) + + for func in cfg.functions: + if func.tokenDef is None: + continue + if func.isStatic: + i = identifier(func.tokenDef) + i['inlinefunc'] = func.isInlineKeyword + internal_identifiers.append(i) + else: + if func.token is None: + i = identifier(func.tokenDef) + else: + i = identifier(func.token) + i['decl'] = func.token is None + external_identifiers.append(i) + + cppcheckdata.reportSummary(dumpfile, 'MisraExternalIdentifiers', external_identifiers) + cppcheckdata.reportSummary(dumpfile, 'MisraInternalIdentifiers', internal_identifiers) + cppcheckdata.reportSummary(dumpfile, 'MisraLocalIdentifiers', local_identifiers) + + def _save_ctu_summary_usage(self, dumpfile, cfg): + if self._ctu_summary_usage: + return + self._ctu_summary_usage = True + + names = [] + for token in cfg.tokenlist: + if not token.isName: + continue + if token.function and token != token.function.tokenDef: + if (not token.function.isStatic) and (token.str not in names): + names.append({'name': token.str, 'file': token.file}) + elif token.variable: + if token == token.variable.nameToken: + continue + if token.variable.access == 'Global' and (not token.variable.isStatic) and (token.str not in names): + names.append({'name': token.str, 'file': token.file}) + + if len(names) > 0: + cppcheckdata.reportSummary(dumpfile, 'MisraUsage', names) + + + def misra_1_2(self, cfg): + # gcc language extensions: https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html + for token in cfg.tokenlist: + if simpleMatch(token, '? :'): + self.reportError(token, 1, 2) + elif simpleMatch(token, '( {') and simpleMatch(token.next.link.previous, '; } )'): + self.reportError(token, 1, 2) + + + def misra_1_4(self, cfg): + for token in cfg.tokenlist: + if token.str in ('_Atomic', '_Noreturn', '_Generic', '_Thread_local', '_Alignas', '_Alignof'): + self.reportError(token, 1, 4) + if token.str.endswith('_s') and isFunctionCall(token.next): + # See C specification C11 - Annex K, page 578 + if token.str in ('tmpfile_s', 'tmpnam_s', 'fopen_s', 'freopen_s', 'fprintf_s', 'fscanf_s', 'printf_s', 'scanf_s', + 'snprintf_s', 'sprintf_s', 'sscanf_s', 'vfprintf_s', 'vfscanf_s', 'vprintf_s', 'vscanf_s', + 'vsnprintf_s', 'vsprintf_s', 'vsscanf_s', 'gets_s', 'set_constraint_handler_s', 'abort_handler_s', + 'ignore_handler_s', 'getenv_s', 'bsearch_s', 'qsort_s', 'wctomb_s', 'mbstowcs_s', 'wcstombs_s', + 'memcpy_s', 'memmove_s', 'strcpy_s', 'strncpy_s', 'strcat_s', 'strncat_s', 'strtok_s', 'memset_s', + 'strerror_s', 'strerrorlen_s', 'strnlen_s', 'asctime_s', 'ctime_s', 'gmtime_s', 'localtime_s', + 'fwprintf_s', 'fwscanf_s', 'snwprintf_s', 'swprintf_s', 'swscanf_s', 'vfwprintf_s', 'vfwscanf_s', + 'vsnwprintf_s', 'vswprintf_s', 'vswscanf_s', 'vwprintf_s', 'vwscanf_s', 'wprintf_s', 'wscanf_s', + 'wcscpy_s', 'wcsncpy_s', 'wmemcpy_s', 'wmemmove_s', 'wcscat_s', 'wcsncat_s', 'wcstok_s', 'wcsnlen_s', + 'wcrtomb_s', 'mbsrtowcs_s', 'wcsrtombs_s'): + self.reportError(token, 1, 4) + + def misra_2_2(self, cfg): + for token in cfg.tokenlist: + if token.isExpandedMacro: + continue + if (token.str in '+-') and token.astOperand2: + if simpleMatch(token.astOperand1, '0'): + self.reportError(token.astOperand1, 2, 2) + elif simpleMatch(token.astOperand2, '0'): + self.reportError(token.astOperand2, 2, 2) + if token.str == '*' and token.astOperand2: + if simpleMatch(token.astOperand2, '0'): + self.reportError(token.astOperand1, 2, 2) + elif simpleMatch(token.astOperand1, '0'): + self.reportError(token.astOperand2, 2, 2) + elif simpleMatch(token.astOperand1, '1'): + self.reportError(token.astOperand1, 2, 2) + elif simpleMatch(token.astOperand2, '1'): + self.reportError(token.astOperand2, 2, 2) + + def misra_2_3(self, dumpfile, typedefInfo): + self._save_ctu_summary_typedefs(dumpfile, typedefInfo) + + def misra_2_4(self, dumpfile, cfg): + self._save_ctu_summary_tagnames(dumpfile, cfg) + + def misra_2_5(self, dumpfile, cfg): + used_macros = list() + for m in cfg.macro_usage: + used_macros.append(m.name) + summary = [] + for directive in cfg.directives: + res = re.match(r'#define[ \t]+([a-zA-Z_][a-zA-Z_0-9]*).*', directive.str) + if res: + macro_name = res.group(1) + summary.append({'name': macro_name, 'used': (macro_name in used_macros), 'file': directive.file, 'line': directive.linenr, 'column': directive.column}) + if len(summary) > 0: + cppcheckdata.reportSummary(dumpfile, 'MisraMacro', summary) + + def misra_2_7(self, data): + for func in data.functions: + # Skip function with no parameter + if len(func.argument) == 0: + continue + # Setup list of function parameters + func_param_list = list() + for arg in func.argument: + func_arg = func.argument[arg] + if func_arg.typeStartToken and func_arg.typeStartToken.str == '...': + continue + func_param_list.append(func_arg) + # Search for scope of current function + for scope in data.scopes: + if (scope.type == "Function") and (scope.function == func): + # Search function body: remove referenced function parameter from list + token = scope.bodyStart + while token.next is not None and token != scope.bodyEnd and len(func_param_list) > 0: + if token.variable is not None and token.variable in func_param_list: + func_param_list.remove(token.variable) + token = token.next + # Emit a warning for each unused variable, but no more that one warning per line + reported_linenrs = set() + for func_param in func_param_list: + if func_param.nameToken: + linenr = func_param.nameToken + if linenr not in reported_linenrs: + self.reportError(func_param.nameToken, 2, 7) + reported_linenrs.add(linenr) + else: + linenr = func.tokenDef.linenr + if linenr not in reported_linenrs: + self.reportError(func.tokenDef, 2, 7) + reported_linenrs.add(linenr) + + def misra_3_1(self, rawTokens): + for token in rawTokens: + starts_with_double_slash = token.str.startswith('//') + starts_with_block_comment = token.str.startswith("/*") + s = token.str.lstrip('/') + if (starts_with_double_slash or starts_with_block_comment) and "/*" in s: + # Block comment inside of regular comment, violation + self.reportError(token, 3, 1) + elif starts_with_block_comment and "//" in s: + # "//" in block comment, check if it's a uri + while "//" in s: + possible_uri, s = s.split("//", 1) + if not re.search(r"\w+:$", possible_uri): + # Violation if no uri was found + self.reportError(token, 3, 1) + break + + def misra_3_2(self, rawTokens): + for token in rawTokens: + if token.str.startswith('//'): + # Check for comment ends with trigraph which might be replaced + # by a backslash. + if token.str.endswith('??/'): + self.reportError(token, 3, 2) + # Check for comment which has been merged with subsequent line + # because it ends with backslash. + # The last backslash is no more part of the comment token thus + # check if next token exists and compare line numbers. + elif (token.next is not None) and (token.linenr == token.next.linenr): + self.reportError(token, 3, 2) + + def misra_4_1(self, rawTokens): + for token in rawTokens: + if (token.str[0] != '"') and (token.str[0] != '\''): + continue + if len(token.str) < 3: + continue + + delimiter = token.str[0] + symbols = token.str[1:-1] + + # No closing delimiter. This will not compile. + if token.str[-1] != delimiter: + continue + + if len(symbols) < 2: + continue + + if not hasNumericEscapeSequence(symbols): + continue + + # String literals that contains one or more escape sequences. All of them should be + # terminated. + for sequence in ['\\' + t for t in symbols.split('\\')][1:]: + if (isHexEscapeSequence(sequence) or isOctalEscapeSequence(sequence) or + isSimpleEscapeSequence(sequence)): + continue + else: + self.reportError(token, 4, 1) + + def misra_4_2(self, rawTokens): + for token in rawTokens: + if (token.str[0] != '"') or (token.str[-1] != '"'): + continue + # Check for trigraph sequence as defined by ISO/IEC 9899:1999 + for sequence in ['??=', '??(', '??/', '??)', '??\'', '??<', '??!', '??>', '??-']: + if sequence in token.str[1:-1]: + # First trigraph sequence match, report error and leave loop. + self.reportError(token, 4, 2) + break + + def misra_5_1(self, data): + long_vars = {} + num_sign_chars = self.get_num_significant_naming_chars(data) + for var in data.variables: + if var.nameToken is None: + continue + if len(var.nameToken.str) <= num_sign_chars: + continue + if not hasExternalLinkage(var): + continue + long_vars.setdefault(var.nameToken.str[:num_sign_chars], []).append(var.nameToken) + for name_prefix in long_vars: + tokens = long_vars[name_prefix] + if len(tokens) < 2: + continue + for tok in sorted(tokens, key=lambda t: (t.linenr, t.column))[1:]: + self.reportError(tok, 5, 1) + + def misra_5_2(self, data): + scopeVars = {} + num_sign_chars = self.get_num_significant_naming_chars(data) + for var in data.variables: + if var.nameToken is None: + continue + if len(var.nameToken.str) <= num_sign_chars: + continue + if var.nameToken.scope not in scopeVars: + scopeVars.setdefault(var.nameToken.scope, {})["varlist"] = [] + scopeVars.setdefault(var.nameToken.scope, {})["scopelist"] = [] + scopeVars[var.nameToken.scope]["varlist"].append(var) + for scope in data.scopes: + if scope.nestedIn and scope.className: + if scope.nestedIn not in scopeVars: + scopeVars.setdefault(scope.nestedIn, {})["varlist"] = [] + scopeVars.setdefault(scope.nestedIn, {})["scopelist"] = [] + scopeVars[scope.nestedIn]["scopelist"].append(scope) + for scope in scopeVars: + if len(scopeVars[scope]["varlist"]) <= 1: + continue + for i, variable1 in enumerate(scopeVars[scope]["varlist"]): + for variable2 in scopeVars[scope]["varlist"][i + 1:]: + if variable1.isArgument and variable2.isArgument: + continue + if hasExternalLinkage(variable1) or hasExternalLinkage(variable2): + continue + if (variable1.nameToken.str[:num_sign_chars] == variable2.nameToken.str[:num_sign_chars] and + variable1 is not variable2): + if int(variable1.nameToken.linenr) > int(variable2.nameToken.linenr): + self.reportError(variable1.nameToken, 5, 2) + else: + self.reportError(variable2.nameToken, 5, 2) + for innerscope in scopeVars[scope]["scopelist"]: + if variable1.nameToken.str[:num_sign_chars] == innerscope.className[:num_sign_chars]: + if int(variable1.nameToken.linenr) > int(innerscope.bodyStart.linenr): + self.reportError(variable1.nameToken, 5, 2) + else: + self.reportError(innerscope.bodyStart, 5, 2) + if len(scopeVars[scope]["scopelist"]) <= 1: + continue + for i, scopename1 in enumerate(scopeVars[scope]["scopelist"]): + for scopename2 in scopeVars[scope]["scopelist"][i + 1:]: + if scopename1.className[:num_sign_chars] == scopename2.className[:num_sign_chars]: + if int(scopename1.bodyStart.linenr) > int(scopename2.bodyStart.linenr): + self.reportError(scopename1.bodyStart, 5, 2) + else: + self.reportError(scopename2.bodyStart, 5, 2) + + def misra_5_4(self, data): + num_sign_chars = self.get_num_significant_naming_chars(data) + macro = {} + compile_name = re.compile(r'#define ([a-zA-Z0-9_]+)') + compile_param = re.compile(r'#define ([a-zA-Z0-9_]+)[(]([a-zA-Z0-9_, ]+)[)]') + short_names = {} + macro_w_arg = [] + for dir in data.directives: + res1 = compile_name.match(dir.str) + if res1: + if dir not in macro: + macro.setdefault(dir, {})["name"] = [] + macro.setdefault(dir, {})["params"] = [] + full_name = res1.group(1) + macro[dir]["name"] = full_name + short_name = full_name[:num_sign_chars] + if short_name in short_names: + _dir = short_names[short_name] + if full_name != macro[_dir]["name"]: + self.reportError(dir, 5, 4) + else: + short_names[short_name] = dir + res2 = compile_param.match(dir.str) + if res2: + res_gp2 = res2.group(2).split(",") + res_gp2 = [macroname.replace(" ", "") for macroname in res_gp2] + macro[dir]["params"].extend(res_gp2) + macro_w_arg.append(dir) + for mvar in macro_w_arg: + for i, macroparam1 in enumerate(macro[mvar]["params"]): + for j, macroparam2 in enumerate(macro[mvar]["params"]): + if j > i and macroparam1[:num_sign_chars] == macroparam2[:num_sign_chars]: + self.reportError(mvar, 5, 4) + param = macroparam1 + if param[:num_sign_chars] in short_names: + m_var1 = short_names[param[:num_sign_chars]] + if m_var1.linenr > mvar.linenr: + self.reportError(m_var1, 5, 4) + else: + self.reportError(mvar, 5, 4) + + def misra_5_5(self, data): + num_sign_chars = self.get_num_significant_naming_chars(data) + macroNames = {} + compiled = re.compile(r'#define ([A-Za-z0-9_]+)') + for dir in data.directives: + res = compiled.match(dir.str) + if res: + macroNames[res.group(1)[:num_sign_chars]] = dir + for var in data.variables: + if var.nameToken and var.nameToken.str[:num_sign_chars] in macroNames: + self.reportError(var.nameToken, 5, 5) + for scope in data.scopes: + if scope.className and scope.className[:num_sign_chars] in macroNames: + self.reportError(scope.bodyStart, 5, 5) + + + def misra_5_6(self, dumpfile, typedefInfo): + self._save_ctu_summary_typedefs(dumpfile, typedefInfo) + + def misra_5_7(self, dumpfile, cfg): + self._save_ctu_summary_tagnames(dumpfile, cfg) + + def misra_5_8(self, dumpfile, cfg): + self._save_ctu_summary_identifiers(dumpfile, cfg) + + def misra_5_9(self, dumpfile, cfg): + self._save_ctu_summary_identifiers(dumpfile, cfg) + + def misra_6_1(self, data): + # Bitfield type must be bool or explicitly signed/unsigned int + for token in data.tokenlist: + if not token.valueType: + continue + if token.valueType.bits == 0: + continue + if not token.variable: + continue + if not token.scope: + continue + if token.scope.type not in 'Struct': + continue + + if data.standards.c == 'c89': + if token.valueType.type != 'int' and not isUnsignedType(token.variable.typeStartToken.str): + self.reportError(token, 6, 1) + elif data.standards.c == 'c99': + if token.valueType.type == 'bool': + continue + + isExplicitlySignedOrUnsigned = False + typeToken = token.variable.typeStartToken + while typeToken: + if typeToken.isUnsigned or typeToken.isSigned or isUnsignedType(typeToken.str): + isExplicitlySignedOrUnsigned = True + break + + if typeToken is token.variable.typeEndToken: + break + + typeToken = typeToken.next + + if not isExplicitlySignedOrUnsigned: + self.reportError(token, 6, 1) + + + def misra_6_2(self, data): + # Bitfields of size 1 can not be signed + for token in data.tokenlist: + if not token.valueType: + continue + if not token.scope: + continue + if token.scope.type not in 'Struct': + continue + if token.valueType.bits == 1 and token.valueType.sign == 'signed': + self.reportError(token, 6, 2) + + + def misra_7_1(self, rawTokens): + compiled = re.compile(r'^0[0-7]+$') + for tok in rawTokens: + if compiled.match(tok.str): + self.reportError(tok, 7, 1) + + def misra_7_2(self, data): + for token in data.tokenlist: + if token.isInt and ('U' not in token.str.upper()) and token.valueType and token.valueType.sign == 'unsigned': + self.reportError(token, 7, 2) + + def misra_7_3(self, rawTokens): + compiled = re.compile(r'^[0-9.]+[Uu]*l+[Uu]*$') + for tok in rawTokens: + if compiled.match(tok.str): + self.reportError(tok, 7, 3) + + def misra_7_4(self, data): + # A string literal shall not be assigned to an object unless the object's type + # is constant. + def reportErrorIfVariableIsNotConst(variable, stringLiteral): + if variable.valueType: + if (variable.valueType.constness % 2) != 1: + self.reportError(stringLiteral, 7, 4) + + for token in data.tokenlist: + if token.isString: + # Check normal variable assignment + variable = getAssignedVariableToken(token) + if variable: + reportErrorIfVariableIsNotConst(variable, token) + + # Check use as return value + function = getFunctionUsingReturnValue(token) + if function: + # "Primitive" test since there is no info available on return value type + if not tokenFollowsSequence(function.tokenDef, ['const', 'char', '*']): + self.reportError(token, 7, 4) + + # Check use as function parameter + if isFunctionCall(token) and token.astOperand1 and token.astOperand1.function: + functionDeclaration = token.astOperand1.function + + if functionDeclaration.tokenDef: + if functionDeclaration.tokenDef is token.astOperand1: + # Token is not a function call, but it is the definition of the function + continue + + parametersUsed = getArguments(token) + for i in range(len(parametersUsed)): + usedParameter = parametersUsed[i] + parameterDefinition = functionDeclaration.argument.get(i+1) + + if usedParameter.isString and parameterDefinition and parameterDefinition.nameToken: + reportErrorIfVariableIsNotConst(parameterDefinition.nameToken, usedParameter) + + def misra_8_1(self, cfg): + for token in cfg.tokenlist: + if token.isImplicitInt and not token.isUnsigned and not token.isSigned: + self.reportError(token, 8, 1) + + def misra_8_2(self, data, rawTokens): + def getFollowingRawTokens(rawTokens, token, count): + following =[] + for rawToken in rawTokens: + if (rawToken.file == token.file and + rawToken.linenr == token.linenr and + rawToken.column == token.column): + for _ in range(count): + rawToken = rawToken.next + # Skip comments + while rawToken and (rawToken.str.startswith('/*') or rawToken.str.startswith('//')): + rawToken = rawToken.next + if rawToken is None: + break + following.append(rawToken) + return following + + # Zero arguments should be in form ( void ) + def checkZeroArguments(func, startCall, endCall): + if not startCall.isRemovedVoidParameter and len(func.argument) == 0: + if func.tokenDef.next: + self.reportError(func.tokenDef.next, 8, 2) + else: + self.reportError(func.tokenDef, 8, 2) + + def checkDeclarationArgumentsViolations(func, startCall, endCall): + # Collect the tokens for the arguments in function definition + argNameTokens = set() + for arg in func.argument: + argument = func.argument[arg] + typeStartToken = argument.typeStartToken + if typeStartToken is None: + continue + nameToken = argument.nameToken + if nameToken is None: + continue + argNameTokens.add(nameToken) + + # Check if we have the same number of variables in both the + # declaration and the definition. + # + # TODO: We actually need to check if the names of the arguments are + # the same. But we can't do this because we have no links to + # variables in the arguments in function definition in the dump file. + foundVariables = 0 + while startCall and startCall != endCall: + if startCall.varId: + foundVariables += 1 + startCall = startCall.next + + if len(argNameTokens) != foundVariables: + if func.tokenDef.next: + self.reportError(func.tokenDef.next, 8, 2) + else: + self.reportError(func.tokenDef, 8, 2) + + def checkDefinitionArgumentsViolations(func, startCall, endCall): + for arg in func.argument: + argument = func.argument[arg] + typeStartToken = argument.typeStartToken + if typeStartToken is None: + continue + + # Arguments should have a name unless variable length arg + nameToken = argument.nameToken + if nameToken is None and typeStartToken.str != '...': + self.reportError(typeStartToken, 8, 2) + + # Type declaration on next line (old style declaration list) is not allowed + if typeStartToken.linenr > endCall.linenr: + self.reportError(typeStartToken, 8, 2) + + # Check arguments in function declaration + for func in data.functions: + + # Check arguments in function definition + tokenImpl = func.token + if tokenImpl: + startCall = tokenImpl.next + if startCall is None or startCall.str != '(': + continue + endCall = startCall.link + if endCall is None or endCall.str != ')': + continue + checkZeroArguments(func, startCall, endCall) + checkDefinitionArgumentsViolations(func, startCall, endCall) + + # Check arguments in function declaration + tokenDef = func.tokenDef + if tokenDef: + startCall = func.tokenDef.next + if startCall is None or startCall.str != '(': + continue + endCall = startCall.link + if endCall is None or endCall.str != ')': + continue + checkZeroArguments(func, startCall, endCall) + if tokenImpl: + checkDeclarationArgumentsViolations(func, startCall, endCall) + else: + # When there is no function definition, we should execute + # its checks for the declaration token. The point is that without + # a known definition we have no Function.argument list required + # for declaration check. + checkDefinitionArgumentsViolations(func, startCall, endCall) + + # Check arguments in pointer declarations + for var in data.variables: + if not var.isPointer: + continue + + if var.nameToken is None: + continue + + rawTokensFollowingPtr = getFollowingRawTokens(rawTokens, var.nameToken, 3) + if len(rawTokensFollowingPtr) != 3: + continue + + # Compliant: returnType (*ptrName) ( ArgType ) + # Non-compliant: returnType (*ptrName) ( ) + if (rawTokensFollowingPtr[0].str == ')' and + rawTokensFollowingPtr[1].str == '(' and + rawTokensFollowingPtr[2].str == ')'): + self.reportError(var.nameToken, 8, 2) + + + def misra_8_4(self, cfg): + for func in cfg.functions: + if func.isStatic: + continue + if func.token is None: + continue + if not is_source_file(func.token.file): + continue + if func.token != func.tokenDef: + continue + if func.tokenDef.str == 'main': + continue + self.reportError(func.tokenDef, 8, 4) + + extern_vars = [] + var_defs = [] + + for var in cfg.variables: + if not var.isGlobal: + continue + if var.isStatic: + continue + if var.nameToken is None: + continue + if var.isExtern: + extern_vars.append(var.nameToken.str) + else: + var_defs.append(var.nameToken) + for vartok in var_defs: + if vartok.str not in extern_vars: + self.reportError(vartok, 8, 4) + + def misra_8_5(self, dumpfile, cfg): + self._save_ctu_summary_identifiers(dumpfile, cfg) + + def misra_8_6(self, dumpfile, cfg): + self._save_ctu_summary_identifiers(dumpfile, cfg) + + def misra_8_7(self, dumpfile, cfg): + self._save_ctu_summary_usage(dumpfile, cfg) + + def misra_8_8(self, cfg): + vars = {} + for var in cfg.variables: + if var.access != 'Global': + continue + if var.nameToken is None: + continue + varname = var.nameToken.str + if varname in vars: + vars[varname].append(var) + else: + vars[varname] = [var] + for varname, varlist in vars.items(): + static_var = None + extern_var = None + for var in varlist: + if var.isStatic: + static_var = var + elif var.isExtern: + extern_var = var + if static_var and extern_var: + self.reportError(extern_var.nameToken, 8, 8) + + def misra_8_9(self, cfg): + variables = {} + for scope in cfg.scopes: + if scope.type != 'Function': + continue + variables_used_in_scope = [] + tok = scope.bodyStart + while tok != scope.bodyEnd: + if tok.variable and tok.variable.access == 'Global' and tok.variable.isStatic: + if tok.variable not in variables_used_in_scope: + variables_used_in_scope.append(tok.variable) + tok = tok.next + for var in variables_used_in_scope: + if var in variables: + variables[var] += 1 + else: + variables[var] = 1 + for var, count in variables.items(): + if count == 1: + self.reportError(var.nameToken, 8, 9) + + + def misra_8_10(self, cfg): + for func in cfg.functions: + if func.isInlineKeyword and not func.isStatic: + self.reportError(func.tokenDef, 8, 10) + + def misra_8_11(self, data): + for var in data.variables: + if var.isExtern and simpleMatch(var.nameToken.next, '[ ]') and var.nameToken.scope.type == 'Global': + self.reportError(var.nameToken, 8, 11) + + def misra_8_12(self, data): + for scope in data.scopes: + if scope.type != 'Enum': + continue + enum_values = [] + implicit_enum_values = [] + e_token = scope.bodyStart.next + while e_token != scope.bodyEnd: + if e_token.str == '(': + e_token = e_token.link + continue + if e_token.previous.str not in ',{': + e_token = e_token.next + continue + if e_token.isName and e_token.values and e_token.valueType and e_token.valueType.typeScope == scope: + token_values = [v.intvalue for v in e_token.values] + enum_values += token_values + if e_token.next.str != "=": + implicit_enum_values += token_values + e_token = e_token.next + for implicit_enum_value in implicit_enum_values: + if enum_values.count(implicit_enum_value) != 1: + self.reportError(scope.bodyStart, 8, 12) + + def misra_8_14(self, rawTokens): + for token in rawTokens: + if token.str == 'restrict': + self.reportError(token, 8, 14) + + def misra_9_2(self, data): + misra_9.misra_9_x(self, data, 902) + + def misra_9_3(self, data): + misra_9.misra_9_x(self, data, 903) + + def misra_9_4(self, data): + misra_9.misra_9_x(self, data, 904) + + def misra_9_5(self, data, rawTokens): + misra_9.misra_9_x(self, data, 905, rawTokens) + #for token in rawTokens: + # if simpleMatch(token, '[ ] = { ['): + # self.reportError(token, 9, 5) + + def misra_10_1(self, data): + for token in data.tokenlist: + if not token.isOp: + continue + + for t1, t2 in itertools.product( + list(getTernaryOperandsRecursive(token.astOperand1) or [token.astOperand1]), + list(getTernaryOperandsRecursive(token.astOperand2) or [token.astOperand2]), + ): + e1 = getEssentialTypeCategory(t1) + e2 = getEssentialTypeCategory(t2) + if not e1 or not e2: + continue + if token.str in ('<<', '>>'): + if not isUnsignedType(e1): + self.reportError(token, 10, 1) + elif not isUnsignedType(e2) and not token.astOperand2.isNumber: + self.reportError(token, 10, 1) + elif token.str in ('~', '&', '|', '^'): + e1_et = getEssentialType(token.astOperand1) + e2_et = getEssentialType(token.astOperand2) + if e1_et == 'char' or e2_et == 'char': + self.reportError(token, 10, 1) + + def misra_10_2(self, data): + def isEssentiallySignedOrUnsigned(op): + e = getEssentialType(op) + return e and (e.split(' ')[0] in ('unsigned', 'signed')) + + def isEssentiallyChar(op): + if op is None: + return False + if op.str == '+': + return isEssentiallyChar(op.astOperand1) or isEssentiallyChar(op.astOperand2) + return op.isChar + + for token in data.tokenlist: + if token.str not in ('+', '-'): + continue + + if (not isEssentiallyChar(token.astOperand1)) and (not isEssentiallyChar(token.astOperand2)): + continue + + if token.str == '+': + if isEssentiallyChar(token.astOperand1) and not isEssentiallySignedOrUnsigned(token.astOperand2): + self.reportError(token, 10, 2) + if isEssentiallyChar(token.astOperand2) and not isEssentiallySignedOrUnsigned(token.astOperand1): + self.reportError(token, 10, 2) + + if token.str == '-': + e1 = getEssentialType(token.astOperand1) + if e1 and e1.split(' ')[-1] != 'char': + self.reportError(token, 10, 2) + if not isEssentiallyChar(token.astOperand2) and not isEssentiallySignedOrUnsigned(token.astOperand2): + self.reportError(token, 10, 2) + + def misra_10_3(self, cfg): + def get_category(essential_type): + if essential_type: + if essential_type in ('bool', 'char'): + return essential_type + if essential_type.split(' ')[-1] in ('float', 'double'): + return 'floating' + if essential_type.split(' ')[0] in ('unsigned', 'signed'): + return essential_type.split(' ')[0] + return None + for tok in cfg.tokenlist: + if tok.isAssignmentOp: + lhs = getEssentialType(tok.astOperand1) + rhs = getEssentialType(tok.astOperand2) + #print(lhs) + #print(rhs) + if lhs is None or rhs is None: + continue + lhs_category = get_category(lhs) + rhs_category = get_category(rhs) + if lhs_category and rhs_category and lhs_category != rhs_category and rhs_category not in ('signed','unsigned'): + self.reportError(tok, 10, 3) + if bitsOfEssentialType(lhs) < bitsOfEssentialType(rhs): + self.reportError(tok, 10, 3) + + + def misra_10_4(self, data): + op = {'+', '-', '*', '/', '%', '&', '|', '^', '+=', '-=', ':'} + for token in data.tokenlist: + if token.str not in op and not token.isComparisonOp: + continue + if not token.astOperand1 or not token.astOperand2: + continue + if not token.astOperand1.valueType or not token.astOperand2.valueType: + continue + if ((token.astOperand1.str in op or token.astOperand1.isComparisonOp) and + (token.astOperand2.str in op or token.astOperand2.isComparisonOp)): + e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2.astOperand1) + elif token.astOperand1.str in op or token.astOperand1.isComparisonOp: + e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2) + elif token.astOperand2.str in op or token.astOperand2.isComparisonOp: + e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2.astOperand1) + else: + e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2) + if token.str == "+=" or token.str == "+": + if e1 == "char" and (e2 == "signed" or e2 == "unsigned"): + continue + if e2 == "char" and (e1 == "signed" or e1 == "unsigned"): + continue + if token.str == "-=" or token.str == "-": + if e1 == "char" and (e2 == "signed" or e2 == "unsigned"): + continue + if e1 and e2 and (e1.find('Anonymous') != -1 and (e2 == "signed" or e2 == "unsigned")): + continue + if e1 and e2 and (e2.find('Anonymous') != -1 and (e1 == "signed" or e1 == "unsigned")): + continue + if e1 and e2 and e1 != e2: + self.reportError(token, 10, 4) + + def misra_10_5(self, cfg): + def _get_essential_category(token): + essential_type = getEssentialType(token) + #print(essential_type) + if essential_type: + if essential_type in ('bool', 'char'): + return essential_type + if essential_type.split(' ')[-1] in ('float', 'double'): + return 'floating' + if essential_type.split(' ')[0] in ('unsigned', 'signed'): + return essential_type.split(' ')[0] + return None + for token in cfg.tokenlist: + if not isCast(token): + continue + to_type = _get_essential_category(token) + #print(to_type) + if to_type is None: + continue + from_type = _get_essential_category(token.astOperand1) + #print(from_type) + if from_type is None: + continue + if to_type == from_type: + continue + if to_type == 'bool' or from_type == 'bool': + if token.astOperand1.isInt and token.astOperand1.getKnownIntValue() == 1: + # Exception + continue + self.reportError(token, 10, 5) + continue + if to_type == 'enum': + self.reportError(token, 10, 5) + continue + if from_type == 'float' and to_type == 'char': + self.reportError(token, 10, 5) + continue + if from_type == 'char' and to_type == 'float': + self.reportError(token, 10, 5) + continue + + def misra_10_6(self, data): + for token in data.tokenlist: + if token.str != '=' or not token.astOperand1 or not token.astOperand2: + continue + if not is_composite_expr(token.astOperand2): + continue + vt1 = token.astOperand1.valueType + vt2 = token.astOperand2.valueType + if not vt1 or vt1.pointer > 0: + continue + if not vt2 or vt2.pointer > 0: + continue + try: + if isCast(token.astOperand2): + e = vt2.type + else: + e = getEssentialType(token.astOperand2) + if not e: + continue + if e == "char" and vt1.type == "int": + # When arithmetic operations are performed on char values, they are usually promoted to int + continue + lhsbits = vt1.bits if vt1.bits else bitsOfEssentialType(vt1.type) + if lhsbits > bitsOfEssentialType(e): + self.reportError(token, 10, 6) + except ValueError: + pass + + def misra_10_7(self, cfg): + for token in cfg.tokenlist: + if token.astOperand1 is None or token.astOperand2 is None: + continue + if not token.isArithmeticalOp: + continue + if not is_composite_expr(token): + continue + parent = token.astParent + if parent is None: + continue + if not parent.isArithmeticalOp: + if not parent.isAssignmentOp: + continue + if parent.str == '=': + continue + token_type = getEssentialType(token) + if token_type is None: + continue + sibling = parent.astOperand1 if (token == parent.astOperand2) else parent.astOperand2 + sibling_type = getEssentialType(sibling) + if sibling_type is None: + continue + b1 = bitsOfEssentialType(token_type) + b2 = bitsOfEssentialType(sibling_type) + if b1 > 0 and b1 < b2: + self.reportError(token, 10, 7) + + def misra_10_8(self, data): + for token in data.tokenlist: + if not isCast(token): + continue + if not token.valueType or token.valueType.pointer > 0: + continue + if not token.astOperand1.valueType or token.astOperand1.valueType.pointer > 0: + continue + if not token.astOperand1.astOperand1: + continue + if token.astOperand1.str not in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~'): + continue + if token.astOperand1.str != '~' and not token.astOperand1.astOperand2: + continue + if token.astOperand1.str == '~': + e2 = getEssentialTypeCategory(token.astOperand1.astOperand1) + else: + e2, e3 = getEssentialCategorylist(token.astOperand1.astOperand1, token.astOperand1.astOperand2) + if e2 != e3: + continue + e1 = getEssentialTypeCategory(token) + if e1 != e2: + self.reportError(token, 10, 8) + else: + try: + e = getEssentialType(token.astOperand1) + if not e: + continue + if bitsOfEssentialType(token.valueType.type) > bitsOfEssentialType(e): + self.reportError(token, 10, 8) + except ValueError: + pass + + def misra_11_1(self, data): + for token in data.tokenlist: + to_from = get_type_conversion_to_from(token) + if to_from is None: + continue + from_type = get_function_pointer_type(to_from[1]) + if from_type is None: + continue + to_type = get_function_pointer_type(to_from[0]) + if to_type is None or to_type != from_type: + self.reportError(token, 11, 1) + + def misra_11_2(self, data): + def get_pointer_type(type_token): + while type_token and (type_token.str in ('const', 'struct')): + type_token = type_token.next + if type_token is None: + return None + if not type_token.isName: + return None + return type_token if (type_token.next and type_token.next.str == '*') else None + + incomplete_types = [] + + for token in data.tokenlist: + if token.str == 'struct' and token.next and token.next.next and token.next.isName and token.next.next.str == ';': + incomplete_types.append(token.next.str) + to_from = get_type_conversion_to_from(token) + if to_from is None: + continue + to_pointer_type_token = get_pointer_type(to_from[0]) + if to_pointer_type_token is None: + continue + from_pointer_type_token = get_pointer_type(to_from[1]) + if from_pointer_type_token is None: + continue + if to_pointer_type_token.str == from_pointer_type_token.str: + continue + if from_pointer_type_token.typeScope is None and (from_pointer_type_token.str in incomplete_types): + self.reportError(token, 11, 2) + elif to_pointer_type_token.typeScope is None and (to_pointer_type_token.str in incomplete_types): + self.reportError(token, 11, 2) + + def misra_11_3(self, data): + for token in data.tokenlist: + if not isCast(token): + continue + vt1 = token.valueType + vt2 = token.astOperand1.valueType + if not vt1 or not vt2: + continue + if vt1.type == 'void' or vt2.type == 'void': + continue + if (vt1.pointer > 0 and vt1.type == 'record' and + vt2.pointer > 0 and vt2.type == 'record' and + vt1.typeScopeId != vt2.typeScopeId): + self.reportError(token, 11, 3) + elif (vt1.pointer == vt2.pointer and vt1.pointer > 0 and + vt1.type != vt2.type and vt1.type != 'char'): + self.reportError(token, 11, 3) + + def misra_11_4(self, data): + # Get list of macro definitions + macros = {} + for directive in data.directives: + #define X ((peripheral_t *)0x40000U) + res = re.match(r'#define ([A-Za-z0-9_]+).*', directive.str) + if res: + if res.group(1) in macros: + macros[res.group(1)].append(directive) + else: + macros[res.group(1)] = [directive] + + # If macro definition is non-compliant then warn about the macro definition instead of + # the macro usages. To reduce diagnostics for a non-compliant macro. + bad_macros = [] + for token in data.tokenlist: + if not isCast(token): + continue + vt1 = token.valueType + vt2 = token.astOperand1.valueType + if not vt1 or not vt2: + continue + if vt2.pointer > 0 and vt1.pointer == 0 and (vt1.isIntegral() or vt1.isEnum()) and vt2.type != 'void': + self.reportError(token, 11, 4) + elif vt1.pointer > 0 and vt2.pointer == 0 and (vt2.isIntegral() or vt2.isEnum()) and vt1.type != 'void': + if token.macroName is not None and \ + token.macroName == token.astOperand1.macroName and \ + token.astOperand1.isInt and \ + token.link.previous.str == '*' and \ + token.macroName == token.link.previous.macroName and \ + token.macroName in macros and \ + len(macros[token.macroName]) == 1: + if token.macroName not in bad_macros: + bad_macros.append(token.macroName) + self.reportError(macros[token.macroName][0], 11, 4) + continue + self.reportError(token, 11, 4) + + def misra_11_5(self, data): + for token in data.tokenlist: + if not isCast(token): + if token.astOperand1 and token.astOperand2 and token.str == "=" and token.next.str != "(": + vt1 = token.astOperand1.valueType + vt2 = token.astOperand2.valueType + if not vt1 or not vt2: + continue + if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void': + self.reportError(token, 11, 5) + continue + if token.astOperand1.astOperand1 and token.astOperand1.astOperand1.str in ( + 'malloc', 'calloc', 'realloc', 'free'): + continue + vt1 = token.valueType + vt2 = token.astOperand1.valueType + if not vt1 or not vt2: + continue + if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void': + self.reportError(token, 11, 5) + + def misra_11_6(self, data): + for token in data.tokenlist: + if not isCast(token): + continue + if token.astOperand1.astOperand1: + continue + vt1 = token.valueType + vt2 = token.astOperand1.valueType + if not vt1 or not vt2: + continue + if vt1.pointer == 1 and vt1.type == 'void' and vt2.pointer == 0 and token.astOperand1.getKnownIntValue() != 0: + self.reportError(token, 11, 6) + elif vt1.pointer == 0 and vt1.type != 'void' and vt2.pointer == 1 and vt2.type == 'void': + self.reportError(token, 11, 6) + + def misra_11_7(self, data): + for token in data.tokenlist: + if not isCast(token): + continue + vt1 = token.valueType + vt2 = token.astOperand1.valueType + if not vt1 or not vt2: + continue + if token.astOperand1.astOperand1: + continue + if (vt2.pointer > 0 and vt1.pointer == 0 and + not vt1.isIntegral() and not vt1.isEnum() and + vt1.type != 'void'): + self.reportError(token, 11, 7) + elif (vt1.pointer > 0 and vt2.pointer == 0 and + not vt2.isIntegral() and not vt2.isEnum() and + vt1.type != 'void'): + self.reportError(token, 11, 7) + + def misra_11_8(self, data): + # TODO: reuse code in CERT-EXP05 + for token in data.tokenlist: + if isCast(token): + # C-style cast + if not token.valueType: + continue + if not token.astOperand1.valueType: + continue + if token.valueType.pointer == 0: + continue + if token.astOperand1.valueType.pointer == 0: + continue + const1 = token.valueType.constness + const2 = token.astOperand1.valueType.constness + if (const1 % 2) < (const2 % 2): + self.reportError(token, 11, 8) + + elif token.str == '(' and token.astOperand1 and token.astOperand2 and token.astOperand1.function: + # Function call + function = token.astOperand1.function + arguments = getArguments(token) + for argnr, argvar in function.argument.items(): + if argnr < 1 or argnr > len(arguments): + continue + if not argvar.isPointer: + continue + argtok = arguments[argnr - 1] + if not argtok.valueType: + continue + if argtok.valueType.pointer == 0: + continue + const1 = argvar.constness + const2 = arguments[argnr - 1].valueType.constness + if (const1 % 2) < (const2 % 2): + self.reportError(token, 11, 8) + + def misra_11_9(self, data): + for token in data.tokenlist: + if token.astOperand1 and token.astOperand2 and token.str in ["=", "==", "!=", "?", ":"]: + vt1 = token.astOperand1.valueType + vt2 = token.astOperand2.valueType + if not vt1 or not vt2: + continue + if vt1.pointer > 0 and vt2.pointer == 0 and token.astOperand2.str == "NULL": + continue + if (token.astOperand2.values and vt1.pointer > 0 and + vt2.pointer == 0 and token.astOperand2.values): + if token.astOperand2.getValue(0): + self.reportError(token, 11, 9) + + def misra_12_1_sizeof(self, rawTokens): + state = 0 + compiled = re.compile(r'^[a-zA-Z_]') + for tok in rawTokens: + if tok.str.startswith('//') or tok.str.startswith('/*'): + continue + if tok.str == 'sizeof': + state = 1 + elif state == 1: + if compiled.match(tok.str): + state = 2 + else: + state = 0 + elif state == 2: + if tok.str in ('+', '-', '*', '/', '%'): + self.reportError(tok, 12, 1) + else: + state = 0 + + def misra_12_1(self, data): + for token in data.tokenlist: + p = getPrecedence(token) + if p < 2 or p > 12: + continue + p1 = getPrecedence(token.astOperand1) + if p < p1 <= 12 and numberOfParentheses(token.astOperand1, token): + self.reportError(token, 12, 1) + continue + p2 = getPrecedence(token.astOperand2) + if p < p2 <= 12 and numberOfParentheses(token, token.astOperand2): + self.reportError(token, 12, 1) + continue + + def misra_12_2(self, data): + for token in data.tokenlist: + if not (token.str in ('<<', '>>')): + continue + if (not token.astOperand2) or (not token.astOperand2.values): + continue + maxval = 0 + for val in token.astOperand2.values: + if val.intvalue and val.intvalue > maxval: + maxval = val.intvalue + if maxval == 0: + continue + sz = bitsOfEssentialType(getEssentialType(token.astOperand1)) + if sz <= 0: + continue + if maxval >= sz: + self.reportError(token, 12, 2) + + def misra_12_3(self, data): + for token in data.tokenlist: + if token.str == ';' and (token.isSplittedVarDeclComma is True): + self.reportError(token, 12, 3) + if token.str == ',' and token.astParent and token.astParent.str == ';': + self.reportError(token, 12, 3) + if token.str == ',' and token.astParent is None: + if token.scope.type in ('Class', 'Struct'): + # Is this initlist.. + tok = token + while tok and tok.str == ',': + tok = tok.next + if tok and tok.next and tok.isName and tok.next.str == '(': + tok = tok.next.link.next + if tok.str == '{': + # This comma is used in initlist, do not warn + continue + prev = token.previous + while prev: + if prev.str == ';': + self.reportError(token, 12, 3) + break + elif prev.str in ')}]': + prev = prev.link + elif prev.str in '({[': + break + prev = prev.previous + + def misra_12_4(self, cfg): + for expr in cfg.tokenlist: + if not expr.astOperand2 or not expr.astOperand1: + continue + if expr.valueType is None: + continue + if expr.valueType.sign is None or expr.valueType.sign != 'unsigned': + continue + if expr.valueType.pointer > 0: + continue + if not expr.valueType.isIntegral(): + continue + op1 = expr.astOperand1.getKnownIntValue() + if op1 is None: + continue + op2 = expr.astOperand2.getKnownIntValue() + if op2 is None: + continue + bits = bitsOfEssentialType('unsigned ' + expr.valueType.type) + if bits <= 0 or bits >= 64: + continue + max_value = (1 << bits) - 1 + if not is_constant_integer_expression(expr): + continue + if expr.str == '+' and op1 + op2 > max_value: + self.reportError(expr, 12, 4) + elif expr.str == '-' and op1 - op2 < 0: + self.reportError(expr, 12, 4) + elif expr.str == '*' and op1 * op2 > max_value: + self.reportError(expr, 12, 4) + + + def misra_13_1(self, data): + for token in data.tokenlist: + if simpleMatch(token, ") {") and token.next.astParent == token.link: + pass + elif not simpleMatch(token, '= {'): + continue + init = token.next + end = init.link + if not end: + continue # syntax is broken + + tn = init + while tn and tn != end: + if tn.str == '[' and tn.link: + tn = tn.link + if tn and tn.next and tn.next.str == '=': + tn = tn.next.next + continue + else: + break + if tn.str == '.' and tn.next and tn.next.isName: + tn = tn.next + if tn.next and tn.next.str == '=': + tn = tn.next.next + continue + if tn.str in {'++', '--'} or tn.isAssignmentOp: + self.reportError(init, 13, 1) + tn = tn.next + + def misra_13_3(self, data): + for token in data.tokenlist: + if token.str not in ('++', '--'): + continue + astTop = token + while astTop.astParent and astTop.astParent.str not in (',', ';'): + astTop = astTop.astParent + if countSideEffects(astTop) >= 2: + self.reportError(astTop, 13, 3) + + def misra_13_4(self, data): + for token in data.tokenlist: + if token.str != '=': + continue + if not token.astParent: + continue + if (token.astOperand1 is None) or (token.astOperand2 is None): + continue + if token.astOperand1.str == '[' and token.astOperand1.previous.str in ('{', ','): + continue + if not (token.astParent.str in [',', ';', '{']): + self.reportError(token, 13, 4) + + def misra_13_5(self, data): + for token in data.tokenlist: + if token.isLogicalOp and countSideEffectsRecursive(token.astOperand2) > 0: + self.reportError(token, 13, 5) + + def misra_13_6(self, data): + for token in data.tokenlist: + if token.str == 'sizeof' and countSideEffectsRecursive(token.next) > 0: + self.reportError(token, 13, 6) + + def misra_14_1(self, data): + for token in data.tokenlist: + if token.str == 'for': + exprs = getForLoopExpressions(token) + if not exprs: + continue + for counter in findCounterTokens(exprs[1]): + if counter.valueType and counter.valueType.isFloat(): + self.reportError(token, 14, 1) + elif token.str == 'while': + if isFloatCounterInWhileLoop(token): + self.reportError(token, 14, 1) + + def misra_14_2(self, data): + for token in data.tokenlist: + if token.str == 'for': + expressions = getForLoopExpressions(token) + if not expressions: + continue + if expressions[0] and not expressions[0].isAssignmentOp: + if expressions[0].str != "(" or not expressions[0].previous.isName: + self.reportError(token, 14, 2) + if countSideEffectsRecursive(expressions[1]) > 0: + self.reportError(token, 14, 2) + if countSideEffectsRecursive(expressions[2]) > 1: + self.reportError(token, 14, 2) + + counter_vars_first_clause, counter_vars_exit_modified = getForLoopCounterVariables(token, data) + if len(counter_vars_exit_modified) == 0: + # if it's not possible to identify a loop counter, all 3 clauses must be empty + for idx in range(len(expressions)): + if expressions[idx]: + self.reportError(token, 14, 2) + break + elif len(counter_vars_exit_modified) > 1: + # there shall be a single loop counter + self.reportError(token, 14, 2) + else: # len(counter_vars_exit_modified) == 1: + loop_counter = counter_vars_exit_modified.pop() + # if the first clause is not empty, then it shall (declare and) initialize the loop counter + if expressions[0] is not None and loop_counter not in counter_vars_first_clause: + self.reportError(token, 14, 2) + + # Inspect modification of loop counter in loop body + body_scope = token.next.link.next.scope + if not body_scope: + continue + tn = body_scope.bodyStart + while tn and tn != body_scope.bodyEnd: + if tn.variable == loop_counter: + if tn.next: + # TODO: Check modifications in function calls + if countSideEffectsRecursive(tn.next) > 0: + self.reportError(tn, 14, 2) + tn = tn.next + + def misra_14_4(self, data): + for token in data.tokenlist: + if token.str != '(': + continue + if not token.astOperand1 or not (token.astOperand1.str in ['if', 'while']): + continue + if isBoolExpression(token.astOperand2): + continue + if token.astOperand2.valueType: + self.reportError(token, 14, 4) + + def misra_15_1(self, data): + for token in data.tokenlist: + if token.str == "goto": + self.reportError(token, 15, 1) + + def misra_15_2(self, data): + for token in data.tokenlist: + if token.str != 'goto': + continue + if (not token.next) or (not token.next.isName): + continue + if not findGotoLabel(token): + self.reportError(token, 15, 2) + + def misra_15_3(self, data): + for token in data.tokenlist: + if token.str != 'goto': + continue + if (not token.next) or (not token.next.isName): + continue + tok = findGotoLabel(token) + if not tok: + continue + scope = token.scope + while scope and scope != tok.scope: + scope = scope.nestedIn + if not scope: + self.reportError(token, 15, 3) + # Jump crosses from one switch-clause to another is non-compliant + elif scope.type == 'Switch': + # Search for start of a current case block + tcase_start = token + while tcase_start and tcase_start.str not in ('case', 'default'): + tcase_start = tcase_start.previous + # Make sure that goto label doesn't occurs in the other + # switch-clauses + if tcase_start: + t = scope.bodyStart + in_this_case = False + while t and t != scope.bodyEnd: + if t == tcase_start: + in_this_case = True + if in_this_case and t.str not in ('case', 'default'): + in_this_case = False + if t == tok and not in_this_case: + self.reportError(token, 15, 3) + break + t = t.next + + def misra_15_4(self, data): + # Return a list of scopes affected by a break or goto + def getLoopsAffectedByBreak(knownLoops, scope, isGoto): + if scope and scope.type and scope.type not in ['Global', 'Function']: + if not isGoto and scope.type == 'Switch': + return + if scope.type in ['For', 'While', 'Do']: + knownLoops.append(scope) + if not isGoto: + return + getLoopsAffectedByBreak(knownLoops, scope.nestedIn, isGoto) + + loopWithBreaks = {} + for token in data.tokenlist: + if token.str not in ['break', 'goto']: + continue + + affectedLoopScopes = [] + getLoopsAffectedByBreak(affectedLoopScopes, token.scope, token.str == 'goto') + for scope in affectedLoopScopes: + if scope in loopWithBreaks: + loopWithBreaks[scope] += 1 + else: + loopWithBreaks[scope] = 1 + + for scope, breakCount in loopWithBreaks.items(): + if breakCount > 1: + self.reportError(scope.bodyStart, 15, 4) + + def misra_15_5(self, data): + for token in data.tokenlist: + if token.str == 'return' and token.scope.type != 'Function': + self.reportError(token, 15, 5) + + def misra_15_6(self, rawTokens): + state = 0 + indent = 0 + tok1 = None + def tokAt(tok,i): + while i < 0 and tok: + tok = tok.previous + if tok.str.startswith('//') or tok.str.startswith('/*'): + continue + i += 1 + while i > 0 and tok: + tok = tok.next + if tok.str.startswith('//') or tok.str.startswith('/*'): + continue + i -= 1 + return tok + + def strtokens(tok, i1, i2): + tok1 = tokAt(tok, i1) + tok2 = tokAt(tok, i2) + tok = tok1 + s = '' + while tok != tok2: + if tok.str.startswith('//') or tok.str.startswith('/*'): + tok = tok.next + continue + s += ' ' + tok.str + tok = tok.next + s += ' ' + tok.str + return s[1:] + + for token in rawTokens: + if token.str in ['if', 'for', 'while']: + if strtokens(token,-1,0) == '# if': + continue + if strtokens(token,-1,0) == "} while": + # is there a 'do { .. } while'? + start = rawlink(tokAt(token,-1)) + if start and strtokens(start, -1, 0) == 'do {': + continue + if state == 2: + self.reportError(tok1, 15, 6) + state = 1 + indent = 0 + tok1 = token + elif token.str == 'else': + if strtokens(token,-1,0) == '# else': + continue + if strtokens(token,0,1) == 'else if': + continue + if state == 2: + self.reportError(tok1, 15, 6) + state = 2 + indent = 0 + tok1 = token + elif state == 1: + if indent == 0 and token.str != '(': + state = 0 + continue + if token.str == '(': + indent = indent + 1 + elif token.str == ')': + if indent == 0: + state = 0 + elif indent == 1: + state = 2 + indent = indent - 1 + elif state == 2: + if token.str.startswith('//') or token.str.startswith('/*'): + continue + state = 0 + if token.str not in ('{', '#'): + self.reportError(tok1, 15, 6) + + def misra_15_7(self, data): + for scope in data.scopes: + if scope.type != 'Else': + continue + if not simpleMatch(scope.bodyStart, '{ if ('): + continue + if scope.bodyStart.column > 0: + continue + tok = scope.bodyStart.next.next.link + if not simpleMatch(tok, ') {'): + continue + tok = tok.next.link + if not simpleMatch(tok, '} else'): + self.reportError(tok, 15, 7) + + def misra_16_1(self, cfg): + for scope in cfg.scopes: + if scope.type != 'Switch': + continue + in_case_or_default = False + tok = scope.bodyStart.next + while tok != scope.bodyEnd: + if not in_case_or_default: + if tok.str not in ('case', 'default'): + self.reportError(tok, 16, 1) + else: + in_case_or_default = True + else: + if simpleMatch(tok, 'break ;'): + in_case_or_default = False + tok = tok.next + if tok.str == '{': + tok = tok.link + if tok.scope.type == 'Unconditional' and simpleMatch(tok.previous.previous, 'break ;'): + in_case_or_default = False + tok = tok.next + + def misra_16_2(self, data): + for token in data.tokenlist: + if token.str == 'case' and token.scope.type != 'Switch': + self.reportError(token, 16, 2) + + def misra_16_3(self, rawTokens): + STATE_NONE = 0 # default state, not in switch case/default block + STATE_BREAK = 1 # break/comment is seen but not its ';' + STATE_OK = 2 # a case/default is allowed (we have seen 'break;'/'comment'/'{'/attribute) + STATE_SWITCH = 3 # walking through switch statement scope + + define = None + state = STATE_NONE + end_switch_token = None # end '}' for the switch scope + for token in rawTokens: + if simpleMatch(token, '# define'): + define = token + if define: + if token.linenr != define.linenr: + define = None + else: + continue + + # Find switch scope borders + if token.str == 'switch': + state = STATE_SWITCH + if state == STATE_SWITCH: + if token.str == '{': + end_switch_token = findRawLink(token) + else: + continue + + if token.str == 'break' or token.str == 'return' or token.str == 'throw': + state = STATE_BREAK + elif token.str == ';': + if state == STATE_BREAK: + state = STATE_OK + elif token.next and token.next == end_switch_token: + self.reportError(token.next, 16, 3) + else: + state = STATE_NONE + elif token.str.startswith('/*') or token.str.startswith('//'): + if 'fallthrough' in token.str.lower(): + state = STATE_OK + elif simpleMatch(token, '[ [ fallthrough ] ] ;'): + state = STATE_BREAK + elif token.str == '{': + state = STATE_OK + elif token.str == '}' and state == STATE_OK: + # is this {} an unconditional block of code? + prev = findRawLink(token) + if prev: + prev = prev.previous + while prev and prev.str[:2] in ('//', '/*'): + prev = prev.previous + if (prev is None) or (prev.str not in ':;{}'): + state = STATE_NONE + elif token.str == 'case' or token.str == 'default': + if state != STATE_OK: + self.reportError(token, 16, 3) + state = STATE_OK + + def misra_16_4(self, data): + for token in data.tokenlist: + if token.str != 'switch': + continue + if not simpleMatch(token, 'switch ('): + continue + if not simpleMatch(token.next.link, ') {'): + continue + startTok = token.next.link.next + tok = startTok.next + while tok and tok.str != '}': + if tok.str == '{': + tok = tok.link + elif tok.str == 'default': + break + tok = tok.next + if tok and tok.str != 'default': + self.reportError(token, 16, 4) + + def misra_16_5(self, data): + for token in data.tokenlist: + if token.str != 'default': + continue + if token.previous and token.previous.str == '{': + continue + tok2 = token + while tok2: + if tok2.str in ('}', 'case'): + break + if tok2.str == '{': + tok2 = tok2.link + tok2 = tok2.next + if tok2 and tok2.str == 'case': + self.reportError(token, 16, 5) + + def misra_16_6(self, data): + for token in data.tokenlist: + if not (simpleMatch(token, 'switch (') and simpleMatch(token.next.link, ') {')): + continue + tok = token.next.link.next.next + count = 0 + while tok: + if tok.str in ['break', 'return', 'throw']: + count = count + 1 + elif tok.str == '{': + tok = tok.link + if isNoReturnScope(tok): + count = count + 1 + elif tok.str == '}': + break + tok = tok.next + if count < 2: + self.reportError(token, 16, 6) + + def misra_16_7(self, data): + for token in data.tokenlist: + if simpleMatch(token, 'switch (') and isBoolExpression(token.next.astOperand2): + self.reportError(token, 16, 7) + + def misra_17_1(self, data): + for token in data.tokenlist: + if isFunctionCall(token) and token.astOperand1.str in ( + 'va_list', 'va_arg', 'va_start', 'va_end', 'va_copy'): + self.reportError(token, 17, 1) + elif token.str == 'va_list': + self.reportError(token, 17, 1) + + def misra_17_2(self, data): + # find recursions.. + def find_recursive_call(search_for_function, direct_call, calls_map, visited=None): + if visited is None: + visited = set() + if direct_call == search_for_function: + return True + for indirect_call in calls_map.get(direct_call, []): + if indirect_call == search_for_function: + return True + if indirect_call in visited: + # This has already been handled + continue + visited.add(indirect_call) + if find_recursive_call(search_for_function, indirect_call, calls_map, visited): + return True + return False + + # List functions called in each function + function_calls = {} + for scope in data.scopes: + if scope.type != 'Function': + continue + calls = [] + tok = scope.bodyStart + while tok != scope.bodyEnd: + tok = tok.next + if not isFunctionCall(tok, data.standards.c): + continue + f = tok.astOperand1.function + if f is not None and f not in calls: + calls.append(f) + function_calls[scope.function] = calls + + # Report warnings for all recursions.. + for func in function_calls: + for call in function_calls[func]: + if not find_recursive_call(func, call, function_calls): + # Function call is not recursive + continue + # Warn about all functions calls.. + for scope in data.scopes: + if scope.type != 'Function' or scope.function != func: + continue + tok = scope.bodyStart + while tok != scope.bodyEnd: + if tok.function and tok.function == call: + self.reportError(tok, 17, 2) + tok = tok.next + + def misra_17_3(self, cfg): + for w in cfg.clang_warnings: + if w['message'].endswith('[-Wimplicit-function-declaration]'): + self.reportError(cppcheckdata.Location(w), 17, 3) + for token in cfg.tokenlist: + if token.str not in ["while", "if"]: + continue + if token.next.str != "(": + continue + tok = token.next + end_token = token.next.link + while tok != end_token: + if tok.isName and tok.function is None and tok.valueType is None and tok.next.str == "(" and \ + tok.next.valueType is None and not isKeyword(tok.str) and not isStdLibId(tok.str): + self.reportError(tok, 17, 3) + break + tok = tok.next + + def misra_config(self, data): + for var in data.variables: + if not var.isArray or var.nameToken is None or not cppcheckdata.simpleMatch(var.nameToken.next, '['): + continue + tok = var.nameToken.next + while tok.str == '[': + sz = tok.astOperand2 + if sz and sz.getKnownIntValue() is None: + has_var = False + unknown_constant = False + tokens = [sz] + while len(tokens) > 0: + t = tokens[-1] + tokens = tokens[:-1] + if t: + if t.isName and t.getKnownIntValue() is None: + if t.varId or t.variable: + has_var = True + continue + unknown_constant = True + self.report_config_error(tok, 'Unknown constant {}, please review configuration'.format(t.str)) + if t.isArithmeticalOp: + tokens += [t.astOperand1, t.astOperand2] + if not unknown_constant and not has_var: + self.report_config_error(tok, 'Unknown array size, please review configuration') + tok = tok.link.next + + for token in data.tokenlist: + if token.str not in ("while", "if"): + continue + tok = token.next + if token is None or tok.str != "(": + continue + end_token = tok.link + while tok != end_token: + tok = tok.next + if tok.str == "(" and tok.isCast: + tok = tok.link + continue + if not tok.isName: + continue + if tok.function or tok.variable or tok.varId or tok.valueType: + continue + if tok.next.str == "(" or tok.str in ["EOF"]: + continue + if isKeyword(tok.str) or isStdLibId(tok.str): + continue + if tok.astParent is None: + continue + if tok.astParent.str == "." and tok.astParent.valueType: + continue + self.report_config_error(tok, "Variable '%s' is unknown" % tok.str) + + def misra_17_6(self, rawTokens): + for token in rawTokens: + if simpleMatch(token, '[ static'): + self.reportError(token, 17, 6) + + def misra_17_7(self, data): + for token in data.tokenlist: + if not token.scope.isExecutable: + continue + if token.str != '(' or token.astParent: + continue + if token.astOperand1 is None or not token.astOperand1.isName: + continue + if token.astOperand1.varId and (token.astOperand1.variable is None or get_function_pointer_type(token.astOperand1.variable.typeStartToken) is None): + continue + if token.valueType is None: + continue + if token.valueType.type == 'void' and token.valueType.pointer == 0: + continue + self.reportError(token, 17, 7) + + def misra_17_8(self, data): + for token in data.tokenlist: + if not (token.isAssignmentOp or (token.str in ('++', '--'))): + continue + if not token.astOperand1: + continue + var = token.astOperand1.variable + if var and var.isArgument: + self.reportError(token, 17, 8) + + def misra_18_4(self, data): + for token in data.tokenlist: + if token.str not in ('+', '-', '+=', '-='): + continue + if token.astOperand1 is None or token.astOperand2 is None: + continue + vt1 = token.astOperand1.valueType + vt2 = token.astOperand2.valueType + if vt1 and vt1.pointer > 0: + self.reportError(token, 18, 4) + elif vt2 and vt2.pointer > 0: + self.reportError(token, 18, 4) + + def misra_18_5(self, data): + for var in data.variables: + if not var.isPointer: + continue + typetok = var.nameToken + count = 0 + while typetok: + if typetok.str == '*': + count = count + 1 + elif not typetok.isName: + break + typetok = typetok.previous + if count > 2: + self.reportError(var.nameToken, 18, 5) + + def misra_18_7(self, data): + for scope in data.scopes: + if scope.type != 'Struct': + continue + + token = scope.bodyStart.next + while token != scope.bodyEnd and token is not None: + # Handle nested structures to not duplicate an error. + if token.str == '{': + token = token.link + + # skip function pointer parameter types + if token.astOperand1 is None: + pass + elif cppcheckdata.simpleMatch(token, "[ ]"): + self.reportError(token, 18, 7) + break + token = token.next + + def misra_18_8(self, data): + for var in data.variables: + if not var.isArray or not var.isLocal: + continue + # TODO Array dimensions are not available in dump, must look in tokens + typetok = var.nameToken.next + if not typetok or typetok.str != '[': + continue + # Unknown define or syntax error + if not typetok.astOperand2: + continue + if not isConstantExpression(typetok.astOperand2) and not isUnknownConstantExpression(typetok.astOperand2): + self.reportError(var.nameToken, 18, 8) + + def misra_19_2(self, data): + for token in data.tokenlist: + if token.str == 'union': + self.reportError(token, 19, 2) + + def misra_20_1(self, data): + token_in_file = {} + for token in data.tokenlist: + if token.file not in token_in_file: + token_in_file[token.file] = int(token.linenr) + else: + token_in_file[token.file] = min(token_in_file[token.file], int(token.linenr)) + + for directive in data.directives: + if not directive.str.startswith('#include'): + continue + if directive.file not in token_in_file: + continue + if token_in_file[directive.file] < int(directive.linenr): + self.reportError(directive, 20, 1) + + def misra_20_2(self, data): + for directive in data.directives: + if not directive.str.startswith('#include '): + continue + for pattern in ('\\', '//', '/*', ',', "'"): + if pattern in directive.str: + self.reportError(directive, 20, 2) + break + + def misra_20_3(self, data): + for directive in data.directives: + if not directive.str.startswith('#include '): + continue + + words = directive.str.split(' ') + + # If include directive contains more than two words, here would be + # violation anyway. + if len(words) > 2: + self.reportError(directive, 20, 3) + + # Handle include directives with not quoted argument + elif len(words) > 1: + filename = words[1] + if not ((filename.startswith('"') and + filename.endswith('"')) or + (filename.startswith('<') and + filename.endswith('>'))): + # We are handle only directly included files in the + # following format: #include file.h + # Cases with macro expansion provided by MISRA document are + # skipped because we don't always have access to directive + # definition. + if '.' in filename: + self.reportError(directive, 20, 3) + + def misra_20_4(self, data): + for directive in data.directives: + res = re.search(r'#define ([a-z][a-z0-9_]+)', directive.str) + if res and isKeyword(res.group(1), data.standards.c): + self.reportError(directive, 20, 4) + + def misra_20_5(self, data): + for directive in data.directives: + if directive.str.startswith('#undef '): + self.reportError(directive, 20, 5) + + def misra_20_7(self, data): + def find_string_concat(exp, arg, directive_args): + # Handle concatenation of string literals, e.g.: + # #define MACRO(A, B) (A " " B) + # Addon should not report errors for both macro arguments. + arg_pos = exp.find(arg, 0) + need_check = False + skip_next = False + state_in_string = False + pos_search = arg_pos + 1 + directive_args = [a.strip() for a in directive_args if a != arg] + arg = arg.strip() + while pos_search < len(exp): + if exp[pos_search] == '"': + if state_in_string: + state_in_string = False + else: + state_in_string = True + pos_search += 1 + elif exp[pos_search].isalnum(): + word = "" + while pos_search < len(exp) and exp[pos_search].isalnum(): + word += exp[pos_search] + pos_search += 1 + if word == arg: + pos_search += 1 + elif word in directive_args: + skip_next = True + break + elif exp[pos_search] == ' ': + pos_search += 1 + elif state_in_string: + pos_search += 1 + else: + need_check = True + break + return need_check, skip_next + + for directive in data.directives: + d = Define(directive) + exp = '(' + d.expansionList + ')' + skip_next = False + for arg in d.args: + if skip_next: + _, skip_next = find_string_concat(exp, arg, d.args) + continue + need_check, skip_next = find_string_concat(exp, arg, d.args) + if not need_check: + continue + + pos = 0 + while pos < len(exp): + pos = exp.find(arg, pos) + if pos < 0: + break + # is 'arg' used at position pos + pos1 = pos - 1 + pos2 = pos + len(arg) + pos = pos2 + if pos1 >= 0 and (isalnum(exp[pos1]) or exp[pos1] == '_'): + continue + if pos2 < len(exp) and (isalnum(exp[pos2]) or exp[pos2] == '_'): + continue + + while pos1 >= 0 and exp[pos1] == ' ': + pos1 -= 1 + if exp[pos1] == '#': + continue + if exp[pos1] not in '([,.': + self.reportError(directive, 20, 7) + break + while pos2 < len(exp) and exp[pos2] == ' ': + pos2 += 1 + if pos2 < len(exp) and exp[pos2] not in ')]#,': + self.reportError(directive, 20, 7) + break + + def misra_20_8(self, cfg): + for cond in cfg.preprocessor_if_conditions: + #print(cond) + if cond.result and cond.result not in (0,1): + self.reportError(cond, 20, 8) + + def misra_20_9(self, cfg): + for cond in cfg.preprocessor_if_conditions: + if cond.E is None: + continue + defined = [] + for directive in cfg.directives: + if directive.file == cond.file and directive.linenr == cond.linenr: + for name in re.findall(r'[^_a-zA-Z0-9]defined[ ]*\([ ]*([_a-zA-Z0-9]+)[ ]*\)', directive.str): + defined.append(name) + for name in re.findall(r'[^_a-zA-Z0-9]defined[ ]*([_a-zA-Z0-9]+)', directive.str): + defined.append(name) + break + for s in cond.E.split(' '): + if (s[0] >= 'A' and s[0] <= 'Z') or (s[0] >= 'a' and s[0] <= 'z'): + if isKeyword(s): + continue + if s in defined: + continue + self.reportError(cond, 20, 9) + + def misra_20_10(self, data): + for directive in data.directives: + d = Define(directive) + if d.expansionList.find('#') >= 0: + self.reportError(directive, 20, 10) + + def misra_20_11(self, cfg): + for directive in cfg.directives: + d = Define(directive) + for arg in d.args: + res = re.search(r'[^#]#[ ]*%s[ ]*##' % arg, ' ' + d.expansionList) + if res: + self.reportError(directive, 20, 11) + + def misra_20_12(self, cfg): + def _is_hash_hash_op(expansion_list, arg): + return re.search(r'##[ ]*%s[^a-zA-Z0-9_]' % arg, expansion_list) or \ + re.search(r'[^a-zA-Z0-9_]%s[ ]*##' % arg, expansion_list) + + def _is_other_op(expansion_list, arg): + pos = expansion_list.find(arg) + while pos >= 0: + pos1 = pos - 1 + pos2 = pos + len(arg) + pos = expansion_list.find(arg, pos2) + if isalnum(expansion_list[pos1]) or expansion_list[pos1] == '_': + continue + if isalnum(expansion_list[pos2]) or expansion_list[pos2] == '_': + continue + while expansion_list[pos1] == ' ': + pos1 = pos1 - 1 + if expansion_list[pos1] == '#': + continue + while expansion_list[pos2] == ' ': + pos2 = pos2 + 1 + if expansion_list[pos2] == '#': + continue + return True + return False + + def _is_arg_macro_usage(directive, arg): + for macro_usage in cfg.macro_usage: + if macro_usage.file == directive.file and macro_usage.linenr == directive.linenr: + for macro_usage_arg in cfg.macro_usage: + if macro_usage_arg == macro_usage: + continue + if (macro_usage.usefile == macro_usage_arg.usefile and + macro_usage.uselinenr == macro_usage_arg.uselinenr and + macro_usage.usecolumn == macro_usage_arg.usecolumn): + # TODO: check arg better + return True + return False + + for directive in cfg.directives: + define = Define(directive) + expansion_list = '(%s)' % define.expansionList + for arg in define.args: + if not _is_hash_hash_op(expansion_list, arg): + continue + if not _is_other_op(expansion_list, arg): + continue + if _is_arg_macro_usage(directive, arg): + self.reportError(directive, 20, 12) + break + + def misra_20_13(self, data): + dir_pattern = re.compile(r'#[ ]*([^ (<]*)') + for directive in data.directives: + dir = directive.str + mo = dir_pattern.match(dir) + if mo: + dir = mo.group(1) + if dir not in ['define', 'elif', 'else', 'endif', 'error', 'if', 'ifdef', 'ifndef', 'include', + 'pragma', 'undef', 'warning']: + self.reportError(directive, 20, 13) + + def misra_20_14(self, data): + # stack for #if blocks. contains the #if directive until the corresponding #endif is seen. + # the size increases when there are inner #if directives. + ifStack = [] + for directive in data.directives: + if directive.str.startswith('#if ') or directive.str.startswith('#ifdef ') or directive.str.startswith( + '#ifndef '): + ifStack.append(directive) + elif directive.str == '#else' or directive.str.startswith('#elif '): + if len(ifStack) == 0: + self.reportError(directive, 20, 14) + ifStack.append(directive) + elif directive.file != ifStack[-1].file: + self.reportError(directive, 20, 14) + elif directive.str == '#endif': + if len(ifStack) == 0: + self.reportError(directive, 20, 14) + elif directive.file != ifStack[-1].file: + self.reportError(directive, 20, 14) + ifStack.pop() + + def misra_21_1(self, data): + re_forbidden_macro = re.compile(r'#(?:define|undef) _[_A-Z]+') + re_macro_name = re.compile(r'#(?:define|undef) (.+)[ $]') + + for d in data.directives: + # Search for forbidden identifiers + m = re.search(re_forbidden_macro, d.str) + if m: + self.reportError(d, 21, 1) + continue + + # Search standard library identifiers in macro names + m = re.search(re_macro_name, d.str) + if not m: + continue + name = m.group(1) + if isStdLibId(name, data.standards.c): + self.reportError(d, 21, 1) + + def misra_21_2(self, cfg): + for directive in cfg.directives: + define = Define(directive) + if re.match(r'_+BUILTIN_.*', define.name.upper()): + self.reportError(directive, 21, 2) + for func in cfg.functions: + if isStdLibId(func.name, cfg.standards.c): + tok = func.tokenDef if func.tokenDef else func.token + self.reportError(tok, 21, 2) + + def misra_21_3(self, data): + for token in data.tokenlist: + if isFunctionCall(token) and (token.astOperand1.str in ('malloc', 'calloc', 'realloc', 'free')): + self.reportError(token, 21, 3) + + def misra_21_4(self, data): + directive = findInclude(data.directives, '') + if directive: + self.reportError(directive, 21, 4) + + def misra_21_5(self, data): + directive = findInclude(data.directives, '') + if directive: + self.reportError(directive, 21, 5) + + def misra_21_6(self, data): + dir_stdio = findInclude(data.directives, '') + dir_wchar = findInclude(data.directives, '') + if dir_stdio: + self.reportError(dir_stdio, 21, 6) + if dir_wchar: + self.reportError(dir_wchar, 21, 6) + + def misra_21_7(self, data): + for token in data.tokenlist: + if isFunctionCall(token) and (token.astOperand1.str in ('atof', 'atoi', 'atol', 'atoll')): + self.reportError(token, 21, 7) + + def misra_21_8(self, data): + for token in data.tokenlist: + if isFunctionCall(token) and (token.astOperand1.str in ('abort', 'exit', 'getenv')): + self.reportError(token, 21, 8) + + def misra_21_9(self, data): + for token in data.tokenlist: + if (token.str in ('bsearch', 'qsort')) and token.next and token.next.str == '(': + self.reportError(token, 21, 9) + + def misra_21_10(self, data): + directive = findInclude(data.directives, '') + if directive: + self.reportError(directive, 21, 10) + + for token in data.tokenlist: + if (token.str == 'wcsftime') and token.next and token.next.str == '(': + self.reportError(token, 21, 10) + + def misra_21_11(self, data): + directive = findInclude(data.directives, '') + if directive: + self.reportError(directive, 21, 11) + + def misra_21_12(self, data): + if findInclude(data.directives, ''): + for token in data.tokenlist: + if token.str == 'fexcept_t' and token.isName: + self.reportError(token, 21, 12) + if isFunctionCall(token) and (token.astOperand1.str in ( + 'feclearexcept', + 'fegetexceptflag', + 'feraiseexcept', + 'fesetexceptflag', + 'fetestexcept')): + self.reportError(token, 21, 12) + + def misra_21_14(self, data): + # buffers used in strcpy/strlen/etc function calls + string_buffers = [] + for token in data.tokenlist: + if token.str[0] == 's' and isFunctionCall(token.next): + name, args = cppcheckdata.get_function_call_name_args(token) + if name is None: + continue + def _get_string_buffers(match, args, argnum): + if not match: + return [] + ret = [] + for a in argnum: + if a < len(args): + arg = args[a] + while arg and arg.str in ('.', '::'): + arg = arg.astOperand2 + if arg and arg.varId != 0 and arg.varId not in ret: + ret.append(arg.varId) + return ret + string_buffers += _get_string_buffers(name == 'strcpy', args, [0, 1]) + string_buffers += _get_string_buffers(name == 'strncpy', args, [0, 1]) + string_buffers += _get_string_buffers(name == 'strlen', args, [0]) + string_buffers += _get_string_buffers(name == 'strcmp', args, [0, 1]) + string_buffers += _get_string_buffers(name == 'sprintf', args, [0]) + string_buffers += _get_string_buffers(name == 'snprintf', args, [0, 3]) + + for token in data.tokenlist: + if token.str != 'memcmp': + continue + name, args = cppcheckdata.get_function_call_name_args(token) + if name is None: + continue + if len(args) != 3: + continue + for arg in args[:2]: + if arg.str[-1] == '\"': + self.reportError(arg, 21, 14) + continue + while arg and arg.str in ('.', '::'): + arg = arg.astOperand2 + if arg and arg.varId and arg.varId in string_buffers: + self.reportError(arg, 21, 14) + + def misra_21_15(self, data): + for token in data.tokenlist: + if token.str not in ('memcpy', 'memmove', 'memcmp'): + continue + name, args = cppcheckdata.get_function_call_name_args(token) + if name is None: + continue + if len(args) != 3: + continue + if args[0].valueType is None or args[1].valueType is None: + continue + if args[0].valueType.type == args[1].valueType.type: + continue + if args[0].valueType.type == 'void' or args[1].valueType.type == 'void': + continue + self.reportError(token, 21, 15) + + def misra_21_16(self, cfg): + for token in cfg.tokenlist: + if token.str != 'memcmp': + continue + name, args = cppcheckdata.get_function_call_name_args(token) + if name is None: + continue + if len(args) != 3: + continue + for arg in args[:2]: + if arg.valueType is None: + continue + if arg.valueType.pointer > 1: + continue + if arg.valueType.sign in ('unsigned', 'signed'): + continue + if arg.valueType.isEnum(): + continue + self.reportError(token, 21, 16) + + def misra_21_19(self, cfg): + for token in cfg.tokenlist: + if token.str in ('localeconv', 'getenv', 'setlocale', 'strerror') and simpleMatch(token.next, '('): + name, _ = cppcheckdata.get_function_call_name_args(token) + if name is None or name != token.str: + continue + parent = token.next + while simpleMatch(parent.astParent, '+'): + parent = parent.astParent + # x = f() + if simpleMatch(parent.astParent, '=') and parent == parent.astParent.astOperand2: + lhs = parent.astParent.astOperand1 + if lhs and lhs.valueType and lhs.valueType.pointer > 0 and lhs.valueType.constness == 0: + self.reportError(token, 21, 19) + if token.str == '=': + lhs = token.astOperand1 + while simpleMatch(lhs, '*') and lhs.astOperand2 is None: + lhs = lhs.astOperand1 + if not simpleMatch(lhs, '.'): + continue + while simpleMatch(lhs, '.'): + lhs = lhs.astOperand1 + if lhs and lhs.variable and simpleMatch(lhs.variable.typeStartToken, 'lconv'): + self.reportError(token, 21, 19) + + def misra_21_20(self, cfg): + assigned = {} + invalid = [] + for token in cfg.tokenlist: + # No sophisticated data flow analysis, bail out if control flow is "interrupted" + if token.str in ('{', '}', 'break', 'continue', 'return'): + assigned = {} + invalid = [] + continue + + # When pointer is assigned, remove it from 'assigned' and 'invalid' + if token.varId and token.varId > 0 and simpleMatch(token.next, '='): + for name in assigned.keys(): + while token.varId in assigned[name]: + assigned[name].remove(token.varId) + while token.varId in invalid: + invalid.remove(token.varId) + continue + + # Calling dangerous function + if token.str in ('asctime', 'ctime', 'gmtime', 'localtime', 'localeconv', 'getenv', 'setlocale', 'strerror'): + name, args = cppcheckdata.get_function_call_name_args(token) + if name and name == token.str: + # make assigned pointers invalid + for varId in assigned.get(name, ()): + if varId not in invalid: + invalid.append(varId) + + # assign pointer + parent = token.next + while parent.astParent and (parent.astParent.str == '+' or isCast(parent.astParent)): + parent = parent.astParent + if simpleMatch(parent.astParent, '='): + eq = parent.astParent + vartok = eq.previous + if vartok and vartok.varId and vartok.varId > 0: + if name not in assigned: + assigned[name] = [vartok.varId] + elif vartok.varId not in assigned[name]: + assigned[name].append(vartok.varId) + continue + + # taking value of invalid pointer.. + if token.astParent and token.varId: + if token.varId in invalid: + self.reportError(token, 21, 20) + + def misra_21_21(self, cfg): + for token in cfg.tokenlist: + if token.str == 'system': + name, args = cppcheckdata.get_function_call_name_args(token) + if name == 'system' and len(args) == 1: + self.reportError(token, 21, 21) + + def misra_22_5(self, cfg): + for token in cfg.tokenlist: + if token.isUnaryOp("*") or (token.isBinaryOp() and token.str == '.'): + fileptr = token.astOperand1 + if fileptr.variable and cppcheckdata.simpleMatch(fileptr.variable.typeStartToken, 'FILE *'): + self.reportError(token, 22, 5) + + def misra_22_7(self, cfg): + for eofToken in cfg.tokenlist: + if eofToken.str != 'EOF': + continue + if eofToken.astParent is None or not eofToken.astParent.isComparisonOp: + continue + if eofToken.astParent.astOperand1 == eofToken: + eofTokenSibling = eofToken.astParent.astOperand2 + else: + eofTokenSibling = eofToken.astParent.astOperand1 + while isCast(eofTokenSibling) and eofTokenSibling.valueType and eofTokenSibling.valueType.type and eofTokenSibling.valueType.type == 'int': + eofTokenSibling = eofTokenSibling.astOperand2 if eofTokenSibling.astOperand2 else eofTokenSibling.astOperand1 + if eofTokenSibling is not None and eofTokenSibling.valueType and eofTokenSibling.valueType and eofTokenSibling.valueType.type in ('bool', 'char', 'short'): + self.reportError(eofToken, 22, 7) + + def misra_22_8(self, cfg): + is_zero = False + for token in cfg.tokenlist: + if simpleMatch(token, 'errno = 0'): + is_zero = True + if token.str == '(' and not simpleMatch(token.link, ') {'): + name, _ = cppcheckdata.get_function_call_name_args(token.previous) + if name is None: + continue + if is_errno_setting_function(name): + if not is_zero: + self.reportError(token, 22, 8) + else: + is_zero = False + + def misra_22_9(self, cfg): + errno_is_set = False + for token in cfg.tokenlist: + if token.str == '(' and not simpleMatch(token.link, ') {'): + name, args = cppcheckdata.get_function_call_name_args(token.previous) + if name is None: + continue + errno_is_set = is_errno_setting_function(name) + if errno_is_set and token.str in '{};': + errno_is_set = False + tok = token.next + while tok and tok.str not in ('{','}',';','errno'): + tok = tok.next + if tok is None or tok.str != 'errno': + self.reportError(token, 22, 9) + elif (tok.astParent is None) or (not tok.astParent.isComparisonOp): + self.reportError(token, 22, 9) + + def misra_22_10(self, cfg): + last_function_call = None + for token in cfg.tokenlist: + if token.isName and token.next.str == '(' and not simpleMatch(token.next.link, ') {'): + name, args = cppcheckdata.get_function_call_name_args(token) + last_function_call = name + if token.str == '}': + last_function_call = None + if token.str == 'errno' and token.astParent and token.astParent.isComparisonOp: + if last_function_call is None: + self.reportError(token, 22, 10) + elif not is_errno_setting_function(last_function_call): + self.reportError(token, 22, 10) + + + def get_verify_expected(self): + """Return the list of expected violations in the verify test""" + return self.verify_expected + + def get_verify_actual(self): + """Return the list of actual violations in for the verify test""" + return self.verify_actual + + def get_violations(self, violation_type=None): + """Return the list of violations for a normal checker run""" + if violation_type is None: + return self.violations.items() + else: + return self.violations[violation_type] + + def get_violation_types(self): + """Return the list of violations for a normal checker run""" + return self.violations.keys() + + def addSuppressedRule(self, ruleNum, + fileName=None, + lineNumber=None, + symbolName=None): + """ + Add a suppression to the suppressions data structure + + Suppressions are stored in a dictionary of dictionaries that + contains a list of tuples. + + The first dictionary is keyed by the MISRA rule in hundreds + format. The value of that dictionary is a dictionary of filenames. + If the value is None then the rule is assumed to be suppressed for + all files. + If the filename exists then the value of that dictionary contains a list + with the scope of the suppression. If the list contains an item of None + then the rule is assumed to be suppressed for the entire file. Otherwise + the list contains line number, symbol name tuples. + For each tuple either line number or symbol name can can be none. + + """ + normalized_filename = None + + if fileName is not None: + normalized_filename = os.path.expanduser(fileName) + normalized_filename = os.path.normpath(normalized_filename) + + if lineNumber is not None or symbolName is not None: + line_symbol = (lineNumber, symbolName) + else: + line_symbol = None + + # If the rule is not in the dict already then add it + if ruleNum not in self.suppressedRules: + ruleItemList = list() + ruleItemList.append(line_symbol) + + fileDict = dict() + fileDict[normalized_filename] = ruleItemList + + self.suppressedRules[ruleNum] = fileDict + + # Rule is added. Done. + return + + # Rule existed in the dictionary. Check for + # filename entries. + + # Get the dictionary for the rule number + fileDict = self.suppressedRules[ruleNum] + + # If the filename is not in the dict already add it + if normalized_filename not in fileDict: + ruleItemList = list() + ruleItemList.append(line_symbol) + + fileDict[normalized_filename] = ruleItemList + + # Rule is added with a file scope. Done + return + + # Rule has a matching filename. Get the rule item list. + + # Check the lists of rule items + # to see if this (lineNumber, symbolName) combination + # or None already exists. + ruleItemList = fileDict[normalized_filename] + + if line_symbol is None: + # is it already in the list? + if line_symbol not in ruleItemList: + ruleItemList.append(line_symbol) + else: + # Check the list looking for matches + matched = False + for each in ruleItemList: + if each is not None: + if (each[0] == line_symbol[0]) and (each[1] == line_symbol[1]): + matched = True + + # Append the rule item if it was not already found + if not matched: + ruleItemList.append(line_symbol) + + def isRuleSuppressed(self, file_path, linenr, ruleNum): + """ + Check to see if a rule is suppressed. + + :param ruleNum: is the rule number in hundreds format + :param file_path: File path of checked location + :param linenr: Line number of checked location + + If the rule exists in the dict then check for a filename + If the filename is None then rule is suppressed globally + for all files. + If the filename exists then look for list of + line number, symbol name tuples. If the list is None then + the rule is suppressed for the entire file + If the list of tuples exists then search the list looking for + matching line numbers. Symbol names are currently ignored + because they can include regular expressions. + TODO: Support symbol names and expression matching. + + """ + ruleIsSuppressed = False + + # Remove any prefix listed in command arguments from the filename. + filename = None + if file_path is not None: + if self.filePrefix is not None: + filename = remove_file_prefix(file_path, self.filePrefix) + else: + filename = os.path.basename(file_path) + + if ruleNum in self.suppressedRules: + fileDict = self.suppressedRules[ruleNum] + + # a file name entry of None means that the rule is suppressed + # globally + if None in fileDict: + ruleIsSuppressed = True + else: + # Does the filename match one of the names in + # the file list + if filename in fileDict: + # Get the list of ruleItems + ruleItemList = fileDict[filename] + + if None in ruleItemList: + # Entry of None in the ruleItemList means the rule is + # suppressed for all lines in the filename + ruleIsSuppressed = True + else: + # Iterate though the the list of line numbers + # and symbols looking for a match of the line + # number. Matching the symbol is a TODO: + for each in ruleItemList: + if each is not None: + if each[0] == linenr: + ruleIsSuppressed = True + + return ruleIsSuppressed + + def isRuleGloballySuppressed(self, rule_num): + """ + Check to see if a rule is globally suppressed. + :param rule_num: is the rule number in hundreds format + """ + if rule_num not in self.suppressedRules: + return False + return None in self.suppressedRules[rule_num] + + def showSuppressedRules(self): + """ + Print out rules in suppression list sorted by Rule Number + """ + print("Suppressed Rules List:") + outlist = list() + + for ruleNum in self.suppressedRules: + fileDict = self.suppressedRules[ruleNum] + + for fname in fileDict: + ruleItemList = fileDict[fname] + + for item in ruleItemList: + if item is None: + item_str = "None" + else: + item_str = str(item[0]) + + outlist.append("%s: %s: %s (%d locations suppressed)" % ( + float(ruleNum) / 100, fname, item_str, self.suppressionStats.get(ruleNum, 0))) + + for line in sorted(outlist, reverse=True): + print(" %s" % line) + + def setFilePrefix(self, prefix): + """ + Set the file prefix to ignore from files when matching + suppression files + """ + self.filePrefix = prefix + + def setSeverity(self, severity): + """ + Set the severity for all errors. + """ + self.severity = severity + + def setSuppressionList(self, suppressionlist): + num1 = 0 + num2 = 0 + rule_pattern = re.compile(r'([0-9]+).([0-9]+)') + strlist = suppressionlist.split(",") + + # build ignore list + for item in strlist: + res = rule_pattern.match(item) + if res: + num1 = int(res.group(1)) + num2 = int(res.group(2)) + ruleNum = (num1 * 100) + num2 + + self.addSuppressedRule(ruleNum) + + def report_config_error(self, location, errmsg): + cppcheck_severity = 'error' + error_id = 'config' + if self.settings.verify: + self.verify_actual.append('%s:%d %s' % (location.file, location.linenr, error_id)) + else: + cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', error_id) + + def reportError(self, location, num1, num2): + ruleNum = num1 * 100 + num2 + + if self.isRuleGloballySuppressed(ruleNum): + return + + if self.settings.verify: + self.verify_actual.append('%s:%d %d.%d' % (location.file, location.linenr, num1, num2)) + elif self.isRuleSuppressed(location.file, location.linenr, ruleNum): + # Error is suppressed. Ignore + self.suppressionStats.setdefault(ruleNum, 0) + self.suppressionStats[ruleNum] += 1 + return + else: + errorId = 'c2012-' + str(num1) + '.' + str(num2) + misra_severity = 'Undefined' + cppcheck_severity = 'style' + if self.path_premium_addon and ruleNum not in self.ruleTexts: + for line in cppcheckdata.cmd_output([self.path_premium_addon, '--cli', '--get-rule-text=' + errorId]).split('\n'): + if len(line) > 1 and not line.startswith('{'): + errmsg = line.strip() + rule = Rule(num1, num2) + rule.text = errmsg + self.ruleTexts[rule.num] = rule + break + if ruleNum in self.ruleTexts: + errmsg = self.ruleTexts[ruleNum].text + if self.ruleTexts[ruleNum].misra_severity: + misra_severity = self.ruleTexts[ruleNum].misra_severity + cppcheck_severity = self.ruleTexts[ruleNum].cppcheck_severity + elif len(self.ruleTexts) == 0: + if self.ruleText_filename is None: + errmsg = 'misra violation (use --rule-texts= to get proper output)' + else: + errmsg = 'misra violation (rule-texts-file not found: ' + self.ruleText_filename + ')' + else: + errmsg = 'misra violation %s with no text in the supplied rule-texts-file' % (ruleNum) + + if self.severity: + cppcheck_severity = self.severity + + this_violation = '{}-{}-{}-{}'.format(location.file, location.linenr, location.column, ruleNum) + + # If this is new violation then record it and show it. If not then + # skip it since it has already been displayed. + if this_violation not in self.existing_violations: + self.existing_violations.add(this_violation) + cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId, misra_severity) + + if misra_severity not in self.violations: + self.violations[misra_severity] = [] + self.violations[misra_severity].append('misra-' + errorId) + + def loadRuleTexts(self, filename): + num1 = 0 + num2 = 0 + appendixA = False + ruleText = False + expect_more = False + + Rule_pattern = re.compile(r'^Rule ([0-9]+).([0-9]+)') + severity_pattern = re.compile(r'.*[ ]*(Advisory|Required|Mandatory)$') + xA_Z_pattern = re.compile(r'^[#A-Z].*') + a_z_pattern = re.compile(r'^[a-z].*') + # Try to detect the file encoding + file_stream = None + encodings = ['ascii', 'utf-8', 'windows-1250', 'windows-1252'] + for e in encodings: + try: + file_stream = codecs.open(filename, 'r', encoding=e) + file_stream.readlines() + file_stream.seek(0) + except UnicodeDecodeError: + file_stream.close() + file_stream = None + else: + break + if not file_stream: + print('Could not find a suitable codec for "' + filename + '".') + print('If you know the codec please report it to the developers so the list can be enhanced.') + print('Trying with default codec now and ignoring errors if possible ...') + try: + file_stream = open(filename, 'rt', errors='ignore') + except TypeError: + # Python 2 does not support the errors parameter + file_stream = open(filename, 'rt') + + rule = None + have_severity = False + severity_loc = 0 + + for line in file_stream: + + line = line.replace('\r', '').replace('\n', '') + + if not appendixA: + if line.find('Appendix A') >= 0 and line.find('Summary of guidelines') >= 10: + appendixA = True + continue + if line.find('Appendix B') >= 0: + break + if len(line) == 0: + continue + + # Parse rule declaration. + res = Rule_pattern.match(line) + + if res: + have_severity = False + expect_more = False + severity_loc = 0 + num1 = int(res.group(1)) + num2 = int(res.group(2)) + rule = Rule(num1, num2) + + if not have_severity and rule is not None: + res = severity_pattern.match(line) + + if res: + rule.misra_severity = res.group(1) + have_severity = True + else: + severity_loc += 1 + + # Only look for severity on the Rule line + # or the next non-blank line after + # If it's not in either of those locations then + # assume a severity was not provided. + + if severity_loc < 2: + continue + else: + rule.misra_severity = '' + have_severity = True + + if rule is None: + continue + + # Parse continuing of rule text. + if expect_more: + if a_z_pattern.match(line): + self.ruleTexts[rule.num].text += ' ' + line + continue + + expect_more = False + continue + + # Parse beginning of rule text. + if xA_Z_pattern.match(line): + rule.text = line + self.ruleTexts[rule.num] = rule + expect_more = True + + file_stream.close() + + def verifyRuleTexts(self): + """Prints rule numbers without rule text.""" + rule_texts_rules = [] + for rule_num in self.ruleTexts: + rule = self.ruleTexts[rule_num] + rule_texts_rules.append(str(rule.num1) + '.' + str(rule.num2)) + + all_rules = list(getAddonRules() + getCppcheckRules()) + + missing_rules = list(set(all_rules) - set(rule_texts_rules)) + if len(missing_rules) == 0: + print("Rule texts are correct.") + else: + print("Missing rule texts: " + ', '.join(missing_rules)) + + def printStatus(self, *args, **kwargs): + if not self.settings.quiet: + print(*args, **kwargs) + + def executeCheck(self, rule_num, check_function, *args): + """Execute check function for a single MISRA rule. + + :param rule_num: Number of rule in hundreds format + :param check_function: Check function to execute + :param args: Check function arguments + """ + if not self.isRuleGloballySuppressed(rule_num): + check_function(*args) + # misra_cpp = ( + # 202, # misra-c2012-2.3 : misra c++2008 0-1-9 + # 203, # misra-c2012-2.3 : misra c++2008 0-1-5 + # 402, # misra-c2012-4.2 : misra c++2008 2-3-1 + # 701, # misra-c2012-7.1 : misra c++2008 2-3-1 + # 702, # misra-c2012-7.2 : misra c++2008 2-13-2 + # 1203, # misra-c2012-12.3 : misra c++2008 5-14-1 + # 1204, # misra-c2012-12.4 : misra c++2008 5-18-1 + # 1305, # misra-c2012-13.5 : misra c++2008 5-19-1 + # 1702, # misra-c2012-17.2 : misra c++2008 7-5-4 + # 1901) # misra-c2012-19.1 : misra c++2008 2-13-3 + + # if (not self.is_cpp) or rule_num in misra_cpp: + # check_function(*args) + + def parseDump(self, dumpfile, path_premium_addon=None): + def fillVerifyExpected(verify_expected, tok): + """Add expected suppressions to verify_expected list.""" + rule_re = re.compile(r'[0-9]+\.[0-9]+') + if tok.str.startswith('//') and 'TODO' not in tok.str: + for word in tok.str[2:].split(' '): + if rule_re.match(word) or word == "config": + verify_expected.append('%s:%d %s' % (tok.file, tok.linenr, word)) + + data = cppcheckdata.parsedump(dumpfile) + typeBits['CHAR'] = data.platform.char_bit + typeBits['SHORT'] = data.platform.short_bit + typeBits['INT'] = data.platform.int_bit + typeBits['LONG'] = data.platform.long_bit + typeBits['LONG_LONG'] = data.platform.long_long_bit + typeBits['POINTER'] = data.platform.pointer_bit + + if self.settings.verify: + # Add suppressions from the current file + for tok in data.rawTokens: + fillVerifyExpected(self.verify_expected, tok) + # Add suppressions from the included headers + include_re = re.compile(r'^#include [<"]([a-zA-Z0-9]+[a-zA-Z\-_./\\0-9]*)[">]$') + dump_dir = os.path.dirname(data.filename) + for conf in data.configurations: + for directive in conf.directives: + m = re.match(include_re, directive.str) + if not m: + continue + header_dump_path = os.path.join(dump_dir, m.group(1) + '.dump') + if not os.path.exists(header_dump_path): + continue + header_data = cppcheckdata.parsedump(header_dump_path) + for tok in header_data.rawTokens: + fillVerifyExpected(self.verify_expected, tok) + else: + self.printStatus('Checking ' + dumpfile + '...') + + self.is_cpp = data.files and data.files[0].endswith('.cpp') + self.printStatus('1', self.is_cpp) + + for cfgNumber, cfg in enumerate(data.iterconfigurations()): + if not self.settings.quiet: + self.printStatus('Checking %s, config %s...' % (dumpfile, cfg.name)) + + self.printStatus('2') + self.executeCheck(102, self.misra_1_2, cfg) + if not path_premium_addon: + self.executeCheck(104, self.misra_1_4, cfg) + self.executeCheck(202, self.misra_2_2, cfg) + self.executeCheck(203, self.misra_2_3, dumpfile, cfg.typedefInfo) + self.executeCheck(204, self.misra_2_4, dumpfile, cfg) + self.executeCheck(205, self.misra_2_5, dumpfile, cfg) + self.executeCheck(207, self.misra_2_7, cfg) + # data.rawTokens is same for all configurations + if cfgNumber == 0: + self.executeCheck(301, self.misra_3_1, data.rawTokens) + self.executeCheck(302, self.misra_3_2, data.rawTokens) + self.executeCheck(401, self.misra_4_1, data.rawTokens) + self.executeCheck(402, self.misra_4_2, data.rawTokens) + self.executeCheck(501, self.misra_5_1, cfg) + self.executeCheck(502, self.misra_5_2, cfg) + self.executeCheck(504, self.misra_5_4, cfg) + self.executeCheck(505, self.misra_5_5, cfg) + self.executeCheck(506, self.misra_5_6, dumpfile, cfg.typedefInfo) + self.executeCheck(507, self.misra_5_7, dumpfile, cfg) + self.executeCheck(508, self.misra_5_8, dumpfile, cfg) + self.executeCheck(509, self.misra_5_9, dumpfile, cfg) + self.executeCheck(601, self.misra_6_1, cfg) + self.executeCheck(602, self.misra_6_2, cfg) + if cfgNumber == 0: + self.executeCheck(701, self.misra_7_1, data.rawTokens) + self.executeCheck(702, self.misra_7_2, cfg) + if cfgNumber == 0: + self.executeCheck(703, self.misra_7_3, data.rawTokens) + self.executeCheck(704, self.misra_7_4, cfg) + self.executeCheck(801, self.misra_8_1, cfg) + if cfgNumber == 0: + self.executeCheck(802, self.misra_8_2, cfg, data.rawTokens) + self.executeCheck(804, self.misra_8_4, cfg) + self.executeCheck(805, self.misra_8_5, dumpfile, cfg) + self.executeCheck(806, self.misra_8_6, dumpfile, cfg) + self.executeCheck(807, self.misra_8_7, dumpfile, cfg) + self.executeCheck(808, self.misra_8_8, cfg) + self.executeCheck(809, self.misra_8_9, cfg) + self.executeCheck(810, self.misra_8_10, cfg) + self.executeCheck(811, self.misra_8_11, cfg) + self.executeCheck(812, self.misra_8_12, cfg) + if cfgNumber == 0: + self.executeCheck(814, self.misra_8_14, data.rawTokens) + self.executeCheck(902, self.misra_9_2, cfg) + self.executeCheck(903, self.misra_9_3, cfg) + self.executeCheck(904, self.misra_9_4, cfg) + if cfgNumber == 0: + self.executeCheck(905, self.misra_9_5, cfg, data.rawTokens) + if not path_premium_addon: + self.executeCheck(1001, self.misra_10_1, cfg) + self.executeCheck(1002, self.misra_10_2, cfg) + self.executeCheck(1003, self.misra_10_3, cfg) + self.executeCheck(1004, self.misra_10_4, cfg) + self.executeCheck(1005, self.misra_10_5, cfg) + self.executeCheck(1006, self.misra_10_6, cfg) + self.executeCheck(1007, self.misra_10_7, cfg) + self.executeCheck(1008, self.misra_10_8, cfg) + self.executeCheck(1101, self.misra_11_1, cfg) + self.executeCheck(1102, self.misra_11_2, cfg) + self.executeCheck(1103, self.misra_11_3, cfg) + self.executeCheck(1104, self.misra_11_4, cfg) + self.executeCheck(1105, self.misra_11_5, cfg) + self.executeCheck(1106, self.misra_11_6, cfg) + self.executeCheck(1107, self.misra_11_7, cfg) + self.executeCheck(1108, self.misra_11_8, cfg) + self.executeCheck(1109, self.misra_11_9, cfg) + if cfgNumber == 0: + self.executeCheck(1201, self.misra_12_1_sizeof, data.rawTokens) + self.executeCheck(1201, self.misra_12_1, cfg) + self.executeCheck(1202, self.misra_12_2, cfg) + self.executeCheck(1203, self.misra_12_3, cfg) + self.executeCheck(1204, self.misra_12_4, cfg) + self.executeCheck(1301, self.misra_13_1, cfg) + self.executeCheck(1303, self.misra_13_3, cfg) + self.executeCheck(1304, self.misra_13_4, cfg) + self.executeCheck(1305, self.misra_13_5, cfg) + self.executeCheck(1306, self.misra_13_6, cfg) + self.executeCheck(1401, self.misra_14_1, cfg) + self.executeCheck(1402, self.misra_14_2, cfg) + self.executeCheck(1404, self.misra_14_4, cfg) + self.executeCheck(1501, self.misra_15_1, cfg) + self.executeCheck(1502, self.misra_15_2, cfg) + self.executeCheck(1503, self.misra_15_3, cfg) + self.executeCheck(1504, self.misra_15_4, cfg) + self.executeCheck(1505, self.misra_15_5, cfg) + if cfgNumber == 0: + self.executeCheck(1506, self.misra_15_6, data.rawTokens) + self.executeCheck(1507, self.misra_15_7, cfg) + self.executeCheck(1601, self.misra_16_1, cfg) + self.executeCheck(1602, self.misra_16_2, cfg) + if cfgNumber == 0: + self.executeCheck(1603, self.misra_16_3, data.rawTokens) + self.executeCheck(1604, self.misra_16_4, cfg) + self.executeCheck(1605, self.misra_16_5, cfg) + self.executeCheck(1606, self.misra_16_6, cfg) + self.executeCheck(1607, self.misra_16_7, cfg) + self.executeCheck(1701, self.misra_17_1, cfg) + self.executeCheck(1702, self.misra_17_2, cfg) + self.executeCheck(1703, self.misra_17_3, cfg) + self.misra_config(cfg) + if cfgNumber == 0: + self.executeCheck(1706, self.misra_17_6, data.rawTokens) + self.executeCheck(1707, self.misra_17_7, cfg) + self.executeCheck(1708, self.misra_17_8, cfg) + self.executeCheck(1804, self.misra_18_4, cfg) + self.executeCheck(1805, self.misra_18_5, cfg) + self.executeCheck(1807, self.misra_18_7, cfg) + self.executeCheck(1808, self.misra_18_8, cfg) + self.executeCheck(1902, self.misra_19_2, cfg) + self.executeCheck(2001, self.misra_20_1, cfg) + self.executeCheck(2002, self.misra_20_2, cfg) + self.executeCheck(2003, self.misra_20_3, cfg) + self.executeCheck(2004, self.misra_20_4, cfg) + self.executeCheck(2005, self.misra_20_5, cfg) + self.executeCheck(2007, self.misra_20_7, cfg) + self.executeCheck(2008, self.misra_20_8, cfg) + self.executeCheck(2009, self.misra_20_9, cfg) + self.executeCheck(2010, self.misra_20_10, cfg) + self.executeCheck(2011, self.misra_20_11, cfg) + self.executeCheck(2012, self.misra_20_12, cfg) + self.executeCheck(2013, self.misra_20_13, cfg) + self.executeCheck(2014, self.misra_20_14, cfg) + self.executeCheck(2101, self.misra_21_1, cfg) + self.executeCheck(2102, self.misra_21_2, cfg) + self.executeCheck(2103, self.misra_21_3, cfg) + self.executeCheck(2104, self.misra_21_4, cfg) + self.executeCheck(2105, self.misra_21_5, cfg) + self.executeCheck(2106, self.misra_21_6, cfg) + self.executeCheck(2107, self.misra_21_7, cfg) + self.executeCheck(2108, self.misra_21_8, cfg) + self.executeCheck(2109, self.misra_21_9, cfg) + self.executeCheck(2110, self.misra_21_10, cfg) + self.executeCheck(2111, self.misra_21_11, cfg) + self.executeCheck(2112, self.misra_21_12, cfg) + self.executeCheck(2114, self.misra_21_14, cfg) + self.executeCheck(2115, self.misra_21_15, cfg) + self.executeCheck(2116, self.misra_21_16, cfg) + self.executeCheck(2119, self.misra_21_19, cfg) + self.executeCheck(2120, self.misra_21_20, cfg) + self.executeCheck(2121, self.misra_21_21, cfg) + # 22.4 is already covered by Cppcheck writeReadOnlyFile + self.executeCheck(2205, self.misra_22_5, cfg) + self.executeCheck(2207, self.misra_22_7, cfg) + self.executeCheck(2208, self.misra_22_8, cfg) + self.executeCheck(2209, self.misra_22_9, cfg) + self.executeCheck(2210, self.misra_22_10, cfg) + + def read_ctu_info_line(self, line): + if not line.startswith('{'): + return None + try: + ctu_info = json.loads(line) + except json.decoder.JSONDecodeError: + return None + if 'summary' not in ctu_info: + return None + if 'data' not in ctu_info: + return None + return ctu_info + + def analyse_ctu_info(self, ctu_info_files): + all_typedef_info = {} + all_tagname_info = {} + all_macro_info = {} + all_external_identifiers_decl = {} + all_external_identifiers_def = {} + all_internal_identifiers = {} + all_local_identifiers = {} + all_usage_files = {} + + from cppcheckdata import Location + + def is_different_location(loc1, loc2): + return loc1['file'] != loc2['file'] or loc1['line'] != loc2['line'] + + def is_different_file(loc1, loc2): + return loc1['file'] != loc2['file'] + + try: + for filename in ctu_info_files: + for line in open(filename, 'rt'): + s = self.read_ctu_info_line(line) + if s is None: + continue + summary_type = s.get('summary', '') + summary_data = s.get('data', None) + + if summary_type == 'MisraTypedefInfo': + for new_typedef_info in summary_data: + key = new_typedef_info['name'] + existing_typedef_info = all_typedef_info.get(key, None) + if existing_typedef_info: + if is_different_location(existing_typedef_info, new_typedef_info): + self.reportError(Location(existing_typedef_info), 5, 6) + self.reportError(Location(new_typedef_info), 5, 6) + else: + existing_typedef_info['used'] = existing_typedef_info['used'] or new_typedef_info['used'] + else: + all_typedef_info[key] = new_typedef_info + + if summary_type == 'MisraTagName': + for new_tagname_info in summary_data: + key = new_tagname_info['name'] + existing_tagname_info = all_tagname_info.get(key, None) + if existing_tagname_info: + if is_different_location(existing_tagname_info, new_tagname_info): + self.reportError(Location(existing_tagname_info), 5, 7) + self.reportError(Location(new_tagname_info), 5, 7) + else: + existing_tagname_info['used'] = existing_tagname_info['used'] or new_tagname_info['used'] + else: + all_tagname_info[key] = new_tagname_info + + if summary_type == 'MisraMacro': + for new_macro in summary_data: + key = new_macro['name'] + existing_macro = all_macro_info.get(key, None) + if existing_macro: + existing_macro['used'] = existing_macro['used'] or new_macro['used'] + else: + all_macro_info[key] = new_macro + + if summary_type == 'MisraExternalIdentifiers': + for s in sorted(summary_data, key=lambda d: "%s %s %s" %(d['file'],d['line'], d['column'] )): + is_declaration = s['decl'] + if is_declaration: + all_external_identifiers = all_external_identifiers_decl + else: + all_external_identifiers = all_external_identifiers_def + + name = s['name'] + if name in all_external_identifiers: + if is_declaration and is_different_location(s, all_external_identifiers[name]): + self.reportError(Location(s), 8, 5) + self.reportError(Location(all_external_identifiers[name]), 8, 5) + elif is_different_file(s, all_external_identifiers[name]): + self.reportError(Location(s), 8, 6) + self.reportError(Location(all_external_identifiers[name]), 8, 6) + all_external_identifiers[name] = s + + if summary_type == 'MisraInternalIdentifiers': + for s in summary_data: + if s['name'] in all_internal_identifiers: + if not s['inlinefunc'] or s['file'] != all_internal_identifiers[s['name']]['file']: + self.reportError(Location(s), 5, 9) + self.reportError(Location(all_internal_identifiers[s['name']]), 5, 9) + all_internal_identifiers[s['name']] = s + + if summary_type == 'MisraLocalIdentifiers': + for s in summary_data: + all_local_identifiers[s['name']] = s + + if summary_type == 'MisraUsage': + for s in summary_data: + if s['name'] in all_usage_files: + all_usage_files[s['name']].append(s['file']) + else: + all_usage_files[s['name']] = [s['file']] + + except FileNotFoundError: + return + + unused_typedefs = [tdi for tdi in all_typedef_info.values() if not tdi['used']] + for tdi in unused_typedefs: + self.reportError(Location(tdi), 2, 3) + + unused_tags = [tag for tag in all_tagname_info.values() if not tag['used']] + for tag in unused_tags: + self.reportError(Location(tag), 2, 4) + + unused_macros = [m for m in all_macro_info.values() if not m['used']] + for m in unused_macros: + self.reportError(Location(m), 2, 5) + + all_external_identifiers = all_external_identifiers_decl + all_external_identifiers.update(all_external_identifiers_def) + for name, external_identifier in all_external_identifiers.items(): + internal_identifier = all_internal_identifiers.get(name) + if internal_identifier: + self.reportError(Location(internal_identifier), 5, 8) + self.reportError(Location(external_identifier), 5, 8) + + local_identifier = all_local_identifiers.get(name) + if local_identifier: + self.reportError(Location(local_identifier), 5, 8) + self.reportError(Location(external_identifier), 5, 8) + + for name, files in all_usage_files.items(): + #print('%s:%i' % (name, count)) + count = len(files) + if count != 1 or name not in all_external_identifiers_def: + continue + if files[0] != Location(all_external_identifiers_def[name]).file: + continue + if name in all_external_identifiers: + self.reportError(Location(all_external_identifiers[name]), 8, 7) + +RULE_TEXTS_HELP = '''Path to text file of MISRA rules + +If you have the tool 'pdftotext' you might be able +to generate this textfile with such command: + + pdftotext MISRA_C_2012.pdf MISRA_C_2012.txt + +Otherwise you can more or less copy/paste the chapter +Appendix A Summary of guidelines +from the MISRA pdf. You can buy the MISRA pdf from +http://www.misra.org.uk/ + +Format: + +<..arbitrary text..> +Appendix A Summary of guidelines +Rule 1.1 Required +Rule text for 1.1 +continuation of rule text for 1.1 +Rule 1.2 Mandatory +Rule text for 1.2 +continuation of rule text for 1.2 +<...> + +''' + +SUPPRESS_RULES_HELP = '''MISRA rules to suppress (comma-separated) + +For example, if you'd like to suppress rules 15.1, 11.3, +and 20.13, run: + + python misra.py --suppress-rules 15.1,11.3,20.13 ... + +''' + + +def get_args_parser(): + """Generates list of command-line arguments acceptable by misra.py script.""" + parser = cppcheckdata.ArgumentParser() + parser.add_argument("--rule-texts", type=str, help=RULE_TEXTS_HELP) + parser.add_argument("--verify-rule-texts", + help="Verify that all supported rules texts are present in given file and exit.", + action="store_true") + parser.add_argument("--suppress-rules", type=str, help=SUPPRESS_RULES_HELP) + parser.add_argument("--no-summary", help="Hide summary of violations", action="store_true") + parser.add_argument("--show-suppressed-rules", help="Print rule suppression list", action="store_true") + parser.add_argument("-P", "--file-prefix", type=str, help="Prefix to strip when matching suppression file rules") + parser.add_argument("-generate-table", help=argparse.SUPPRESS, action="store_true") + parser.add_argument("-verify", help=argparse.SUPPRESS, action="store_true") + parser.add_argument("--severity", type=str, help="Set a custom severity string, for example 'error' or 'warning'. ") + return parser + + +def main(): + parser = get_args_parser() + args = parser.parse_args() + settings = MisraSettings(args) + checker = MisraChecker(settings) + + checker.path_premium_addon = cppcheckdata.get_path_premium_addon() + + if args.generate_table: + generateTable() + sys.exit(0) + + # args.rule_texts = "/home/jensb/pull_requests/3d_magnetic/magnetic-3d-sensor-tlx493d/misra.txt" + if not args.rule_texts: + args.rule_texts = os.environ.get('RULE_TEXTS') + + if args.rule_texts: + filename = os.path.expanduser(args.rule_texts) + filename = os.path.normpath(filename) + checker.ruleText_filename = filename + if os.path.isfile(filename): + checker.loadRuleTexts(filename) + if args.verify_rule_texts: + checker.verifyRuleTexts() + sys.exit(0) + else: + if args.verify_rule_texts: + print('Fatal error: file is not found: ' + filename) + sys.exit(1) + + + if args.verify_rule_texts and not args.rule_texts: + print("Error: Please specify rule texts file with --rule-texts=") + sys.exit(1) + + if args.suppress_rules: + checker.setSuppressionList(args.suppress_rules) + + if args.file_prefix: + checker.setFilePrefix(args.file_prefix) + + dump_files, ctu_info_files = cppcheckdata.get_files(args) + + if (not dump_files) and (not ctu_info_files): + if not args.quiet: + print("No input files.") + sys.exit(0) + + if args.severity: + checker.setSeverity(args.severity) + + for item in dump_files: + checker.parseDump(item,checker.path_premium_addon) + + if settings.verify: + verify_expected = checker.get_verify_expected() + verify_actual = checker.get_verify_actual() + + exitCode = 0 + for expected in verify_expected: + if expected not in verify_actual: + print('Expected but not seen: ' + expected) + exitCode = 1 + for actual in verify_actual: + if actual not in verify_expected: + print('Not expected: ' + actual) + exitCode = 1 + + # Existing behavior of verify mode is to exit + # on the first un-expected output. + # TODO: Is this required? or can it be moved to after + # all input files have been processed + if exitCode != 0: + sys.exit(exitCode) + + checker.analyse_ctu_info(ctu_info_files) + + if settings.verify: + sys.exit(exitCode) + + number_of_violations = len(checker.get_violations()) + if number_of_violations > 0: + if settings.show_summary: + print("\nMISRA rules violations found:\n\t%s\n" % ( + "\n\t".join(["%s: %d" % (viol, len(checker.get_violations(viol))) for viol in + checker.get_violation_types()]))) + + rules_violated = {} + for severity, ids in checker.get_violations(): + for misra_id in ids: + rules_violated[misra_id] = rules_violated.get(misra_id, 0) + 1 + print("MISRA rules violated:") + convert = lambda text: int(text) if text.isdigit() else 0 + misra_sort = lambda key: [convert(c) for c in re.split(r'[\.-]([0-9]*)', key)] + for misra_id in sorted(rules_violated.keys(), key=misra_sort): + res = re.match(r'misra-c2012-([0-9]+)\\.([0-9]+)', misra_id) + if res is None: + num = 0 + else: + num = int(res.group(1)) * 100 + int(res.group(2)) + severity = '-' + if num in checker.ruleTexts: + severity = checker.ruleTexts[num].cppcheck_severity + print("\t%15s (%s): %d" % (misra_id, severity, rules_violated[misra_id])) + + if args.show_suppressed_rules: + checker.showSuppressedRules() + + +if __name__ == '__main__': + main() + sys.exit(cppcheckdata.EXIT_CODE) diff --git a/libraries/CAN/Makefile b/libraries/CAN/Makefile index 87f800fa..08f9d452 100644 --- a/libraries/CAN/Makefile +++ b/libraries/CAN/Makefile @@ -1,20 +1,19 @@ FQBN ?= PORT ?= TESTS ?= +UNITY_PATH ?= $(info FQBN : $(FQBN)) $(info PORT : $(PORT)) +$(info UNITY_PATH : $(UNITY_PATH)) -TESTS_CONNECTED=-DTEST_CAN_CONNECTED - +TESTS_CONNECTED=-DTEST_CAN_CONNECTED TESTS_NOT_CONNECTED=-DTEST_CAN - -CAN_connected: TESTS=-DTEST_CAN -DTEST_CAN_CONNECTED CAN: TESTS=-DTEST_CAN - -CAN_connected CAN: unity flash +CAN_connected: TESTS=-DTEST_CAN -DTEST_CAN_CONNECTED +CAN_connected CAN: unity_corelibs flash test_all: TESTS=$(TESTS_CONNECTED) $(TESTS_NOT_CONNECTED) test_connected: TESTS=$(TESTS_CONNECTED) @@ -22,25 +21,22 @@ test: TESTS=$(TESTS_NOT_CONNECTED) test_all \ test_connected \ -test: unity flash +test: unity_corelibs flash -EXAMPLES = CANReceiver CANReceiverCallback CANSender - +EXAMPLES= CANReceiver CANReceiverCallback CANSender CANLoopBack clean: -rm -rf build/* - arduino: clean mkdir -p build -# copy library file -# cp -r libraries/CAN/* build -# find src -name '*.[hc]*' -a \( \! -name 'main*' \) -print -exec cp {} build \; - - +# copy library files (not needed for bundled libraries) +# cp -r src/* build +# find src -name '*.[hc]*' -a \( \! -name '*mtb*' \) -print -exec cp {} build \; +.PHONY: CANReceiver CANReceiverCallback CANSender CANLoopBack CANReceiver: arduino - cp examples/CANReceiver/CANReceiver.ino build/build.ino + cp examples/CANReceiver/CANReceiver.ino build/build.ino CANReceiverCallback: arduino cp examples/CANReceiverCallback/CANReceiverCallback.ino build/build.ino @@ -48,20 +44,20 @@ CANReceiverCallback: arduino CANSender: arduino cp examples/CANSender/CANSender.ino build/build.ino -# master1: -# ... +CANLoopBack: arduino + cp examples/CANSender/CANLoopBack.ino build/build.ino -# slave1: -# ... +# install Unity from https://www.throwtheswitch.org/unity or git +unity_corelibs: arduino +ifeq ($(UNITY_PATH),) + $(error "Must set variable UNITY_PATH in order to be able to compile Arduino unit tests !") +else + find $(UNITY_PATH) -name '*.[hc]' \( -path '*extras*' -a -path '*src*' -or -path '*src*' -a \! -path '*example*' \) -exec \cp {} build \; + find test/unit -name '*.[hc]*' -a \! -path '*mtb*' -a \! -path '*applibs*' -exec \cp {} build \; + cp test/unit/src/framework/arduino/corelibs/Test_main.ino build/build.ino +endif -# old unity setup part. -# TODO: Add unit test -#unity: arduino -# cp -r test/unit/Unity/*.[hc] build -# cp test/unit/src/framework/arduino/Test_*.[hc]* build -# cp test/unit/src/framework/arduino/unity_ifx.cpp build -# cp test/unit/src/framework/arduino/Test_main.ino build/build.ino # For WSL and Windows : @@ -72,23 +68,35 @@ ifeq ($(FQBN),) else # CAUTION : only use '=' when assigning values to vars, not '+=' arduino-cli.exe compile --clean --log --warnings all --fqbn $(FQBN) --build-property "compiler.c.extra_flags=\"-DUNITY_INCLUDE_CONFIG_H=1\"" \ - --build-property compiler.cpp.extra_flags="$(TESTS)" \ - --build-property compiler.ar.cmd=arm-none-eabi-gcc-ar \ - --build-property compiler.libraries.ldflags=-lstdc++ \ - --build-property compiler.arm.cmsis.path="-isystem{compiler.xmclib_include.path}/XMCLib_CAN_v4_3_0/inc -isystem{compiler.xmclib_include.path}/XMCLib/inc -isystem{compiler.dsp_include.path} -isystem{compiler.nn_include.path} -isystem{compiler.cmsis_include.path} -isystem{compiler.xmclib_include.path}/LIBS -isystem{build.variant.path} -isystem{build.variant.config_path}" \ - --build-property compiler.usb.path="-isystem{runtime.platform.path}/cores/usblib -isystem{runtime.platform.path}/cores/usblib/Common -isystem{runtime.platform.path}/cores/usblib/Class -isystem{runtime.platform.path}/cores/usblib/Class/Common -isystem{runtime.platform.path}/cores/usblib/Class/Device -isystem{runtime.platform.path}/cores/usblib/Core -isystem{runtime.platform.path}/cores/usblib/Core/XMC4000" \ - build + --build-property compiler.cpp.extra_flags="$(TESTS)" build endif -upload: +compileLTO: +ifeq ($(FQBN),) + $(error "Must set variable FQBN in order to be able to compile Arduino sketches !") +else +# compiler.c.extra_flags : switch to -std=c23 whenever XMCLib is conforming; currently neither c99 nor c11 work ! +# CAUTION : only use '=' when assigning values to vars, not '+=' + arduino-cli.exe compile --clean --log --warnings all --fqbn $(FQBN) \ + --build-property compiler.c.extra_flags="\"-DUNITY_INCLUDE_CONFIG_H=1\" -DNDEBUG -flto -fno-fat-lto-objects -Wextra -Wall -Wfloat-equal -Wconversion -Wredundant-decls -Wswitch-default -Wdouble-promotion -Wpedantic -Wunreachable-code -fanalyzer -std=c20 " \ + --build-property compiler.cpp.extra_flags="$(TESTS) -DNDEBUG -flto -fno-fat-lto-objects -Wextra -Wall -Wfloat-equal -Wconversion -Wredundant-decls -Wswitch-default -Wdouble-promotion -Wpedantic -Wunreachable-code -fanalyzer -std=c++20 " \ + --build-property compiler.ar.cmd=arm-none-eabi-gcc-ar \ + --build-property compiler.libraries.ldflags=-lstdc++ \ + --build-property compiler.arm.cmsis.path="-isystem{compiler.xmclib_include.path}/XMCLib/inc -isystem{compiler.dsp_include.path} -isystem{compiler.nn_include.path} -isystem{compiler.cmsis_include.path} -isystem{compiler.xmclib_include.path}/LIBS -isystem{build.variant.path} -isystem{build.variant.config_path}" \ + --build-property compiler.usb.path="-isystem{runtime.platform.path}/cores/usblib -isystem{runtime.platform.path}/cores/usblib/Common -isystem{runtime.platform.path}/cores/usblib/Class -isystem{runtime.platform.path}/cores/usblib/Class/Common -isystem{runtime.platform.path}/cores/usblib/Class/Device -isystem{runtime.platform.path}/cores/usblib/Core -isystem{runtime.platform.path}/cores/usblib/Core/XMC4000" \ + build +endif + + +upload: compile ifeq ($(PORT),) $(error "Must set variable PORT (Windows port naming convention, ie COM16) in order to be able to flash Arduino sketches !") endif ifeq ($(FQBN),) $(error "Must set variable FQBN in order to be able to flash Arduino sketches !") else - arduino-cli.exe upload -p $(PORT) --fqbn $(FQBN) build + arduino-cli.exe upload -p $(PORT) --fqbn $(FQBN) --verbose build endif @@ -102,5 +110,6 @@ endif ifeq ($(FQBN),) $(error "Must set variable FQBN in order to be able to flash Arduino sketches !") else - arduino-cli.exe monitor -c baudrate=9600 -p $(PORT) --fqbn $(FQBN) + arduino-cli.exe monitor -c baudrate=115200 -p $(PORT) --fqbn $(FQBN) endif + diff --git a/libraries/CAN/Makefile.codechecks.mk b/libraries/CAN/Makefile.codechecks.mk new file mode 100644 index 00000000..77b02818 --- /dev/null +++ b/libraries/CAN/Makefile.codechecks.mk @@ -0,0 +1,63 @@ + +WIN_USER ?= + + + +#################################### clang-format code formatter +### Need to install clang-tidy +### You may want to copy the config to your working directory to use the common settings. +### cp config/clang-format/.clang-format . + +clang-format: + clang-format -i $(shell find src -name CAN\*.[hc]pp\*) + + + +#################################### clang-tidy static code checker //TODO: how to implement this for core library? +### Need to install clang-tidy + +clang-tidy-test: C_CPP_SOURCES = $(shell find test -name \*.[hc]pp\*) +clang-tidy-src: C_CPP_SOURCES = $(shell find src -name \*.[hc]pp\*) + +clang-tidy-test clang-tidy-src : clang-tidy + +clang-tidy: +ifeq ($(WIN_USER),) + $(warn "Must set variable WIN_USER in order to include XMCLib files in clang-tidy analysis !") + clang-tidy --config-file=../../config/clang-tidy/.clang-tidy -header-filter=. --extra-arg="-Isrc/framework/arduino" --extra-arg="-Itest/unit/src/framework/arduino/corelibs" $(C_CPP_SOURCES) -- +else + $(info $(C_CPP_SOURCES)) + clang-tidy --config-file=../../config/clang-tidy/.clang-tidy -header-filter=. --extra-arg="-Isrc/framework/arduino" --extra-arg="-Itest/unit/src/framework/arduino/corelibs" --extra-arg="-I/mnt/c/Users/$(WIN_USER)/AppData/Local/Arduino15/packages/Infineon/hardware/xmc/3.1.0/cores" $(C_CPP_SOURCES) -- +endif + + + +#################################### scan-build static code checker +### Need to install scan-build +### After executing the scan-build target run the command scan-view as printed in the scan-build output last line. +### Or use firefox directly. +scan-build: + scan-build -o scan-build-reports make -f Makefile.codechecks.mk foo +# firefox scan-build-reports/ + +foo: + $(CC) -c -Wextra -Wall -Wfloat-equal -Wconversion -Wredundant-decls -Wswitch-default -Wdouble-promotion -Wpedantic -Wunreachable-code -std=c++20 config/sca/foo.cpp -o foo.o + + + +#################################### cppcheck static code checker +### Requirement : install cppcheck from https://github.com/danmar/cppcheck +cppcheck: C_CPP_SOURCES = ./config/sca + +CPPCHECK_PATH = ~/cppcheck/cppcheck.danmar + +cppcheck: + export RULE_TEXTS=config/cppcheck/misra.txt + $(CPPCHECK_PATH)/cppcheck -i build -i doc -i examples -i results -i reports_hml -i src -i test \ + -I$(C_CPP_SOURCES) \ + --checkers-report=cppcheck.checkers --check-level=exhaustive --xml --enable=all --inconclusive \ + --addon=config/cppcheck/misra_local.py --addon=misc \ + --max-configs=100 ./ 2> ./err.xml + $(CPPCHECK_PATH)/htmlreport/cppcheck-htmlreport --file=err.xml --title=TLx493D --report-dir=cppcheck_reports --source-dir=. + firefox cppcheck_reports/index.html + diff --git a/libraries/CAN/Makefile.test b/libraries/CAN/Makefile.test index 44e22ff2..9c27c5b8 100644 --- a/libraries/CAN/Makefile.test +++ b/libraries/CAN/Makefile.test @@ -22,6 +22,6 @@ make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 CANLoopBack flash monitor ### Unit tests -make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM20 CAN monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN monitor -make FQBN=Infineon:xmc:XMC1100_XMC2GO PORT=COM21 CAN_connected monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN_connected monitor diff --git a/libraries/CAN/src/CANController.cpp b/libraries/CAN/src/CANController.cpp index df4bb3f9..e0080311 100644 --- a/libraries/CAN/src/CANController.cpp +++ b/libraries/CAN/src/CANController.cpp @@ -1,101 +1,93 @@ // Copyright (c) Sandeep Mistry. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license -// information. +// Licensed under the MIT license. See LICENSE file in the project root for full +// license information. #include "CANController.h" CANControllerClass::CANControllerClass() : _onReceive(NULL), - _packetBegun(false), - _txId(-1), - _txExtended(-1), - _txRtr(false), - _txDlc(0), + _packetBegun(false), _txId(-1), _txExtended(-1), _txRtr(false), _txDlc(0), _txLength(0), - _rxId(-1), - _rxExtended(false), - _rxRtr(false), - _rxDlc(0), - _rxLength(0), + _rxId(-1), _rxExtended(false), _rxRtr(false), _rxDlc(0), _rxLength(0), _rxIndex(0) { - // overide Stream timeout value - setTimeout(0); + // overide Stream timeout value + setTimeout(0); } CANControllerClass::~CANControllerClass() {} int CANControllerClass::begin(long /*baudRate*/) { - _packetBegun = false; - _txId = -1; - _txRtr = false; - _txDlc = 0; - _txLength = 0; - - _rxId = -1; - _rxRtr = false; - _rxDlc = 0; - _rxLength = 0; - _rxIndex = 0; - - return 1; + _packetBegun = false; + _txId = -1; + _txRtr = false; + _txDlc = 0; + _txLength = 0; + + _rxId = -1; + _rxRtr = false; + _rxDlc = 0; + _rxLength = 0; + _rxIndex = 0; + + return 1; } void CANControllerClass::end() {} int CANControllerClass::beginPacket(int id, int dlc, bool rtr) { - if (id < 0 || id > 0x7FF) { - return 0; - } + if (id < 0 || id > 0x7FF) { + return 0; + } - if (dlc > 8) { - return 0; - } + if (dlc > 8) { + return 0; + } - _packetBegun = true; - _txId = id; - _txExtended = false; - _txRtr = rtr; - _txDlc = dlc; - _txLength = 0; + _packetBegun = true; + _txId = id; + _txExtended = false; + _txRtr = rtr; + _txDlc = dlc; + _txLength = 0; - memset(_txData, 0x00, sizeof(_txData)); + memset(_txData, 0x00, sizeof(_txData)); - return 1; + return 1; } int CANControllerClass::beginExtendedPacket(long id, int dlc, bool rtr) { - if (id < 0 || id > 0x1FFFFFFF) { - return 0; - } + if (id < 0 || id > 0x1FFFFFFF) { + return 0; + } - if (dlc > 8) { - return 0; - } + if (dlc > 8) { + return 0; + } - _packetBegun = true; - _txId = id; - _txExtended = true; - _txRtr = rtr; - _txDlc = dlc; - _txLength = 0; + _packetBegun = true; + _txId = id; + _txExtended = true; + _txRtr = rtr; + _txDlc = dlc; + _txLength = 0; - memset(_txData, 0x00, sizeof(_txData)); + memset(_txData, 0x00, sizeof(_txData)); - return 1; + return 1; } int CANControllerClass::endPacket() { - if (!_packetBegun) { - return 0; - } - _packetBegun = false; + if (!_packetBegun) { + return 0; + } + _packetBegun = false; - if (_txDlc >= 0) { - _txLength = _txDlc; - } + if (_txDlc >= 0) { + _txLength = _txDlc; + } - return 1; + return 1; } int CANControllerClass::parsePacket() { return 0; } @@ -108,44 +100,48 @@ bool CANControllerClass::packetRtr() { return _rxRtr; } int CANControllerClass::packetDlc() { return _rxDlc; } -size_t CANControllerClass::write(uint8_t byte) { return write(&byte, sizeof(byte)); } +size_t CANControllerClass::write(uint8_t byte) { + return write(&byte, sizeof(byte)); +} size_t CANControllerClass::write(const uint8_t *buffer, size_t size) { - if (!_packetBegun) { - return 0; - } + if (!_packetBegun) { + return 0; + } - if (size > (sizeof(_txData) - _txLength)) { - size = sizeof(_txData) - _txLength; - } + if (size > (sizeof(_txData) - _txLength)) { + size = sizeof(_txData) - _txLength; + } - memcpy(&_txData[_txLength], buffer, size); - _txLength += size; + memcpy(&_txData[_txLength], buffer, size); + _txLength += size; - return size; + return size; } int CANControllerClass::available() { return (_rxLength - _rxIndex); } int CANControllerClass::read() { - if (!available()) { - return -1; - } + if (!available()) { + return -1; + } - return _rxData[_rxIndex++]; + return _rxData[_rxIndex++]; } int CANControllerClass::peek() { - if (!available()) { - return -1; - } + if (!available()) { + return -1; + } - return _rxData[_rxIndex]; + return _rxData[_rxIndex]; } void CANControllerClass::flush() {} -void CANControllerClass::onReceive(void (*callback)(int)) { _onReceive = callback; } +void CANControllerClass::onReceive(void (*callback)(int)) { + _onReceive = callback; +} int CANControllerClass::filter(int /*id*/, int /*mask*/) { return 0; } diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index 69ef539f..55ea1d81 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -5,9 +5,9 @@ * frame*/ XMC_CAN_MO_t CAN_msg_rx = { .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO0, - {0xFF, XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode + {0xFF, XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} - {0x000, 1U}, // {can_id_mask, can_ide_mask} + {0x000, 1U}, // {can_id_mask, can_ide_mask} .can_data_length = 0U, .can_mo_type = XMC_CAN_MO_TYPE_RECMSGOBJ, }; @@ -15,9 +15,9 @@ XMC_CAN_MO_t CAN_msg_rx = { /* CAN Transmit Message Object definition */ XMC_CAN_MO_t CAN_msg_tx = { .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO1, - {0xFF, XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode + {0xFF, XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} - {0x7FFU, 1U}, // {can_id_mask, can_ide_mask} + {0x7FFU, 1U}, // {can_id_mask, can_ide_mask} .can_data_length = 0U, .can_mo_type = XMC_CAN_MO_TYPE_TRANSMSGOBJ, }; @@ -32,87 +32,96 @@ CANXMC::~CANXMC() {} int CANXMC::setIdentifier(long id) { // TODO: delete in the future! // figure out filtering problem for xmc4700 - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); - return 0; + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); + return 0; }; /** * @brief Initializes the CAN interface with the specified baudrate. * - * This function configures the CAN bit time, enables the CAN node, and sets up the necessary pin - * configurations. + * This function configures the CAN bit time, enables the CAN node, and sets up + * the necessary pin configurations. * - * @param baudrate The desired baudrate for the CAN interface. The default value is 500,000 bps. + * @param baudrate The desired baudrate for the CAN interface. The default value + * is 500,000 bps. * @return Returns 1 if the initialization is successful, 0 otherwise. */ int CANXMC::begin(long baudrate /*= 500e3*/) { - /* CAN bit time configuration*/ - XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { - .can_frequency = _XMC_CAN_config->can_frequency, - .baudrate = (uint32_t)baudrate, - .sample_point = (uint16_t)(80 * 100), - .sjw = (uint16_t)1, - }; - - XMC_CAN_Enable(CAN_xmc); - - /* Configuration of CAN Node and enable the clock */ - XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, _XMC_CAN_config->can_frequency); - if (XMC_CAN_STATUS_SUCCESS == XMC_CAN_NODE_NominalBitTimeConfigureEx( - _XMC_CAN_config->can_node, &CAN_NODE_bit_time_config)) { - XMC_CAN_NODE_EnableConfigurationChange(_XMC_CAN_config->can_node); - XMC_CAN_NODE_SetInitBit(_XMC_CAN_config->can_node); - XMC_CAN_NODE_ReSetAnalyzerMode(_XMC_CAN_config->can_node); - - /* Configure CAN NODE input pin */ - XMC_GPIO_Init(_XMC_CAN_config->tx.port, _XMC_CAN_config->tx.pin, - &(_XMC_CAN_config->tx_config)); - XMC_GPIO_SetHardwareControl(_XMC_CAN_config->tx.port, _XMC_CAN_config->tx.pin, - XMC_GPIO_HWCTRL_DISABLED); - XMC_GPIO_Init(_XMC_CAN_config->rx.port, _XMC_CAN_config->rx.pin, - &(_XMC_CAN_config->rx_config)); - XMC_GPIO_SetHardwareControl(_XMC_CAN_config->rx.port, _XMC_CAN_config->rx.pin, - XMC_GPIO_HWCTRL_DISABLED); - XMC_CAN_NODE_SetReceiveInput(_XMC_CAN_config->can_node, _XMC_CAN_config->node_input); - - /* Allocate the rx and tx message object*/ - XMC_CAN_MO_Config(&CAN_msg_rx); - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 0); - XMC_CAN_MO_Config(&CAN_msg_tx); - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 1); - - /* Message object accepts the reception of both, standard and extended - * frames */ - XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); + /* CAN bit time configuration*/ + XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { + .can_frequency = _XMC_CAN_config->can_frequency, + .baudrate = (uint32_t)baudrate, + .sample_point = (uint16_t)(80 * 100), + .sjw = (uint16_t)1, + }; + + XMC_CAN_Enable(CAN_xmc); + + /* Configuration of CAN Node and enable the clock */ + XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, + _XMC_CAN_config->can_frequency); + if (XMC_CAN_STATUS_SUCCESS == + XMC_CAN_NODE_NominalBitTimeConfigureEx(_XMC_CAN_config->can_node, + &CAN_NODE_bit_time_config)) { + XMC_CAN_NODE_EnableConfigurationChange(_XMC_CAN_config->can_node); + XMC_CAN_NODE_SetInitBit(_XMC_CAN_config->can_node); + XMC_CAN_NODE_ReSetAnalyzerMode(_XMC_CAN_config->can_node); + + /* Configure CAN NODE input pin */ + XMC_GPIO_Init(_XMC_CAN_config->tx.port, _XMC_CAN_config->tx.pin, + &(_XMC_CAN_config->tx_config)); + XMC_GPIO_SetHardwareControl(_XMC_CAN_config->tx.port, + _XMC_CAN_config->tx.pin, + XMC_GPIO_HWCTRL_DISABLED); + XMC_GPIO_Init(_XMC_CAN_config->rx.port, _XMC_CAN_config->rx.pin, + &(_XMC_CAN_config->rx_config)); + XMC_GPIO_SetHardwareControl(_XMC_CAN_config->rx.port, + _XMC_CAN_config->rx.pin, + XMC_GPIO_HWCTRL_DISABLED); + XMC_CAN_NODE_SetReceiveInput(_XMC_CAN_config->can_node, + _XMC_CAN_config->node_input); + + /* Allocate the rx and tx message object*/ + XMC_CAN_MO_Config(&CAN_msg_rx); + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 0); + XMC_CAN_MO_Config(&CAN_msg_tx); + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 1); + + /* Message object accepts the reception of both, standard and extended + * frames */ + XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); #if (UC_SERIES == XMC14) - // select interrupt source (A,B,C etc) input to NVIC node (only for XMC1400 - // devices) - XMC_SCU_SetInterruptControl(_XMC_CAN_config->irq_num, _XMC_CAN_config->irq_source); + // select interrupt source (A,B,C etc) input to NVIC node (only for XMC1400 + // devices) + XMC_SCU_SetInterruptControl(_XMC_CAN_config->irq_num, + _XMC_CAN_config->irq_source); #endif - XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, XMC_CAN_MO_POINTER_EVENT_RECEIVE, - _XMC_CAN_config->irq_service_request); - XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); - XMC_CAN_NODE_DisableConfigurationChange(_XMC_CAN_config->can_node); - XMC_CAN_NODE_ResetInitBit(_XMC_CAN_config->can_node); + XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, + XMC_CAN_MO_POINTER_EVENT_RECEIVE, + _XMC_CAN_config->irq_service_request); + XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); + XMC_CAN_NODE_DisableConfigurationChange(_XMC_CAN_config->can_node); + XMC_CAN_NODE_ResetInitBit(_XMC_CAN_config->can_node); - return 1; - } else { - return 0; - } + return 1; + } else { + return 0; + } }; /** * @brief Disables the CAN module and ends the CANXMC object. * - * This function disables the CAN module and ends the CANXMC object. It disables the receive event - * for the CAN message object and disables the CAN module using the XMC_CAN_Disable function. + * This function disables the CAN module and ends the CANXMC object. It disables + * the receive event for the CAN message object and disables the CAN module + * using the XMC_CAN_Disable function. */ void CANXMC::end() { - XMC_CAN_MO_DisableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); - XMC_CAN_Disable(CAN_xmc); - CANControllerClass::end(); + XMC_CAN_MO_DisableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); + XMC_CAN_Disable(CAN_xmc); + CANControllerClass::end(); }; /** @@ -121,116 +130,116 @@ void CANXMC::end() { * @return 1 if the packet was successfully transmitted, 0 otherwise. */ int CANXMC::endPacket() { - if (!CANControllerClass::endPacket()) { - return 0; + if (!CANControllerClass::endPacket()) { + return 0; + } + + if (_txRtr) { + // TODO: LJ: how to receive remote frame? and response... + + /* Configure remote frame to be transmitted and data length code */ + if (_txExtended) { + XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); + } else { + XMC_CAN_MO_SetStandardID(&CAN_msg_rx); } + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, _txId); + XMC_CAN_MO_SetDataLengthCode(&CAN_msg_rx, _txLength); + XMC_CAN_MO_UpdateData(&CAN_msg_rx); - if (_txRtr) { - // TODO: LJ: how to receive remote frame? and response... + /* Send remote frame */ + XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_rx); - /* Configure remote frame to be transmitted and data length code */ - if (_txExtended) { - XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); - } else { - XMC_CAN_MO_SetStandardID(&CAN_msg_rx); - } - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, _txId); - XMC_CAN_MO_SetDataLengthCode(&CAN_msg_rx, _txLength); - XMC_CAN_MO_UpdateData(&CAN_msg_rx); + if (send_status == XMC_CAN_STATUS_SUCCESS) { + return 1; + } else { + return 0; + } + + } else { + /* Configure data frame to be transmitted and data length code */ + if (_txExtended) { + XMC_CAN_MO_SetExtendedID(&CAN_msg_tx); + } else { + XMC_CAN_MO_SetStandardID(&CAN_msg_tx); + } + XMC_CAN_MO_SetIdentifier(&CAN_msg_tx, _txId); + memcpy(CAN_msg_tx.can_data_byte, _txData, _txLength); + XMC_CAN_MO_SetDataLengthCode(&CAN_msg_tx, _txLength); - /* Send remote frame */ - XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_rx); + XMC_CAN_MO_UpdateData(&CAN_msg_tx); - if (send_status == XMC_CAN_STATUS_SUCCESS) { - return 1; - } else { - return 0; - } + /* Send data frame */ + XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_tx); + if (send_status == XMC_CAN_STATUS_SUCCESS) { + return 1; } else { - /* Configure data frame to be transmitted and data length code */ - if (_txExtended) { - XMC_CAN_MO_SetExtendedID(&CAN_msg_tx); - } else { - XMC_CAN_MO_SetStandardID(&CAN_msg_tx); - } - XMC_CAN_MO_SetIdentifier(&CAN_msg_tx, _txId); - memcpy(CAN_msg_tx.can_data_byte, _txData, _txLength); - XMC_CAN_MO_SetDataLengthCode(&CAN_msg_tx, _txLength); - - XMC_CAN_MO_UpdateData(&CAN_msg_tx); - - /* Send data frame */ - XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_tx); - - if (send_status == XMC_CAN_STATUS_SUCCESS) { - return 1; - } else { - return 0; - } + return 0; } + } }; /** * @brief Parses a CAN packet received by the XMC_CAN module. * - * This function waits until a new CAN packet is available in the receive buffer, - * then retrieves the packet information such as the identifier, frame type, remote - * transmission request (RTR), data length code (DLC), and data payload. If the packet - * is an extended frame, the `_rxExtended` flag is set to true. If the packet is an RTR - * frame, the `_rxRtr` flag is set to true and the `_rxLength` is set to 0. Otherwise, - * the `_rxLength` is set to the DLC and the data payload is copied to the `_rxData` - * buffer. Finally, the function resets the receive flag and returns the length of the - * received packet. + * This function waits until a new CAN packet is available in the receive + * buffer, then retrieves the packet information such as the identifier, frame + * type, remote transmission request (RTR), data length code (DLC), and data + * payload. If the packet is an extended frame, the `_rxExtended` flag is set to + * true. If the packet is an RTR frame, the `_rxRtr` flag is set to true and the + * `_rxLength` is set to 0. Otherwise, the `_rxLength` is set to the DLC and the + * data payload is copied to the `_rxData` buffer. Finally, the function resets + * the receive flag and returns the length of the received packet. * * @return The length of the received packet. */ int CANXMC::parsePacket() { - while ((XMC_CAN_MO_GetStatus(&CAN_msg_rx) & CAN_MO_MOSTAT_NEWDAT_Msk) >> - CAN_MO_MOSTAT_NEWDAT_Pos != - 1) - ; - XMC_CAN_MO_Receive(&CAN_msg_rx); - - /* check CAN frame type */ - _rxId = XMC_CAN_MO_GetIdentifier(&CAN_msg_rx); - - if (CAN_msg_rx.can_id_mode == XMC_CAN_FRAME_TYPE_EXTENDED_29BITS) { - _rxExtended = true; - } else { - _rxExtended = false; - }; - - _rxRtr = CAN_msg_rx.can_mo_ptr->MOFCR & (uint32_t)CAN_MO_MOFCR_RMM_Msk; - _rxDlc = CAN_msg_rx.can_data_length; - if (_rxRtr) { - _rxLength = 0; - } else { - _rxLength = _rxDlc; - memcpy(_rxData, CAN_msg_rx.can_data_byte, _rxLength); - } - /* set the flag back and wait for next receive */ - can_frame_received = false; - - _rxIndex = 0; - - return _rxLength; + while ((XMC_CAN_MO_GetStatus(&CAN_msg_rx) & CAN_MO_MOSTAT_NEWDAT_Msk) >> + CAN_MO_MOSTAT_NEWDAT_Pos != + 1) + ; + XMC_CAN_MO_Receive(&CAN_msg_rx); + + /* check CAN frame type */ + _rxId = XMC_CAN_MO_GetIdentifier(&CAN_msg_rx); + + if (CAN_msg_rx.can_id_mode == XMC_CAN_FRAME_TYPE_EXTENDED_29BITS) { + _rxExtended = true; + } else { + _rxExtended = false; + }; + + _rxRtr = CAN_msg_rx.can_mo_ptr->MOFCR & (uint32_t)CAN_MO_MOFCR_RMM_Msk; + _rxDlc = CAN_msg_rx.can_data_length; + if (_rxRtr) { + _rxLength = 0; + } else { + _rxLength = _rxDlc; + memcpy(_rxData, CAN_msg_rx.can_data_byte, _rxLength); + } + /* set the flag back and wait for next receive */ + can_frame_received = false; + + _rxIndex = 0; + + return _rxLength; }; /** * Sets the callback function to be called when a CAN message is received. * - * @param callback A pointer to the function to be called when a CAN message is received. - * The function should take an integer parameter. - * Pass nullptr to disable the callback. + * @param callback A pointer to the function to be called when a CAN message is + * received. The function should take an integer parameter. Pass nullptr to + * disable the callback. */ void CANXMC::onReceive(void (*callback)(int)) { - CANControllerClass::onReceive(callback); - if (callback) { - NVIC_EnableIRQ(_XMC_CAN_config->irq_num); - } else { - NVIC_DisableIRQ(_XMC_CAN_config->irq_num); - } + CANControllerClass::onReceive(callback); + if (callback) { + NVIC_EnableIRQ(_XMC_CAN_config->irq_num); + } else { + NVIC_DisableIRQ(_XMC_CAN_config->irq_num); + } }; /** @@ -241,12 +250,12 @@ void CANXMC::onReceive(void (*callback)(int)) { * @return 1 if the filter was set successfully, 0 otherwise. */ int CANXMC::filter(int id, int mask) { - XMC_CAN_MO_SetStandardID(&CAN_msg_rx); - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); - XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); - XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); + XMC_CAN_MO_SetStandardID(&CAN_msg_rx); + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); + XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); + XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); - return 1; + return 1; }; /** @@ -257,135 +266,142 @@ int CANXMC::filter(int id, int mask) { * @return 1 if the filter was set successfully, 0 otherwise. */ int CANXMC::filterExtended(long id, long mask) { - XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); - XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); - XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); - return 1; + XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); + XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); + XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); + return 1; }; int CANXMC::observe() { - // TODO: LJ: no idea what this is - return 0; + // TODO: LJ: no idea what this is + return 0; }; int CANXMC::loopback() { - // TODO: LJ: It is actually only some application level... it is not - // library... - - /* reset CAN nodes*/ - XMC_CAN_Disable(CAN_xmc); - /* CAN bit time configuration*/ - XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { - .can_frequency = _XMC_CAN_config->can_frequency, - .baudrate = (uint32_t)50000, // TODO: LJ: need to be changed - .sample_point = (uint16_t)(80 * 100), - .sjw = (uint16_t)1, - }; - - XMC_CAN_Enable(CAN_xmc); - /* Configuration of CAN Node and enable the clock */ - XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, _XMC_CAN_config->can_frequency); - if (XMC_CAN_STATUS_SUCCESS == - XMC_CAN_NODE_NominalBitTimeConfigureEx(CAN_NODE1, &CAN_NODE_bit_time_config)) { - - /* Enable CAN node 1 for Loop-back mode */ - XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE1); - XMC_CAN_NODE_EnableLoopBack(CAN_NODE1); - XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE1); - XMC_CAN_NODE_ResetInitBit(CAN_NODE1); - - /* Initializes CAN tx Message Object for loopback */ - XMC_CAN_MO_Config(&CAN_msg_tx); - /* Allocate tx Message object to Node 1 */ - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 1, 1); - } else { - return 0; - } + // TODO: LJ: It is actually only some application level... it is not + // library... + + /* reset CAN nodes*/ + XMC_CAN_Disable(CAN_xmc); + /* CAN bit time configuration*/ + XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { + .can_frequency = _XMC_CAN_config->can_frequency, + .baudrate = (uint32_t)50000, // TODO: LJ: need to be changed + .sample_point = (uint16_t)(80 * 100), + .sjw = (uint16_t)1, + }; + + XMC_CAN_Enable(CAN_xmc); + /* Configuration of CAN Node and enable the clock */ + XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, + _XMC_CAN_config->can_frequency); + if (XMC_CAN_STATUS_SUCCESS == XMC_CAN_NODE_NominalBitTimeConfigureEx( + CAN_NODE1, &CAN_NODE_bit_time_config)) { + + /* Enable CAN node 1 for Loop-back mode */ + XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE1); + XMC_CAN_NODE_EnableLoopBack(CAN_NODE1); + XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE1); + XMC_CAN_NODE_ResetInitBit(CAN_NODE1); + + /* Initializes CAN tx Message Object for loopback */ + XMC_CAN_MO_Config(&CAN_msg_tx); + /* Allocate tx Message object to Node 1 */ + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 1, 1); + } else { + return 0; + } + + if (XMC_CAN_STATUS_SUCCESS == XMC_CAN_NODE_NominalBitTimeConfigureEx( + CAN_NODE0, &CAN_NODE_bit_time_config)) { + /* Enable CAN node 0 for Loop-back mode */ + XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE0); + XMC_CAN_NODE_EnableLoopBack(CAN_NODE0); + XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE0); + XMC_CAN_NODE_ResetInitBit(CAN_NODE0); + + /* Initializes CAN rx Message Object for loopback */ + XMC_CAN_MO_Config(&CAN_msg_rx); + /*Allocate rx Message object to Node 0 */ + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 0, 0); + XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); + /* Enable receive event */ + XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, + XMC_CAN_MO_POINTER_EVENT_RECEIVE, + _XMC_CAN_config->irq_service_request); + XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); - if (XMC_CAN_STATUS_SUCCESS == - XMC_CAN_NODE_NominalBitTimeConfigureEx(CAN_NODE0, &CAN_NODE_bit_time_config)) { - /* Enable CAN node 0 for Loop-back mode */ - XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE0); - XMC_CAN_NODE_EnableLoopBack(CAN_NODE0); - XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE0); - XMC_CAN_NODE_ResetInitBit(CAN_NODE0); - - /* Initializes CAN rx Message Object for loopback */ - XMC_CAN_MO_Config(&CAN_msg_rx); - /*Allocate rx Message object to Node 0 */ - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 0, 0); - XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); - /* Enable receive event */ - XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, XMC_CAN_MO_POINTER_EVENT_RECEIVE, - _XMC_CAN_config->irq_service_request); - XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); - - return 1; - } else { - return 0; - } + return 1; + } else { + return 0; + } }; - /** * Puts the CAN module into sleep mode. - * This function disables the CAN module by setting the EDIS bit in the CLC register. - * @return true if the CAN module is successfully put into sleep mode, false otherwise. + * This function disables the CAN module by setting the EDIS bit in the CLC + * register. + * @return true if the CAN module is successfully put into sleep mode, false + * otherwise. */ -int CANXMC::sleep() { - CAN_xmc -> CLC |= CAN_CLC_EDIS_Msk; - return CAN_xmc -> CLC & CAN_CLC_EDIS_Msk != 0; +int CANXMC::sleep() { + CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; + return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0; }; /** * @brief Wakes up the CAN module. - * This function is used to wake up the CAN module by clearing the EDIS bit in the CLC register. + * This function is used to wake up the CAN module by clearing the EDIS bit in + * the CLC register. * @return true if the CAN module is successfully woken up, false otherwise. */ -int CANXMC::wakeup() { - CAN_xmc -> CLC &= CAN_CLC_EDIS_Msk; - return CAN_xmc -> CLC & CAN_CLC_EDIS_Msk == 0; +int CANXMC::wakeup() { + CAN_xmc->CLC &= CAN_CLC_EDIS_Msk; + return CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0; }; /** - * @brief This function is called when an interrupt is triggered for the CANXMC module. - * It checks if a CAN frame has been received and if so, it parses the packet and - * calls the _onReceive() function with the number of available frames. + * @brief This function is called when an interrupt is triggered for the CANXMC + * module. It checks if a CAN frame has been received and if so, it parses the + * packet and calls the _onReceive() function with the number of available + * frames. */ void CANXMC::onInterrupt() { - if (can_frame_received == true) { - CAN.parsePacket(); - CAN._onReceive(CAN.available()); - } + if (can_frame_received == true) { + CAN.parsePacket(); + CAN._onReceive(CAN.available()); + } }; /* Interrupt Handler */ /** - * @brief Interrupt handler for CAN0_7_IRQHandler (XMC4 series) or CAN0_3_IRQHandler (XMC1 series). + * @brief Interrupt handler for CAN0_7_IRQHandler (XMC4 series) or + * CAN0_3_IRQHandler (XMC1 series). * - * This function is called when a CAN frame is received. It sets the can_frame_received flag to true - * and calls the onInterrupt() function of the CAN object. + * This function is called when a CAN frame is received. It sets the + * can_frame_received flag to true and calls the onInterrupt() function of the + * CAN object. * - * @note This function is intended to be used as an interrupt handler and should not be called - * directly. + * @note This function is intended to be used as an interrupt handler and should + * not be called directly. */ extern "C" { #if (UC_FAMILY == XMC4) void CAN0_7_IRQHandler() { - /* Set the frame received flag to true */ - can_frame_received = true; + /* Set the frame received flag to true */ + can_frame_received = true; - CAN.onInterrupt(); + CAN.onInterrupt(); } #endif #if (UC_SERIES == XMC14) void CAN0_3_IRQHandler() { - /* Set the frame received flag to true */ - can_frame_received = true; + /* Set the frame received flag to true */ + can_frame_received = true; - CAN.onInterrupt(); + CAN.onInterrupt(); } #endif } diff --git a/libraries/CAN/test/unit/src/Test_common_includes.h b/libraries/CAN/test/unit/src/Test_common_includes.h new file mode 100644 index 00000000..e806fba5 --- /dev/null +++ b/libraries/CAN/test/unit/src/Test_common_includes.h @@ -0,0 +1,21 @@ +#ifndef TEST_COMMON_INCLUDES_H +#define TEST_COMMON_INCLUDES_H + + +// std includes +#include + + +// project c includes + + +// test includes +// Unity c includes +#include "unity.h" +#include "unity_fixture.h" + +// IFX Unity addons +#include "unity_ifx.h" + + +#endif // TEST_COMMON_INCLUDES_H diff --git a/libraries/CAN/test/unit/src/features/Test_CAN.hpp b/libraries/CAN/test/unit/src/features/Test_CAN.hpp new file mode 100644 index 00000000..b6b738dc --- /dev/null +++ b/libraries/CAN/test/unit/src/features/Test_CAN.hpp @@ -0,0 +1,73 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +void CAN_suiteSetUp(void); +void CAN_suiteTearDown(void); + + +// variables used in the tests below that have to be accessed in the setup and tear down methods +static CANXMC dut = CAN; + + +// test includes that may require dut + + +// define test group name +TEST_GROUP(CAN); +TEST_GROUP(CAN_internal); + + +// Setup method called before every individual test defined for this test group +static TEST_SETUP(CAN_internal) +{ +} + + +// Tear down method called before every individual test defined for this test group +static TEST_TEAR_DOWN(CAN_internal) +{ +} + + +// Functionality not - yet - supported +TEST_IFX(CAN_internal, checkUnsupportedFunctionality) +{ + TEST_ASSERT_FALSE(CAN.observe() ); +} + + +TEST_IFX(CAN_internal, checkSupportedFunctionality) +{ + TEST_ASSERT_TRUE(CAN.begin() ); +} + + +TEST_IFX(CAN_internal, checkWhatever) +{ +} + + +// Bundle all tests to be executed for this test group +static TEST_GROUP_RUNNER(CAN_internal) +{ + RUN_TEST_CASE(CAN_internal, checkUnsupportedFunctionality); + RUN_TEST_CASE(CAN_internal, checkSupportedFunctionality); + + RUN_TEST_CASE(CAN_internal, checkWhatever); +} + + +// Bundle all tests to be executed for this test group +TEST_GROUP_RUNNER(CAN) +{ + CAN_suiteSetUp(); + + RUN_TEST_GROUP(CAN_internal); + + CAN_suiteTearDown(); +} diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected.hpp new file mode 100644 index 00000000..c004011a --- /dev/null +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected.hpp @@ -0,0 +1,78 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +void CAN_connected_suiteSetup(void); +void CAN_connected_suiteTearDown(void); + + +// variables used in the tests below that have to be accessed in the setup and tear down methods +static CANXMC dut = CAN; + + +// test includes that may require dut + + +// define test group name +TEST_GROUP(CAN_connected); +TEST_GROUP(CAN_connectedInternal); + + +// Setup method called by Unity before every individual test defined for this test group. +static TEST_SETUP(CAN_connectedInternal) +{ +} + + +// Tear down method called by Unity after every individual test defined for this test group. +static TEST_TEAR_DOWN(CAN_connectedInternal) +{ +} + + +// Define tests for unsupported functionality. These should return false to indicate this. +TEST_IFX(CAN_connectedInternal, checkUnsupportedFunctionality) +{ +} + + +// Define tests for supported common functionality. These should return true to indicate this. +TEST_IFX(CAN_connectedInternal, checkSupportedFunctionality) +{ + // TEST_ASSERT_TRUE( dut.method_connected() ); +} + + +TEST_IFX(CAN_connectedInternal, checkWhatever) +{ + // Useful macros, but many more available - check cheat sheet. + // TEST_ASSERT_TRUE( ); + // TEST_ASSERT_FLOAT_WITHIN( ); + // TEST_ASSERT_EQUAL_INT16( ); + // TEST_ASSERT_EQUAL_FLOAT( ); + // TEST_ASSERT_EQUAL_STRING( "ERROR : Unknown sensorType !", ); +} + + +static TEST_GROUP_RUNNER(CAN_connectedInternal) +{ + RUN_TEST_CASE(CAN_connectedInternal, checkUnsupportedFunctionality); + RUN_TEST_CASE(CAN_connectedInternal, checkSupportedFunctionality); + + RUN_TEST_CASE(CAN_connectedInternal, checkWhatever); +} + + +// Bundle all tests to be executed for this test group +TEST_GROUP_RUNNER(CAN_connected) +{ + CAN_connected_suiteSetup(); + + RUN_TEST_GROUP(CAN_connectedInternal); + + CAN_connected_suiteTearDown(); +} diff --git a/libraries/CAN/test/unit/src/framework/arduino/Utilities.cpp b/libraries/CAN/test/unit/src/framework/arduino/Utilities.cpp new file mode 100644 index 00000000..d03bf694 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/Utilities.cpp @@ -0,0 +1,24 @@ + +// std includes +#include + + +// Arduino includes +#include + + +// project cpp includes + + +void printArray(const char *title, volatile uint8_t *data, uint8_t quantity) { + Serial.print(title); + Serial.print(" : ["); + + for(uint8_t i = 0; i < quantity; ++i) { + Serial.print(data[i]); + Serial.print(", "); + } + + Serial.println("]"); + Serial.flush(); +} diff --git a/libraries/CAN/test/unit/src/framework/arduino/Utilities.hpp b/libraries/CAN/test/unit/src/framework/arduino/Utilities.hpp new file mode 100644 index 00000000..4e3226b3 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/Utilities.hpp @@ -0,0 +1,15 @@ +#ifndef UTILITIES_HPP +#define UTILITIES_HPP + + +// std includes +#include + + +// project cpp includes + + +void printArray(const char *title, volatile uint8_t *data, uint8_t quantity); + + +#endif // UTILITIES_HPP diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN.cpp new file mode 100644 index 00000000..05e99fdc --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN.cpp @@ -0,0 +1,27 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +// defines +#define TRACE_OUTPUT + + +// variables + + +// test feature includes requiring the above defined variables +#include "Test_CAN.hpp" + + +// Method invoked by Unity before a test suite is run +void CAN_suiteSetUp() { +} + + +// Method invoked by Unity after a test suite is run +void CAN_suiteTearDown() { +} diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected.cpp new file mode 100644 index 00000000..9eae8b55 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected.cpp @@ -0,0 +1,27 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +// defines +#define TRACE_OUTPUT + + +// variables + + +// test feature includes requiring the above defined variables +#include "Test_CAN_connected.hpp" + + +// Method invoked before a test suite is run. +void CAN_connected_suiteSetup() { +} + + +// Method invoked after a test suite is run. +void CAN_connected_suiteTearDown() { +} diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_includes.hpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_includes.hpp new file mode 100644 index 00000000..c896f722 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_includes.hpp @@ -0,0 +1,18 @@ +#ifndef TEST_INCLUDES_HPP +#define TEST_INCLUDES_HPP + + +// Arduino includes +#include + + +// project cpp includes +#include "Utilities.hpp" +#include "CAN.h" + + +// test includes +#include "Test_common_includes.h" + + +#endif // TEST_INCLUDES_HPP diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino new file mode 100644 index 00000000..80e9eaf7 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino @@ -0,0 +1,50 @@ +// test includes +#include "Test_includes.hpp" + + +/*** + * Uses flags / defines to determine which groups of tests should be added to this test such that multiple tests CAN be executed in a single run. + * Use the '--build-property option of the 'arduino_compile' target to add the flags by defining the respective targets in the makefile. + * makefile : --build-property "compiler.cpp.extra_flags=\"-D=1\"" build +*/ +void RunAllTests(void) +{ + + +// CAN +#ifdef TEST_CAN + + RUN_TEST_GROUP(CAN); + +#endif + + +// CAN with connections +#ifdef TEST_CAN_CONNECTED + + RUN_TEST_GROUP(CAN_connected); + +#endif + + +} + + +// +void setup() { + Serial.begin(115200); + + Serial.println("setup done."); +} + + +// +void loop() { + Serial.println("\n"); + + const int argc = 2; + const char *argv[argc] = { "", "-v" }; + + (void) UnityMain(argc, argv, RunAllTests); + delay(3000); +} \ No newline at end of file diff --git a/libraries/CAN/test/unit/src/framework/arduino/unity_ifx.cpp b/libraries/CAN/test/unit/src/framework/arduino/unity_ifx.cpp new file mode 100644 index 00000000..c8c5de67 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/unity_ifx.cpp @@ -0,0 +1,22 @@ +// std includes + +// Arduino includes +#include + + +// test includes +#include "unity_ifx.h" + + +extern "C" { + // Method used by Unity to output a single character + void putCharacter(char c) { + Serial.print(c); + Serial.flush(); + } + + // Method used by Unity to flush the output + void flushCharacter() { + Serial.flush(); + } +} diff --git a/libraries/CAN/test/unit/unity_config.h b/libraries/CAN/test/unit/unity_config.h new file mode 100644 index 00000000..eef0f529 --- /dev/null +++ b/libraries/CAN/test/unit/unity_config.h @@ -0,0 +1,250 @@ +/* Unity Configuration + * As of May 11th, 2016 at ThrowTheSwitch/Unity commit 837c529 + * Update: December 29th, 2016 + * See Also: Unity/docs/UnityConfigurationGuide.pdf + * + * Unity is designed to run on almost anything that is targeted by a C compiler. + * It would be awesome if this could be done with zero configuration. While + * there are some targets that come close to this dream, it is sadly not + * universal. It is likely that you are going to need at least a couple of the + * configuration options described in this document. + * + * All of Unity's configuration options are `#defines`. Most of these are simple + * definitions. A couple are macros with arguments. They live inside the + * unity_internals.h header file. We don't necessarily recommend opening that + * file unless you really need to. That file is proof that a cross-platform + * library is challenging to build. From a more positive perspective, it is also + * proof that a great deal of complexity can be centralized primarily to one + * place in order to provide a more consistent and simple experience elsewhere. + * + * Using These Options + * It doesn't matter if you're using a target-specific compiler and a simulator + * or a native compiler. In either case, you've got a couple choices for + * configuring these options: + * + * 1. Because these options are specified via C defines, you can pass most of + * these options to your compiler through command line compiler flags. Even + * if you're using an embedded target that forces you to use their + * overbearing IDE for all configuration, there will be a place somewhere in + * your project to configure defines for your compiler. + * 2. You can create a custom `unity_config.h` configuration file (present in + * your toolchain's search paths). In this file, you will list definitions + * and macros specific to your target. All you must do is define + * `UNITY_INCLUDE_CONFIG_H` and Unity will rely on `unity_config.h` for any + * further definitions it may need. + */ + +#ifndef UNITY_CONFIG_H +#define UNITY_CONFIG_H + +/* ************************* AUTOMATIC INTEGER TYPES *************************** + * C's concept of an integer varies from target to target. The C Standard has + * rules about the `int` matching the register size of the target + * microprocessor. It has rules about the `int` and how its size relates to + * other integer types. An `int` on one target might be 16 bits while on another + * target it might be 64. There are more specific types in compilers compliant + * with C99 or later, but that's certainly not every compiler you are likely to + * encounter. Therefore, Unity has a number of features for helping to adjust + * itself to match your required integer sizes. It starts off by trying to do it + * automatically. + **************************************************************************** */ + +/* The first attempt to guess your types is to check `limits.h`. Some compilers + * that don't support `stdint.h` could include `limits.h`. If you don't + * want Unity to check this file, define this to make it skip the inclusion. + * Unity looks at UINT_MAX & ULONG_MAX, which were available since C89. + */ +/* #define UNITY_EXCLUDE_LIMITS_H */ + +/* The second thing that Unity does to guess your types is check `stdint.h`. + * This file defines `UINTPTR_MAX`, since C99, that Unity can make use of to + * learn about your system. It's possible you don't want it to do this or it's + * possible that your system doesn't support `stdint.h`. If that's the case, + * you're going to want to define this. That way, Unity will know to skip the + * inclusion of this file and you won't be left with a compiler error. + */ +/* #define UNITY_EXCLUDE_STDINT_H */ + +/* ********************** MANUAL INTEGER TYPE DEFINITION *********************** + * If you've disabled all of the automatic options above, you're going to have + * to do the configuration yourself. There are just a handful of defines that + * you are going to specify if you don't like the defaults. + **************************************************************************** */ + + /* Define this to be the number of bits an `int` takes up on your system. The + * default, if not auto-detected, is 32 bits. + * + * Example: + */ +/* #define UNITY_INT_WIDTH 16 */ + +/* Define this to be the number of bits a `long` takes up on your system. The + * default, if not autodetected, is 32 bits. This is used to figure out what + * kind of 64-bit support your system can handle. Does it need to specify a + * `long` or a `long long` to get a 64-bit value. On 16-bit systems, this option + * is going to be ignored. + * + * Example: + */ +/* #define UNITY_LONG_WIDTH 16 */ + +/* Define this to be the number of bits a pointer takes up on your system. The + * default, if not autodetected, is 32-bits. If you're getting ugly compiler + * warnings about casting from pointers, this is the one to look at. + * + * Example: + */ +/* #define UNITY_POINTER_WIDTH 64 */ + +/* Unity will automatically include 64-bit support if it auto-detects it, or if + * your `int`, `long`, or pointer widths are greater than 32-bits. Define this + * to enable 64-bit support if none of the other options already did it for you. + * There can be a significant size and speed impact to enabling 64-bit support + * on small targets, so don't define it if you don't need it. + */ +/* #define UNITY_INCLUDE_64 */ + + +/* *************************** FLOATING POINT TYPES **************************** + * In the embedded world, it's not uncommon for targets to have no support for + * floating point operations at all or to have support that is limited to only + * single precision. We are able to guess integer sizes on the fly because + * integers are always available in at least one size. Floating point, on the + * other hand, is sometimes not available at all. Trying to include `float.h` on + * these platforms would result in an error. This leaves manual configuration as + * the only option. + **************************************************************************** */ + + /* By default, Unity guesses that you will want single precision floating point + * support, but not double precision. It's easy to change either of these using + * the include and exclude options here. You may include neither, just float, + * or both, as suits your needs. + */ +/* #define UNITY_EXCLUDE_FLOAT */ +/* #define UNITY_INCLUDE_DOUBLE */ +/* #define UNITY_EXCLUDE_DOUBLE */ + +/* For features that are enabled, the following floating point options also + * become available. + */ + +/* Unity aims for as small of a footprint as possible and avoids most standard + * library calls (some embedded platforms don't have a standard library!). + * Because of this, its routines for printing integer values are minimalist and + * hand-coded. To keep Unity universal, though, we eventually chose to develop + * our own floating point print routines. Still, the display of floating point + * values during a failure are optional. By default, Unity will print the + * actual results of floating point assertion failures. So a failed assertion + * will produce a message like "Expected 4.0 Was 4.25". If you would like less + * verbose failure messages for floating point assertions, use this option to + * give a failure message `"Values Not Within Delta"` and trim the binary size. + */ +/* #define UNITY_EXCLUDE_FLOAT_PRINT */ + +/* If enabled, Unity assumes you want your `FLOAT` asserts to compare standard C + * floats. If your compiler supports a specialty floating point type, you can + * always override this behavior by using this definition. + * + * Example: + */ +/* #define UNITY_FLOAT_TYPE float16_t */ + +/* If enabled, Unity assumes you want your `DOUBLE` asserts to compare standard + * C doubles. If you would like to change this, you can specify something else + * by using this option. For example, defining `UNITY_DOUBLE_TYPE` to `long + * double` could enable gargantuan floating point types on your 64-bit processor + * instead of the standard `double`. + * + * Example: + */ +/* #define UNITY_DOUBLE_TYPE long double */ + +/* If you look up `UNITY_ASSERT_EQUAL_FLOAT` and `UNITY_ASSERT_EQUAL_DOUBLE` as + * documented in the Unity Assertion Guide, you will learn that they are not + * really asserting that two values are equal but rather that two values are + * "close enough" to equal. "Close enough" is controlled by these precision + * configuration options. If you are working with 32-bit floats and/or 64-bit + * doubles (the normal on most processors), you should have no need to change + * these options. They are both set to give you approximately 1 significant bit + * in either direction. The float precision is 0.00001 while the double is + * 10^-12. For further details on how this works, see the appendix of the Unity + * Assertion Guide. + * + * Example: + */ +/* #define UNITY_FLOAT_PRECISION 0.001f */ +/* #define UNITY_DOUBLE_PRECISION 0.001f */ + + +/* *************************** MISCELLANEOUS *********************************** + * Miscellaneous configuration options for Unity + **************************************************************************** */ + +/* Unity uses the stddef.h header included in the C standard library for the + * "NULL" macro. Define this in order to disable the include of stddef.h. If you + * do this, you have to make sure to provide your own "NULL" definition. + */ +/* #define UNITY_EXCLUDE_STDDEF_H */ + +/* Define this to enable the unity formatted print macro: + * "TEST_PRINTF" + */ +/* #define UNITY_INCLUDE_PRINT_FORMATTED */ + + +/* *************************** TOOLSET CUSTOMIZATION *************************** + * In addition to the options listed above, there are a number of other options + * which will come in handy to customize Unity's behavior for your specific + * toolchain. It is possible that you may not need to touch any of these but + * certain platforms, particularly those running in simulators, may need to jump + * through extra hoops to operate properly. These macros will help in those + * situations. + **************************************************************************** */ + +/* By default, Unity prints its results to `stdout` as it runs. This works + * perfectly fine in most situations where you are using a native compiler for + * testing. It works on some simulators as well so long as they have `stdout` + * routed back to the command line. There are times, however, where the + * simulator will lack support for dumping results or you will want to route + * results elsewhere for other reasons. In these cases, you should define the + * `UNITY_OUTPUT_CHAR` macro. This macro accepts a single character at a time + * (as an `int`, since this is the parameter type of the standard C `putchar` + * function most commonly used). You may replace this with whatever function + * call you like. + * + * Example: + * Say you are forced to run your test suite on an embedded processor with no + * `stdout` option. You decide to route your test result output to a custom + * serial `RS232_putc()` function you wrote like thus: + */ + +#define UNITY_OUTPUT_CHAR(a) putCharacter(a) +#define UNITY_OUTPUT_CHAR_HEADER_DECLARATION putCharacter(char c) +#define UNITY_OUTPUT_FLUSH() flushCharacter() +#define UNITY_OUTPUT_FLUSH_HEADER_DECLARATION flushCharacter() + +/* #define UNITY_OUTPUT_CHAR(a) RS232_putc(a) */ +/* #define UNITY_OUTPUT_CHAR_HEADER_DECLARATION RS232_putc(int) */ +/* #define UNITY_OUTPUT_FLUSH() RS232_flush() */ +/* #define UNITY_OUTPUT_FLUSH_HEADER_DECLARATION RS232_flush(void) */ +/* #define UNITY_OUTPUT_START() RS232_config(115200,1,8,0) */ +/* #define UNITY_OUTPUT_COMPLETE() RS232_close() */ + +/* Some compilers require a custom attribute to be assigned to pointers, like + * `near` or `far`. In these cases, you can give Unity a safe default for these + * by defining this option with the attribute you would like. + * + * Example: + */ +/* #define UNITY_PTR_ATTRIBUTE __attribute__((far)) */ +/* #define UNITY_PTR_ATTRIBUTE near */ + +/* Print execution time of each test when executed in verbose mode + * + * Example: + * + * TEST - PASS (10 ms) + */ +#define UNITY_INCLUDE_EXEC_TIME (1) + +#endif /* UNITY_CONFIG_H */ diff --git a/libraries/CAN/test/unit/unity_ifx.h b/libraries/CAN/test/unit/unity_ifx.h new file mode 100644 index 00000000..4689ae5b --- /dev/null +++ b/libraries/CAN/test/unit/unity_ifx.h @@ -0,0 +1,27 @@ + +#ifndef UNITY_IFX_H +#define UNITY_IFX_H + + +#define TEST_IFX(group, name) static void TEST_##group##_##name##_(void); static void TEST_##group##_##name##_run(void); void TEST_##group##_##name##_run(void) { UnityTestRunner(TEST_##group##_SETUP, TEST_##group##_##name##_, TEST_##group##_TEAR_DOWN, "TEST(" #group ", " #name ")", TEST_GROUP_##group, #name, __FILE__, __LINE__); } void TEST_##group##_##name##_(void) + + +#ifdef __cplusplus + +extern "C" { + +#endif + + +void putCharacter(char c); +void flushCharacter(); + + +#ifdef __cplusplus + +} + +#endif + + +#endif // UNITY_IFX_H From dc5d23eafdc74bd1488c79f0c73b48c19434e8d7 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 1 Oct 2024 14:59:17 +0200 Subject: [PATCH 02/13] Test: update first version for CAN test/ code check --- .gitignore | 7 +- IFX_CONTRIBUTING.md | 26 + libraries/CAN/Makefile | 13 +- libraries/CAN/Makefile.codecheck | 12 + libraries/CAN/Makefile.codechecks.mk | 46 +- libraries/CAN/Makefile.test | 10 +- libraries/CAN/cppcheck.checkers | 225 ++++++++ libraries/CAN/err.xml | 418 +++++++++++++++ libraries/CAN/src/CANController.cpp | 156 +++--- libraries/CAN/src/CANController.h | 6 +- libraries/CAN/src/CANXMC.cpp | 500 +++++++++--------- libraries/CAN/src/CANXMC.h | 6 + .../CAN/test/unit/src/features/Test_CAN.hpp | 110 +++- 13 files changed, 1177 insertions(+), 358 deletions(-) create mode 100644 IFX_CONTRIBUTING.md create mode 100644 libraries/CAN/Makefile.codecheck create mode 100644 libraries/CAN/cppcheck.checkers create mode 100644 libraries/CAN/err.xml diff --git a/.gitignore b/.gitignore index e02e001a..7d34a051 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,9 @@ .vscode pkg_build build -**/__pycache__/ \ No newline at end of file +**/__pycache__/ + +# Code-check reports +clang-tidy-report.txt +scan-build-reports/ +cppcheck_reports/ \ No newline at end of file diff --git a/IFX_CONTRIBUTING.md b/IFX_CONTRIBUTING.md new file mode 100644 index 00000000..70b6fc96 --- /dev/null +++ b/IFX_CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Develop Environment + +## Develop on Windows +Clone the repository in arduino folder: +- Open Arduino and install any Infineon XMC library (e.g. 2.2.0) +- Open the library location in Arduino program folder +`C:\Users\"USERNAME"\AppData\Local\Arduino15\packages\Infineon\hardware\xmc` +- Open git bash, type command: +`git clone "HTTP_SSH_REPOSITORY" "LIBRARY_VERSION (e.g. 2.2.0)"` + +This is a workaround for current local compilation/testing. + +## Develop on WSL +To be able to use some tools (make, clang, gcc...) easier on Linux, WSL is needed. +Since now we still compile the code with arduino-cli.exe on windows, in order to useLinux commands/ build tools, a symbolic link is created to eunsure the changes are immediately reflected in the other. +In WSL, you can create a symbolic link pointing to your windows project folder: + +`ln -s /mnt/c/Users//AppData/Local/Arduino15/packages/Infineon/hardware/xmc /home//project` + +# Code checks +Refer to libraries\CAN\Makefile.codecheck + +# Example and Unity tests +Refer to libraries\CAN\Makefile.test + +XMC4700 Receiver has some issue. \ No newline at end of file diff --git a/libraries/CAN/Makefile b/libraries/CAN/Makefile index 08f9d452..c60d049c 100644 --- a/libraries/CAN/Makefile +++ b/libraries/CAN/Makefile @@ -45,7 +45,7 @@ CANSender: arduino cp examples/CANSender/CANSender.ino build/build.ino CANLoopBack: arduino - cp examples/CANSender/CANLoopBack.ino build/build.ino + cp examples/CANLoopBack/CANLoopBack.ino build/build.ino # install Unity from https://www.throwtheswitch.org/unity or git @@ -107,6 +107,17 @@ monitor: ifeq ($(PORT),) $(error "Must set variable PORT (Windows port naming convention, ie COM16) in order to be able to flash Arduino sketches !") endif +ifeq ($(FQBN),) + $(error "Must set variable FQBN in order to be able to flash Arduino sketches !") +else + arduino-cli.exe monitor -c baudrate=9600 -p $(PORT) --fqbn $(FQBN) +endif + + +test-monitor: +ifeq ($(PORT),) + $(error "Must set variable PORT (Windows port naming convention, ie COM16) in order to be able to flash Arduino sketches !") +endif ifeq ($(FQBN),) $(error "Must set variable FQBN in order to be able to flash Arduino sketches !") else diff --git a/libraries/CAN/Makefile.codecheck b/libraries/CAN/Makefile.codecheck new file mode 100644 index 00000000..5dee7388 --- /dev/null +++ b/libraries/CAN/Makefile.codecheck @@ -0,0 +1,12 @@ +#Run Clang-format for c/c++ code. +make -f Makefile.codechecks.mk clang-format + +#Run Clang-tidy +make -f Makefile.codechecks.mk clang-tidy-src + +make -f Makefile.codechecks.mk clang-tidy-test + +#Run cppcheck +make -f Makefile.codechecks.mk cppcheck + + diff --git a/libraries/CAN/Makefile.codechecks.mk b/libraries/CAN/Makefile.codechecks.mk index 77b02818..fb8669bb 100644 --- a/libraries/CAN/Makefile.codechecks.mk +++ b/libraries/CAN/Makefile.codechecks.mk @@ -1,62 +1,60 @@ WIN_USER ?= +CONFIG_DIR := ../../config +CORE_LIB_DIR := ../../cores +CLANG_FORMAT_DIR := $(CONFIG_DIR)/clang-format +CLANG_TIDY_DIR := $(CONFIG_DIR)/clang-tidy + #################################### clang-format code formatter ### Need to install clang-tidy -### You may want to copy the config to your working directory to use the common settings. -### cp config/clang-format/.clang-format . clang-format: - clang-format -i $(shell find src -name CAN\*.[hc]pp\*) + clang-format -i -style=file:$(CLANG_FORMAT_DIR)/.clang-format $(shell find src -name \*.[hc]\*) -#################################### clang-tidy static code checker //TODO: how to implement this for core library? -### Need to install clang-tidy -clang-tidy-test: C_CPP_SOURCES = $(shell find test -name \*.[hc]pp\*) -clang-tidy-src: C_CPP_SOURCES = $(shell find src -name \*.[hc]pp\*) +##TODO: add compilation database for builtin libraries! +#################################### clang-tidy static code checker + +clang-tidy-test: C_CPP_SOURCES = $(shell find test -name \*.[hc]\*) +clang-tidy-src: C_CPP_SOURCES = $(shell find src -name \*.[hc]\*) clang-tidy-test clang-tidy-src : clang-tidy clang-tidy: -ifeq ($(WIN_USER),) - $(warn "Must set variable WIN_USER in order to include XMCLib files in clang-tidy analysis !") - clang-tidy --config-file=../../config/clang-tidy/.clang-tidy -header-filter=. --extra-arg="-Isrc/framework/arduino" --extra-arg="-Itest/unit/src/framework/arduino/corelibs" $(C_CPP_SOURCES) -- -else $(info $(C_CPP_SOURCES)) - clang-tidy --config-file=../../config/clang-tidy/.clang-tidy -header-filter=. --extra-arg="-Isrc/framework/arduino" --extra-arg="-Itest/unit/src/framework/arduino/corelibs" --extra-arg="-I/mnt/c/Users/$(WIN_USER)/AppData/Local/Arduino15/packages/Infineon/hardware/xmc/3.1.0/cores" $(C_CPP_SOURCES) -- -endif + clang-tidy $(C_CPP_SOURCES) --config-file=$(CLANG_TIDY_DIR)/.clang-tidy -header-filter='' -- -Isrc/framework/arduino -Itest/unit/src/framework/arduino/corelibs -I$(CORE_LIB_DIR) > clang-tidy-report.txt -#################################### scan-build static code checker -### Need to install scan-build +#################################### scan-build static code checker #TODO: still using foo example ### After executing the scan-build target run the command scan-view as printed in the scan-build output last line. ### Or use firefox directly. scan-build: scan-build -o scan-build-reports make -f Makefile.codechecks.mk foo -# firefox scan-build-reports/ + firefox scan-build-reports/ foo: - $(CC) -c -Wextra -Wall -Wfloat-equal -Wconversion -Wredundant-decls -Wswitch-default -Wdouble-promotion -Wpedantic -Wunreachable-code -std=c++20 config/sca/foo.cpp -o foo.o + $(CC) -c -Wextra -Wall -Wfloat-equal -Wconversion -Wredundant-decls -Wswitch-default -Wdouble-promotion -Wpedantic -Wunreachable-code -std=c++20 $(CONFIG_DIR)/sca/foo.cpp -o foo.o -#################################### cppcheck static code checker +#################################### cppcheck static code checker ### Requirement : install cppcheck from https://github.com/danmar/cppcheck -cppcheck: C_CPP_SOURCES = ./config/sca +cppcheck: C_CPP_SOURCES = ./src -CPPCHECK_PATH = ~/cppcheck/cppcheck.danmar +CPPCHECK_PATH = ~/cppcheck/cppcheck cppcheck: - export RULE_TEXTS=config/cppcheck/misra.txt - $(CPPCHECK_PATH)/cppcheck -i build -i doc -i examples -i results -i reports_hml -i src -i test \ - -I$(C_CPP_SOURCES) \ + export RULE_TEXTS=$(CONFIG_DIR)/cppcheck/misra.txt + $(CPPCHECK_PATH)/build/bin/cppcheck -i build -i examples -i test \ + -I$(C_CPP_SOURCES) \ --checkers-report=cppcheck.checkers --check-level=exhaustive --xml --enable=all --inconclusive \ - --addon=config/cppcheck/misra_local.py --addon=misc \ + --addon=$(CONFIG_DIR)/cppcheck/misra_local.py --addon=misc \ --max-configs=100 ./ 2> ./err.xml $(CPPCHECK_PATH)/htmlreport/cppcheck-htmlreport --file=err.xml --title=TLx493D --report-dir=cppcheck_reports --source-dir=. firefox cppcheck_reports/index.html diff --git a/libraries/CAN/Makefile.test b/libraries/CAN/Makefile.test index 9c27c5b8..00762480 100644 --- a/libraries/CAN/Makefile.test +++ b/libraries/CAN/Makefile.test @@ -3,11 +3,11 @@ ### Example program ## XMC4700_Relax_Kit -make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM20 CANReceiver flash monitor +make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM20 CANReceiver flash monitor # CAN Receive has some problem for XMC4700. -make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM20 CANReceiverCallback flash monitor +make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM20 CANReceiverCallback flash monitor # CAN Receive has some problem for XMC4700. -make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM24 CANSender flash monitor +make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM24 CANSender flash monitor make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM24 CANLoopBack flash monitor @@ -22,6 +22,8 @@ make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 CANLoopBack flash monitor ### Unit tests -make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN test-monitor make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN_connected monitor + +make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM20 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN test-monitor \ No newline at end of file diff --git a/libraries/CAN/cppcheck.checkers b/libraries/CAN/cppcheck.checkers new file mode 100644 index 00000000..203c1217 --- /dev/null +++ b/libraries/CAN/cppcheck.checkers @@ -0,0 +1,225 @@ +Critical errors +--------------- +No critical errors, all files were checked. +Important: Analysis is still not guaranteed to be 'complete' it is possible there are false negatives. + + +Open source checkers +-------------------- +Yes Check64BitPortability::pointerassignment +Yes CheckAssert::assertWithSideEffects +Yes CheckAutoVariables::assignFunctionArg +Yes CheckAutoVariables::autoVariables +Yes CheckAutoVariables::checkVarLifetime +Yes CheckBool::checkAssignBoolToFloat +Yes CheckBool::checkAssignBoolToPointer +Yes CheckBool::checkBitwiseOnBoolean +Yes CheckBool::checkComparisonOfBoolExpressionWithInt +Yes CheckBool::checkComparisonOfBoolWithBool +Yes CheckBool::checkComparisonOfBoolWithInt +Yes CheckBool::checkComparisonOfFuncReturningBool +Yes CheckBool::checkIncrementBoolean +Yes CheckBool::pointerArithBool +Yes CheckBool::returnValueOfFunctionReturningBool +Yes CheckBoost::checkBoostForeachModification +Yes CheckBufferOverrun::analyseWholeProgram +Yes CheckBufferOverrun::argumentSize +Yes CheckBufferOverrun::arrayIndex +Yes CheckBufferOverrun::arrayIndexThenCheck +Yes CheckBufferOverrun::bufferOverflow +Yes CheckBufferOverrun::negativeArraySize +Yes CheckBufferOverrun::objectIndex +Yes CheckBufferOverrun::pointerArithmetic +Yes CheckBufferOverrun::stringNotZeroTerminated +Yes CheckClass::analyseWholeProgram +Yes CheckClass::checkConst +Yes CheckClass::checkConstructors +Yes CheckClass::checkCopyConstructors +Yes CheckClass::checkDuplInheritedMembers +Yes CheckClass::checkExplicitConstructors +Yes CheckClass::checkMemset +Yes CheckClass::checkMissingOverride +Yes CheckClass::checkReturnByReference +Yes CheckClass::checkSelfInitialization +Yes CheckClass::checkThisUseAfterFree +No CheckClass::checkUnsafeClassRefMember require:warning,safeChecks +Yes CheckClass::checkUselessOverride +Yes CheckClass::checkVirtualFunctionCallInConstructor +Yes CheckClass::initializationListUsage +Yes CheckClass::initializerListOrder +Yes CheckClass::operatorEqRetRefThis +Yes CheckClass::operatorEqToSelf +Yes CheckClass::privateFunctions +Yes CheckClass::thisSubtraction +Yes CheckClass::virtualDestructor +Yes CheckCondition::alwaysTrueFalse +Yes CheckCondition::assignIf +Yes CheckCondition::checkAssignmentInCondition +Yes CheckCondition::checkBadBitmaskCheck +No CheckCondition::checkCompareValueOutOfTypeRange require:style,platform +Yes CheckCondition::checkDuplicateConditionalAssign +Yes CheckCondition::checkIncorrectLogicOperator +Yes CheckCondition::checkInvalidTestForOverflow +Yes CheckCondition::checkModuloAlwaysTrueFalse +Yes CheckCondition::checkPointerAdditionResultNotNull +Yes CheckCondition::clarifyCondition +Yes CheckCondition::comparison +Yes CheckCondition::duplicateCondition +Yes CheckCondition::multiCondition +Yes CheckCondition::multiCondition2 +Yes CheckExceptionSafety::checkCatchExceptionByValue +Yes CheckExceptionSafety::checkRethrowCopy +Yes CheckExceptionSafety::deallocThrow +Yes CheckExceptionSafety::destructors +Yes CheckExceptionSafety::nothrowThrows +Yes CheckExceptionSafety::rethrowNoCurrentException +Yes CheckExceptionSafety::unhandledExceptionSpecification +Yes CheckFunctions::checkIgnoredReturnValue +Yes CheckFunctions::checkMathFunctions +Yes CheckFunctions::checkMissingReturn +Yes CheckFunctions::checkProhibitedFunctions +Yes CheckFunctions::invalidFunctionUsage +Yes CheckFunctions::memsetInvalid2ndParam +Yes CheckFunctions::memsetZeroBytes +Yes CheckFunctions::returnLocalStdMove +Yes CheckFunctions::useStandardLibrary +Yes CheckIO::checkCoutCerrMisusage +Yes CheckIO::checkFileUsage +Yes CheckIO::checkWrongPrintfScanfArguments +Yes CheckIO::invalidScanf +Yes CheckLeakAutoVar::check +Yes CheckMemoryLeakInClass::check +Yes CheckMemoryLeakInFunction::checkReallocUsage +Yes CheckMemoryLeakNoVar::check +Yes CheckMemoryLeakNoVar::checkForUnsafeArgAlloc +Yes CheckMemoryLeakStructMember::check +Yes CheckNullPointer::analyseWholeProgram +Yes CheckNullPointer::arithmetic +Yes CheckNullPointer::nullConstantDereference +Yes CheckNullPointer::nullPointer +Yes CheckOther::checkAccessOfMovedVariable +Yes CheckOther::checkCastIntToCharAndBack +Yes CheckOther::checkCharVariable +Yes CheckOther::checkComparePointers +Yes CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse +Yes CheckOther::checkConstPointer +Yes CheckOther::checkConstVariable +Yes CheckOther::checkDuplicateBranch +Yes CheckOther::checkDuplicateExpression +Yes CheckOther::checkEvaluationOrder +Yes CheckOther::checkFuncArgNamesDifferent +Yes CheckOther::checkIncompleteArrayFill +Yes CheckOther::checkIncompleteStatement +No CheckOther::checkInterlockedDecrement require:windows-platform +Yes CheckOther::checkInvalidFree +Yes CheckOther::checkKnownArgument +Yes CheckOther::checkKnownPointerToBool +Yes CheckOther::checkMisusedScopedObject +Yes CheckOther::checkModuloOfOne +Yes CheckOther::checkNanInArithmeticExpression +Yes CheckOther::checkNegativeBitwiseShift +Yes CheckOther::checkOverlappingWrite +Yes CheckOther::checkPassByReference +Yes CheckOther::checkRedundantAssignment +Yes CheckOther::checkRedundantCopy +Yes CheckOther::checkRedundantPointerOp +Yes CheckOther::checkShadowVariables +Yes CheckOther::checkSignOfUnsignedVariable +Yes CheckOther::checkSuspiciousCaseInSwitch +Yes CheckOther::checkSuspiciousSemicolon +Yes CheckOther::checkUnreachableCode +Yes CheckOther::checkUnusedLabel +Yes CheckOther::checkVarFuncNullUB +Yes CheckOther::checkVariableScope +Yes CheckOther::checkZeroDivision +Yes CheckOther::clarifyCalculation +Yes CheckOther::clarifyStatement +Yes CheckOther::invalidPointerCast +Yes CheckOther::redundantBitwiseOperationInSwitch +Yes CheckOther::warningOldStylePointerCast +Yes CheckPostfixOperator::postfixOperator +Yes CheckSizeof::checkSizeofForArrayParameter +Yes CheckSizeof::checkSizeofForNumericParameter +Yes CheckSizeof::checkSizeofForPointerSize +Yes CheckSizeof::sizeofCalculation +Yes CheckSizeof::sizeofFunction +Yes CheckSizeof::sizeofVoid +Yes CheckSizeof::sizeofsizeof +Yes CheckSizeof::suspiciousSizeofCalculation +Yes CheckStl::checkDereferenceInvalidIterator +Yes CheckStl::checkDereferenceInvalidIterator2 +Yes CheckStl::checkFindInsert +Yes CheckStl::checkMutexes +Yes CheckStl::erase +Yes CheckStl::eraseIteratorOutOfBounds +Yes CheckStl::if_find +Yes CheckStl::invalidContainer +Yes CheckStl::iterators +Yes CheckStl::knownEmptyContainer +Yes CheckStl::misMatchingContainerIterator +Yes CheckStl::misMatchingContainers +Yes CheckStl::missingComparison +Yes CheckStl::negativeIndex +Yes CheckStl::outOfBounds +Yes CheckStl::outOfBoundsIndexExpression +Yes CheckStl::redundantCondition +No CheckStl::size require:performance,c++03 +Yes CheckStl::stlBoundaries +Yes CheckStl::stlOutOfBounds +Yes CheckStl::string_c_str +Yes CheckStl::useStlAlgorithm +Yes CheckStl::uselessCalls +Yes CheckString::checkAlwaysTrueOrFalseStringCompare +Yes CheckString::checkIncorrectStringCompare +Yes CheckString::checkSuspiciousStringCompare +Yes CheckString::overlappingStrcmp +Yes CheckString::sprintfOverlappingData +Yes CheckString::strPlusChar +Yes CheckString::stringLiteralWrite +Yes CheckType::checkFloatToIntegerOverflow +Yes CheckType::checkIntegerOverflow +Yes CheckType::checkLongCast +Yes CheckType::checkSignConversion +Yes CheckType::checkTooBigBitwiseShift +Yes CheckUninitVar::check +Yes CheckUninitVar::valueFlowUninit +Yes CheckUnusedFunctions::check +Yes CheckUnusedVar::checkFunctionVariableUsage +Yes CheckUnusedVar::checkStructMemberUsage +Yes CheckVaarg::va_list_usage +Yes CheckVaarg::va_start_argument + + +Premium checkers +---------------- +Not available, Cppcheck Premium is not used + + +Autosar +------- +Not available, Cppcheck Premium is not used + + +Cert C +------ +Not available, Cppcheck Premium is not used + + +Cert C++ +-------- +Not available, Cppcheck Premium is not used + + +Misra C +------- +Misra is not enabled + + +Misra C++ 2008 +-------------- +Not available, Cppcheck Premium is not used + + +Misra C++ 2023 +-------------- +Not available, Cppcheck Premium is not used diff --git a/libraries/CAN/err.xml b/libraries/CAN/err.xml new file mode 100644 index 00000000..fc09c501 --- /dev/null +++ b/libraries/CAN/err.xml @@ -0,0 +1,418 @@ + + + + + + + + + + CANControllerClass::_txData + + + + CANControllerClass::_rxData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CAN_msg_tx + + + + CAN_msg_rx + + + + CANXMC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + beginPacket + + + + beginExtendedPacket + + + + packetId + + + + packetExtended + + + + packetRtr + + + + packetDlc + + + + getTxMessage + + + + getRxMessage + + + + begin + + + + observe + + + + loopback + + + + sleep + + + + wakeup + + + + diff --git a/libraries/CAN/src/CANController.cpp b/libraries/CAN/src/CANController.cpp index e0080311..f341435b 100644 --- a/libraries/CAN/src/CANController.cpp +++ b/libraries/CAN/src/CANController.cpp @@ -6,88 +6,96 @@ CANControllerClass::CANControllerClass() : _onReceive(NULL), - _packetBegun(false), _txId(-1), _txExtended(-1), _txRtr(false), _txDlc(0), + _packetBegun(false), + _txId(-1), + _txExtended(-1), + _txRtr(false), + _txDlc(0), _txLength(0), - _rxId(-1), _rxExtended(false), _rxRtr(false), _rxDlc(0), _rxLength(0), + _rxId(-1), + _rxExtended(false), + _rxRtr(false), + _rxDlc(0), + _rxLength(0), _rxIndex(0) { - // overide Stream timeout value - setTimeout(0); + // overide Stream timeout value + setTimeout(0); } CANControllerClass::~CANControllerClass() {} int CANControllerClass::begin(long /*baudRate*/) { - _packetBegun = false; - _txId = -1; - _txRtr = false; - _txDlc = 0; - _txLength = 0; - - _rxId = -1; - _rxRtr = false; - _rxDlc = 0; - _rxLength = 0; - _rxIndex = 0; - - return 1; + _packetBegun = false; + _txId = -1; + _txRtr = false; + _txDlc = 0; + _txLength = 0; + + _rxId = -1; + _rxRtr = false; + _rxDlc = 0; + _rxLength = 0; + _rxIndex = 0; + + return 1; } void CANControllerClass::end() {} int CANControllerClass::beginPacket(int id, int dlc, bool rtr) { - if (id < 0 || id > 0x7FF) { - return 0; - } + if (id < 0 || id > 0x7FF) { + return 0; + } - if (dlc > 8) { - return 0; - } + if (dlc > 8) { + return 0; + } - _packetBegun = true; - _txId = id; - _txExtended = false; - _txRtr = rtr; - _txDlc = dlc; - _txLength = 0; + _packetBegun = true; + _txId = id; + _txExtended = false; + _txRtr = rtr; + _txDlc = dlc; + _txLength = 0; - memset(_txData, 0x00, sizeof(_txData)); + memset(_txData, 0x00, sizeof(_txData)); - return 1; + return 1; } int CANControllerClass::beginExtendedPacket(long id, int dlc, bool rtr) { - if (id < 0 || id > 0x1FFFFFFF) { - return 0; - } + if (id < 0 || id > 0x1FFFFFFF) { + return 0; + } - if (dlc > 8) { - return 0; - } + if (dlc > 8) { + return 0; + } - _packetBegun = true; - _txId = id; - _txExtended = true; - _txRtr = rtr; - _txDlc = dlc; - _txLength = 0; + _packetBegun = true; + _txId = id; + _txExtended = true; + _txRtr = rtr; + _txDlc = dlc; + _txLength = 0; - memset(_txData, 0x00, sizeof(_txData)); + memset(_txData, 0x00, sizeof(_txData)); - return 1; + return 1; } int CANControllerClass::endPacket() { - if (!_packetBegun) { - return 0; - } - _packetBegun = false; + if (!_packetBegun) { + return 0; + } + _packetBegun = false; - if (_txDlc >= 0) { - _txLength = _txDlc; - } + if (_txDlc >= 0) { + _txLength = _txDlc; + } - return 1; + return 1; } int CANControllerClass::parsePacket() { return 0; } @@ -100,48 +108,44 @@ bool CANControllerClass::packetRtr() { return _rxRtr; } int CANControllerClass::packetDlc() { return _rxDlc; } -size_t CANControllerClass::write(uint8_t byte) { - return write(&byte, sizeof(byte)); -} +size_t CANControllerClass::write(uint8_t byte) { return write(&byte, sizeof(byte)); } size_t CANControllerClass::write(const uint8_t *buffer, size_t size) { - if (!_packetBegun) { - return 0; - } + if (!_packetBegun) { + return 0; + } - if (size > (sizeof(_txData) - _txLength)) { - size = sizeof(_txData) - _txLength; - } + if (size > (sizeof(_txData) - _txLength)) { + size = sizeof(_txData) - _txLength; + } - memcpy(&_txData[_txLength], buffer, size); - _txLength += size; + memcpy(&_txData[_txLength], buffer, size); + _txLength += size; - return size; + return size; } int CANControllerClass::available() { return (_rxLength - _rxIndex); } int CANControllerClass::read() { - if (!available()) { - return -1; - } + if (!available()) { + return -1; + } - return _rxData[_rxIndex++]; + return _rxData[_rxIndex++]; } int CANControllerClass::peek() { - if (!available()) { - return -1; - } + if (!available()) { + return -1; + } - return _rxData[_rxIndex]; + return _rxData[_rxIndex]; } void CANControllerClass::flush() {} -void CANControllerClass::onReceive(void (*callback)(int)) { - _onReceive = callback; -} +void CANControllerClass::onReceive(void (*callback)(int)) { _onReceive = callback; } int CANControllerClass::filter(int /*id*/, int /*mask*/) { return 0; } diff --git a/libraries/CAN/src/CANController.h b/libraries/CAN/src/CANController.h index 6d914279..b89e1872 100644 --- a/libraries/CAN/src/CANController.h +++ b/libraries/CAN/src/CANController.h @@ -56,9 +56,9 @@ class CANControllerClass : public Stream { bool _packetBegun; long _txId; bool _txExtended; - bool _txRtr; - int _txDlc; - int _txLength; + bool _txRtr; // if it Remote Transmission Request + int _txDlc; // DLC: Data Length Code specifies number of bytes in the data field + int _txLength; // index of txData uint8_t _txData[8]; long _rxId; diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index 55ea1d81..88220b63 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -1,40 +1,52 @@ #include "CANXMC.h" #include -/* CAN Receive Message Object definition, can also be used for transmit remote - * frame*/ -XMC_CAN_MO_t CAN_msg_rx = { - .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO0, - {0xFF, XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode - XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} - {0x000, 1U}, // {can_id_mask, can_ide_mask} - .can_data_length = 0U, - .can_mo_type = XMC_CAN_MO_TYPE_RECMSGOBJ, -}; - -/* CAN Transmit Message Object definition */ -XMC_CAN_MO_t CAN_msg_tx = { - .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO1, - {0xFF, XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode - XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} - {0x7FFU, 1U}, // {can_id_mask, can_ide_mask} - .can_data_length = 0U, - .can_mo_type = XMC_CAN_MO_TYPE_TRANSMSGOBJ, -}; - /* Flag for receive interrupt*/ static volatile bool can_frame_received = false; /* construct with configuration of different target */ -CANXMC::CANXMC(XMC_ARD_CAN_t *conf) { _XMC_CAN_config = conf; } +CANXMC::CANXMC(XMC_ARD_CAN_t *conf) { + + _XMC_CAN_config = conf; + /* CAN Receive Message Object definition, can also be used for transmit remote + * frame*/ + CAN_msg_tx = { + .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO1, + {0xFFFU, + XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode + XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} + {0x1FFFFFFFU, 0U}, // {can_id_mask, can_ide_mask} + .can_data_length = 0U, + .can_mo_type = XMC_CAN_MO_TYPE_TRANSMSGOBJ, + }; + + /* CAN Transmit Message Object definition */ + CAN_msg_rx = { + .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO0, + {0xFFFU, + XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode + XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} + {0x00000000U, 0U}, // {can_id_mask, can_ide_mask} + .can_data_length = 0U, + .can_mo_type = XMC_CAN_MO_TYPE_RECMSGOBJ, + }; +} + +CANXMC::~CANXMC() { end(); } -CANXMC::~CANXMC() {} +/* Getter function for CAN_msg_tx */ +XMC_CAN_MO_t *CANXMC::getTxMessage() { return &CAN_msg_tx; } +/* Getter function for CAN_msg_rx */ +XMC_CAN_MO_t *CANXMC::getRxMessage() { return &CAN_msg_rx; } + +/* int CANXMC::setIdentifier(long id) { // TODO: delete in the future! // figure out filtering problem for xmc4700 - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); - return 0; + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); + return 0; }; +*/ /** * @brief Initializes the CAN interface with the specified baudrate. @@ -42,73 +54,69 @@ int CANXMC::setIdentifier(long id) { // TODO: delete in the future! * This function configures the CAN bit time, enables the CAN node, and sets up * the necessary pin configurations. * - * @param baudrate The desired baudrate for the CAN interface. The default value + * @param baudRate The desired baudrate for the CAN interface. The default value * is 500,000 bps. * @return Returns 1 if the initialization is successful, 0 otherwise. */ -int CANXMC::begin(long baudrate /*= 500e3*/) { - /* CAN bit time configuration*/ - XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { - .can_frequency = _XMC_CAN_config->can_frequency, - .baudrate = (uint32_t)baudrate, - .sample_point = (uint16_t)(80 * 100), - .sjw = (uint16_t)1, - }; - - XMC_CAN_Enable(CAN_xmc); - - /* Configuration of CAN Node and enable the clock */ - XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, - _XMC_CAN_config->can_frequency); - if (XMC_CAN_STATUS_SUCCESS == - XMC_CAN_NODE_NominalBitTimeConfigureEx(_XMC_CAN_config->can_node, - &CAN_NODE_bit_time_config)) { - XMC_CAN_NODE_EnableConfigurationChange(_XMC_CAN_config->can_node); - XMC_CAN_NODE_SetInitBit(_XMC_CAN_config->can_node); - XMC_CAN_NODE_ReSetAnalyzerMode(_XMC_CAN_config->can_node); - - /* Configure CAN NODE input pin */ - XMC_GPIO_Init(_XMC_CAN_config->tx.port, _XMC_CAN_config->tx.pin, - &(_XMC_CAN_config->tx_config)); - XMC_GPIO_SetHardwareControl(_XMC_CAN_config->tx.port, - _XMC_CAN_config->tx.pin, - XMC_GPIO_HWCTRL_DISABLED); - XMC_GPIO_Init(_XMC_CAN_config->rx.port, _XMC_CAN_config->rx.pin, - &(_XMC_CAN_config->rx_config)); - XMC_GPIO_SetHardwareControl(_XMC_CAN_config->rx.port, - _XMC_CAN_config->rx.pin, - XMC_GPIO_HWCTRL_DISABLED); - XMC_CAN_NODE_SetReceiveInput(_XMC_CAN_config->can_node, - _XMC_CAN_config->node_input); - - /* Allocate the rx and tx message object*/ - XMC_CAN_MO_Config(&CAN_msg_rx); - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 0); - XMC_CAN_MO_Config(&CAN_msg_tx); - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 1); - - /* Message object accepts the reception of both, standard and extended - * frames */ - XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); +int CANXMC::begin(long baudRate /*= 500e3*/) { + /* reset CAN nodes*/ + XMC_CAN_Disable(CAN_xmc); + + /* CAN bit time configuration*/ + XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { + .can_frequency = _XMC_CAN_config->can_frequency, + .baudrate = (uint32_t)baudRate, + .sample_point = (uint16_t)(80 * 100), + .sjw = (uint16_t)1, + }; + + XMC_CAN_Enable(CAN_xmc); + + /* Configuration of CAN Node and enable the clock */ + XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, _XMC_CAN_config->can_frequency); + if (XMC_CAN_STATUS_SUCCESS == XMC_CAN_NODE_NominalBitTimeConfigureEx( + _XMC_CAN_config->can_node, &CAN_NODE_bit_time_config)) { + XMC_CAN_NODE_EnableConfigurationChange(_XMC_CAN_config->can_node); + XMC_CAN_NODE_SetInitBit(_XMC_CAN_config->can_node); + XMC_CAN_NODE_ReSetAnalyzerMode(_XMC_CAN_config->can_node); + + /* Configure CAN NODE input pin */ + XMC_GPIO_Init(_XMC_CAN_config->tx.port, _XMC_CAN_config->tx.pin, + &(_XMC_CAN_config->tx_config)); + XMC_GPIO_SetHardwareControl(_XMC_CAN_config->tx.port, _XMC_CAN_config->tx.pin, + XMC_GPIO_HWCTRL_DISABLED); + XMC_GPIO_Init(_XMC_CAN_config->rx.port, _XMC_CAN_config->rx.pin, + &(_XMC_CAN_config->rx_config)); + XMC_GPIO_SetHardwareControl(_XMC_CAN_config->rx.port, _XMC_CAN_config->rx.pin, + XMC_GPIO_HWCTRL_DISABLED); + XMC_CAN_NODE_SetReceiveInput(_XMC_CAN_config->can_node, _XMC_CAN_config->node_input); + + /* Allocate the rx and tx message object*/ + XMC_CAN_MO_Config(&CAN_msg_rx); + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 0); + XMC_CAN_MO_Config(&CAN_msg_tx); + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, _XMC_CAN_config->can_node_num, 1); + + /* Message object accepts the reception of both, standard and extended + * frames */ + XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); #if (UC_SERIES == XMC14) - // select interrupt source (A,B,C etc) input to NVIC node (only for XMC1400 - // devices) - XMC_SCU_SetInterruptControl(_XMC_CAN_config->irq_num, - _XMC_CAN_config->irq_source); + // select interrupt source (A,B,C etc) input to NVIC node (only for XMC1400 + // devices) + XMC_SCU_SetInterruptControl(_XMC_CAN_config->irq_num, _XMC_CAN_config->irq_source); #endif - XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, - XMC_CAN_MO_POINTER_EVENT_RECEIVE, - _XMC_CAN_config->irq_service_request); - XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); - XMC_CAN_NODE_DisableConfigurationChange(_XMC_CAN_config->can_node); - XMC_CAN_NODE_ResetInitBit(_XMC_CAN_config->can_node); + XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, XMC_CAN_MO_POINTER_EVENT_RECEIVE, + _XMC_CAN_config->irq_service_request); + XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); + XMC_CAN_NODE_DisableConfigurationChange(_XMC_CAN_config->can_node); + XMC_CAN_NODE_ResetInitBit(_XMC_CAN_config->can_node); - return 1; - } else { - return 0; - } + return 1; + } else { + return 0; + } }; /** @@ -119,9 +127,9 @@ int CANXMC::begin(long baudrate /*= 500e3*/) { * using the XMC_CAN_Disable function. */ void CANXMC::end() { - XMC_CAN_MO_DisableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); - XMC_CAN_Disable(CAN_xmc); - CANControllerClass::end(); + XMC_CAN_MO_DisableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); + XMC_CAN_Disable(CAN_xmc); + CANControllerClass::end(); }; /** @@ -130,54 +138,54 @@ void CANXMC::end() { * @return 1 if the packet was successfully transmitted, 0 otherwise. */ int CANXMC::endPacket() { - if (!CANControllerClass::endPacket()) { - return 0; - } - - if (_txRtr) { - // TODO: LJ: how to receive remote frame? and response... - - /* Configure remote frame to be transmitted and data length code */ - if (_txExtended) { - XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); - } else { - XMC_CAN_MO_SetStandardID(&CAN_msg_rx); + if (!CANControllerClass::endPacket()) { + return 0; } - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, _txId); - XMC_CAN_MO_SetDataLengthCode(&CAN_msg_rx, _txLength); - XMC_CAN_MO_UpdateData(&CAN_msg_rx); - /* Send remote frame */ - XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_rx); + if (_txRtr) { + // TODO: LJ: how to receive remote frame? and response... - if (send_status == XMC_CAN_STATUS_SUCCESS) { - return 1; - } else { - return 0; - } + /* Configure remote frame to be transmitted and data length code */ + if (_txExtended) { + XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); + } else { + XMC_CAN_MO_SetStandardID(&CAN_msg_rx); + } + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, _txId); + XMC_CAN_MO_SetDataLengthCode(&CAN_msg_rx, _txLength); + XMC_CAN_MO_UpdateData(&CAN_msg_rx); - } else { - /* Configure data frame to be transmitted and data length code */ - if (_txExtended) { - XMC_CAN_MO_SetExtendedID(&CAN_msg_tx); - } else { - XMC_CAN_MO_SetStandardID(&CAN_msg_tx); - } - XMC_CAN_MO_SetIdentifier(&CAN_msg_tx, _txId); - memcpy(CAN_msg_tx.can_data_byte, _txData, _txLength); - XMC_CAN_MO_SetDataLengthCode(&CAN_msg_tx, _txLength); - - XMC_CAN_MO_UpdateData(&CAN_msg_tx); + /* Send remote frame */ + XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_rx); - /* Send data frame */ - XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_tx); + if (send_status == XMC_CAN_STATUS_SUCCESS) { + return 1; + } else { + return 0; + } - if (send_status == XMC_CAN_STATUS_SUCCESS) { - return 1; } else { - return 0; + /* Configure data frame to be transmitted and data length code */ + if (_txExtended) { + XMC_CAN_MO_SetExtendedID(&CAN_msg_tx); + } else { + XMC_CAN_MO_SetStandardID(&CAN_msg_tx); + } + XMC_CAN_MO_SetIdentifier(&CAN_msg_tx, _txId); + memcpy(CAN_msg_tx.can_data_byte, _txData, _txLength); + XMC_CAN_MO_SetDataLengthCode(&CAN_msg_tx, _txLength); + + XMC_CAN_MO_UpdateData(&CAN_msg_tx); + + /* Send data frame */ + XMC_CAN_STATUS_t send_status = XMC_CAN_MO_Transmit(&CAN_msg_tx); + + if (send_status == XMC_CAN_STATUS_SUCCESS) { + return 1; + } else { + return 0; + } } - } }; /** @@ -195,35 +203,35 @@ int CANXMC::endPacket() { * @return The length of the received packet. */ int CANXMC::parsePacket() { - while ((XMC_CAN_MO_GetStatus(&CAN_msg_rx) & CAN_MO_MOSTAT_NEWDAT_Msk) >> - CAN_MO_MOSTAT_NEWDAT_Pos != - 1) - ; - XMC_CAN_MO_Receive(&CAN_msg_rx); - - /* check CAN frame type */ - _rxId = XMC_CAN_MO_GetIdentifier(&CAN_msg_rx); - - if (CAN_msg_rx.can_id_mode == XMC_CAN_FRAME_TYPE_EXTENDED_29BITS) { - _rxExtended = true; - } else { - _rxExtended = false; - }; - - _rxRtr = CAN_msg_rx.can_mo_ptr->MOFCR & (uint32_t)CAN_MO_MOFCR_RMM_Msk; - _rxDlc = CAN_msg_rx.can_data_length; - if (_rxRtr) { - _rxLength = 0; - } else { - _rxLength = _rxDlc; - memcpy(_rxData, CAN_msg_rx.can_data_byte, _rxLength); - } - /* set the flag back and wait for next receive */ - can_frame_received = false; - - _rxIndex = 0; - - return _rxLength; + while ((XMC_CAN_MO_GetStatus(&CAN_msg_rx) & CAN_MO_MOSTAT_NEWDAT_Msk) >> + CAN_MO_MOSTAT_NEWDAT_Pos != + 1) + ; + XMC_CAN_MO_Receive(&CAN_msg_rx); + + /* check CAN frame type */ + _rxId = XMC_CAN_MO_GetIdentifier(&CAN_msg_rx); + + if (CAN_msg_rx.can_id_mode == XMC_CAN_FRAME_TYPE_EXTENDED_29BITS) { + _rxExtended = true; + } else { + _rxExtended = false; + }; + + _rxRtr = CAN_msg_rx.can_mo_ptr->MOFCR & (uint32_t)CAN_MO_MOFCR_RMM_Msk; + _rxDlc = CAN_msg_rx.can_data_length; + if (_rxRtr) { + _rxLength = 0; + } else { + _rxLength = _rxDlc; + memcpy(_rxData, CAN_msg_rx.can_data_byte, _rxLength); + } + /* set the flag back and wait for next receive */ + can_frame_received = false; + + _rxIndex = 0; + + return _rxLength; }; /** @@ -234,12 +242,12 @@ int CANXMC::parsePacket() { * disable the callback. */ void CANXMC::onReceive(void (*callback)(int)) { - CANControllerClass::onReceive(callback); - if (callback) { - NVIC_EnableIRQ(_XMC_CAN_config->irq_num); - } else { - NVIC_DisableIRQ(_XMC_CAN_config->irq_num); - } + CANControllerClass::onReceive(callback); + if (callback) { + NVIC_EnableIRQ(_XMC_CAN_config->irq_num); + } else { + NVIC_DisableIRQ(_XMC_CAN_config->irq_num); + } }; /** @@ -250,12 +258,12 @@ void CANXMC::onReceive(void (*callback)(int)) { * @return 1 if the filter was set successfully, 0 otherwise. */ int CANXMC::filter(int id, int mask) { - XMC_CAN_MO_SetStandardID(&CAN_msg_rx); - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); - XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); - XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); + XMC_CAN_MO_SetStandardID(&CAN_msg_rx); + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); + XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); + XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); - return 1; + return 1; }; /** @@ -266,76 +274,80 @@ int CANXMC::filter(int id, int mask) { * @return 1 if the filter was set successfully, 0 otherwise. */ int CANXMC::filterExtended(long id, long mask) { - XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); - XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); - XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); - XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); - return 1; + XMC_CAN_MO_SetExtendedID(&CAN_msg_rx); + XMC_CAN_MO_SetIdentifier(&CAN_msg_rx, id); + XMC_CAN_MO_AcceptOnlyMatchingIDE(&CAN_msg_rx); + XMC_CAN_MO_SetAcceptanceMask(&CAN_msg_rx, mask); + return 1; }; int CANXMC::observe() { - // TODO: LJ: no idea what this is - return 0; + // TODO: LJ: no idea what this is + return 0; }; +/** + * Puts the CAN module into loopback mode. + * This function puts the CAN module into loopback mode by enabling the loopback + * mode for both CAN nodes. It also initializes the CAN message objects for + * loopback mode. + * @return true if the CAN module is successfully put into loopback mode, false + * otherwise. + */ int CANXMC::loopback() { - // TODO: LJ: It is actually only some application level... it is not - // library... - - /* reset CAN nodes*/ - XMC_CAN_Disable(CAN_xmc); - /* CAN bit time configuration*/ - XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { - .can_frequency = _XMC_CAN_config->can_frequency, - .baudrate = (uint32_t)50000, // TODO: LJ: need to be changed - .sample_point = (uint16_t)(80 * 100), - .sjw = (uint16_t)1, - }; - - XMC_CAN_Enable(CAN_xmc); - /* Configuration of CAN Node and enable the clock */ - XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, - _XMC_CAN_config->can_frequency); - if (XMC_CAN_STATUS_SUCCESS == XMC_CAN_NODE_NominalBitTimeConfigureEx( - CAN_NODE1, &CAN_NODE_bit_time_config)) { - - /* Enable CAN node 1 for Loop-back mode */ - XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE1); - XMC_CAN_NODE_EnableLoopBack(CAN_NODE1); - XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE1); - XMC_CAN_NODE_ResetInitBit(CAN_NODE1); - - /* Initializes CAN tx Message Object for loopback */ - XMC_CAN_MO_Config(&CAN_msg_tx); - /* Allocate tx Message object to Node 1 */ - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 1, 1); - } else { - return 0; - } - - if (XMC_CAN_STATUS_SUCCESS == XMC_CAN_NODE_NominalBitTimeConfigureEx( - CAN_NODE0, &CAN_NODE_bit_time_config)) { - /* Enable CAN node 0 for Loop-back mode */ - XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE0); - XMC_CAN_NODE_EnableLoopBack(CAN_NODE0); - XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE0); - XMC_CAN_NODE_ResetInitBit(CAN_NODE0); - - /* Initializes CAN rx Message Object for loopback */ - XMC_CAN_MO_Config(&CAN_msg_rx); - /*Allocate rx Message object to Node 0 */ - XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 0, 0); - XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); - /* Enable receive event */ - XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, - XMC_CAN_MO_POINTER_EVENT_RECEIVE, - _XMC_CAN_config->irq_service_request); - XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); - return 1; - } else { - return 0; - } + /* reset CAN nodes*/ + XMC_CAN_Disable(CAN_xmc); + /* CAN bit time configuration*/ + XMC_CAN_NODE_NOMINAL_BIT_TIME_CONFIG_t CAN_NODE_bit_time_config = { + .can_frequency = _XMC_CAN_config->can_frequency, + .baudrate = (uint32_t)50000, // TODO: LJ: need to be changed + .sample_point = (uint16_t)(80 * 100), + .sjw = (uint16_t)1, + }; + + XMC_CAN_Enable(CAN_xmc); + /* Configuration of CAN Node and enable the clock */ + XMC_CAN_InitEx(CAN_xmc, _XMC_CAN_config->can_clock, _XMC_CAN_config->can_frequency); + if (XMC_CAN_STATUS_SUCCESS == + XMC_CAN_NODE_NominalBitTimeConfigureEx(CAN_NODE1, &CAN_NODE_bit_time_config)) { + + /* Enable CAN node 1 for Loop-back mode */ + XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE1); + XMC_CAN_NODE_EnableLoopBack(CAN_NODE1); + XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE1); + XMC_CAN_NODE_ResetInitBit(CAN_NODE1); + + /* Initializes CAN tx Message Object for loopback */ + XMC_CAN_MO_Config(&CAN_msg_tx); + /* Allocate tx Message object to Node 1 */ + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 1, 1); + } else { + return 0; + } + + if (XMC_CAN_STATUS_SUCCESS == + XMC_CAN_NODE_NominalBitTimeConfigureEx(CAN_NODE0, &CAN_NODE_bit_time_config)) { + /* Enable CAN node 0 for Loop-back mode */ + XMC_CAN_NODE_EnableConfigurationChange(CAN_NODE0); + XMC_CAN_NODE_EnableLoopBack(CAN_NODE0); + XMC_CAN_NODE_DisableConfigurationChange(CAN_NODE0); + XMC_CAN_NODE_ResetInitBit(CAN_NODE0); + + /* Initializes CAN rx Message Object for loopback */ + XMC_CAN_MO_Config(&CAN_msg_rx); + /*Allocate rx Message object to Node 0 */ + XMC_CAN_AllocateMOtoNodeList(CAN_xmc, 0, 0); + XMC_CAN_MO_AcceptStandardAndExtendedID(&CAN_msg_rx); + /* Enable receive event */ + XMC_CAN_MO_SetEventNodePointer(&CAN_msg_rx, XMC_CAN_MO_POINTER_EVENT_RECEIVE, + _XMC_CAN_config->irq_service_request); + XMC_CAN_MO_EnableEvent(&CAN_msg_rx, XMC_CAN_MO_EVENT_RECEIVE); + + return 1; + } else { + return 0; + } }; /** @@ -346,8 +358,8 @@ int CANXMC::loopback() { * otherwise. */ int CANXMC::sleep() { - CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; - return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0; + CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; + return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0; }; /** @@ -357,8 +369,8 @@ int CANXMC::sleep() { * @return true if the CAN module is successfully woken up, false otherwise. */ int CANXMC::wakeup() { - CAN_xmc->CLC &= CAN_CLC_EDIS_Msk; - return CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0; + CAN_xmc->CLC &= CAN_CLC_EDIS_Msk; + return CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0; }; /** @@ -368,10 +380,10 @@ int CANXMC::wakeup() { * frames. */ void CANXMC::onInterrupt() { - if (can_frame_received == true) { - CAN.parsePacket(); - CAN._onReceive(CAN.available()); - } + if (can_frame_received == true) { + CAN.parsePacket(); + CAN._onReceive(CAN.available()); + } }; /* Interrupt Handler */ @@ -389,19 +401,19 @@ void CANXMC::onInterrupt() { extern "C" { #if (UC_FAMILY == XMC4) void CAN0_7_IRQHandler() { - /* Set the frame received flag to true */ - can_frame_received = true; + /* Set the frame received flag to true */ + can_frame_received = true; - CAN.onInterrupt(); + CAN.onInterrupt(); } #endif #if (UC_SERIES == XMC14) void CAN0_3_IRQHandler() { - /* Set the frame received flag to true */ - can_frame_received = true; + /* Set the frame received flag to true */ + can_frame_received = true; - CAN.onInterrupt(); + CAN.onInterrupt(); } #endif } diff --git a/libraries/CAN/src/CANXMC.h b/libraries/CAN/src/CANXMC.h index 9e63e43c..26125512 100644 --- a/libraries/CAN/src/CANXMC.h +++ b/libraries/CAN/src/CANXMC.h @@ -28,8 +28,14 @@ class CANXMC : public CANControllerClass { int setIdentifier(long); static void onInterrupt(); + // Getter functions for CAN_msg_rx and CAN_msg_tx + XMC_CAN_MO_t *getRxMessage(); + XMC_CAN_MO_t *getTxMessage(); + private: XMC_ARD_CAN_t *_XMC_CAN_config; + XMC_CAN_MO_t CAN_msg_rx; + XMC_CAN_MO_t CAN_msg_tx; }; extern CANXMC CAN; \ No newline at end of file diff --git a/libraries/CAN/test/unit/src/features/Test_CAN.hpp b/libraries/CAN/test/unit/src/features/Test_CAN.hpp index b6b738dc..1b6d0808 100644 --- a/libraries/CAN/test/unit/src/features/Test_CAN.hpp +++ b/libraries/CAN/test/unit/src/features/Test_CAN.hpp @@ -11,8 +11,7 @@ void CAN_suiteTearDown(void); // variables used in the tests below that have to be accessed in the setup and tear down methods -static CANXMC dut = CAN; - +extern CANXMC CAN; // test includes that may require dut @@ -38,27 +37,128 @@ static TEST_TEAR_DOWN(CAN_internal) TEST_IFX(CAN_internal, checkUnsupportedFunctionality) { TEST_ASSERT_FALSE(CAN.observe() ); + // TEST_ASSERT_TRUE(CAN.packetRtr() ); Remote frame not tested } TEST_IFX(CAN_internal, checkSupportedFunctionality) { + /*TODO: How to test this? TEST_ASSERT_TRUE(CAN.begin() ); + // TEST_ASSERT_TRUE(CAN.end() ); + // TEST_ASSERT_TRUE(CAN.endPacket() ); // need to configure + // TEST_ASSERT_TRUE(CAN.parsePacket() ); // return value + // TEST_ASSERT_TRUE(CAN.onReceive(0) ); // void + TEST_ASSERT_TRUE(CAN.filter(0x12, 0x7FF) ); + TEST_ASSERT_TRUE(CAN.filterExtended(0x12345678, 0x1FFFFFFF) ); + + TEST_ASSERT_TRUE(CAN.loopback() ); + + // TEST_ASSERT_TRUE(CAN.setIdentifier(0x123) ); // need to be deleted in the future + TEST_ASSERT_TRUE(CAN.beginPacket(0x12, 8, false) ); + TEST_ASSERT_TRUE(CAN.beginExtendedPacket(0xFFF, 8, false) ); + + TEST_ASSERT_TRUE(CAN.available() ); + TEST_ASSERT_TRUE(CAN.read() ); + TEST_ASSERT_TRUE(CAN.peek() ); + // TEST_ASSERT_TRUE(CAN.flush() ); // void + TEST_ASSERT_TRUE(CAN.write(0x12) ); + TEST_ASSERT_TRUE(CAN.write((uint8_t*)0x12, 1) ); + + TEST_ASSERT_TRUE(CAN.sleep() ); + + // TEST_ASSERT_TRUE(CAN.onInterrupt() ); // void + // TEST_ASSERT_TRUE(CAN.packetDlc() ); + // TEST_ASSERT_TRUE(CAN.packetExtended() ); + // TEST_ASSERT_TRUE(CAN.packetId() ); + */ } +// Test case for CAN_msg_tx initialization +void test_CAN_msg_tx_initialization(void) +{ + // Get the Tx message object + XMC_CAN_MO_t* txMessage = CAN.getTxMessage(); + + // Verify that the Tx message object is correctly initialized + TEST_ASSERT_NOT_NULL(txMessage); + TEST_ASSERT_EQUAL_UINT32(XMC_CAN_FRAME_TYPE_STANDARD_11BITS, txMessage->can_id_mode); // Standard 11-bit identifier + TEST_ASSERT_EQUAL_UINT32(XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1, txMessage->can_priority); + TEST_ASSERT_EQUAL_UINT32(0x1FFFFFFFU, txMessage->can_id_mask); + TEST_ASSERT_EQUAL_UINT32(0U, txMessage->can_ide_mask); // send both standard and extended frames + TEST_ASSERT_EQUAL_UINT32(XMC_CAN_MO_TYPE_TRANSMSGOBJ, txMessage->can_mo_type); +} -TEST_IFX(CAN_internal, checkWhatever) +// Test case for CAN_msg_rx initialization +void test_CAN_msg_rx_initialization(void) { + // Get the Rx message object + XMC_CAN_MO_t* rxMessage = CAN.getRxMessage(); + + // Verify that the Rx message object is correctly initialized + TEST_ASSERT_NOT_NULL(rxMessage); + TEST_ASSERT_EQUAL_UINT32(XMC_CAN_FRAME_TYPE_STANDARD_11BITS, rxMessage->can_id_mode); // Standard 11-bit identifier + TEST_ASSERT_EQUAL_UINT32(XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1, rxMessage->can_priority); + TEST_ASSERT_EQUAL_UINT32(0x000, rxMessage->can_id_mask); + TEST_ASSERT_EQUAL_UINT32(0U, rxMessage->can_ide_mask); // receive both standard and extended frames + TEST_ASSERT_EQUAL_UINT32(XMC_CAN_MO_TYPE_RECMSGOBJ, rxMessage->can_mo_type); + +} + +// Test case for CAN begin function with different baud rates +void test_CAN_initialization_with_baudrate_125k(void) { + int result = CAN.begin(125000); + TEST_ASSERT_EQUAL(1, result); +} + +void test_CAN_initialization_with_baudrate_250k(void) { + int result = CAN.begin(250000); + TEST_ASSERT_EQUAL(1, result); +} + +void test_CAN_initialization_with_baudrate_500k(void) { + int result = CAN.begin(500000); + TEST_ASSERT_EQUAL(1, result); } +void test_CAN_initialization_with_baudrate_1M(void) { + int result = CAN.begin(1000000); + TEST_ASSERT_EQUAL(1, result); +} + + +void test_CAN_beginPacket(void) { + int result = CAN.beginPacket(0x123); // Assuming 0x123 is the identifier + TEST_ASSERT_EQUAL(1, result); +} + +void test_CAN_write(void) { + uint8_t data[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + size_t result = CAN.write(data, sizeof(data)); + TEST_ASSERT_EQUAL(sizeof(data), result); +} + +void test_CAN_endPacket(void) { + int result = CAN.endPacket(); + TEST_ASSERT_EQUAL(1, result); +} + + // Bundle all tests to be executed for this test group static TEST_GROUP_RUNNER(CAN_internal) { RUN_TEST_CASE(CAN_internal, checkUnsupportedFunctionality); RUN_TEST_CASE(CAN_internal, checkSupportedFunctionality); - - RUN_TEST_CASE(CAN_internal, checkWhatever); + RUN_TEST(test_CAN_msg_tx_initialization); + RUN_TEST(test_CAN_msg_rx_initialization); + RUN_TEST(test_CAN_initialization_with_baudrate_125k); + RUN_TEST(test_CAN_initialization_with_baudrate_250k); + RUN_TEST(test_CAN_initialization_with_baudrate_1M); + RUN_TEST(test_CAN_initialization_with_baudrate_500k); + RUN_TEST(test_CAN_beginPacket); + RUN_TEST(test_CAN_write); + RUN_TEST(test_CAN_endPacket); } From 86bc2df5e83e864ba8e095ca6ce234ec502fc4eb Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Mon, 14 Oct 2024 09:24:10 +0200 Subject: [PATCH 03/13] style/build: update swtemplate and add baudrate for open serial monitor --- config/clang-tidy/.clang-tidy | 2 +- config/cppcheck/misra.json | 4 + config/cppcheck/misra_local.py | 4973 -------------------------------- libraries/CAN/Makefile | 15 +- 4 files changed, 8 insertions(+), 4986 deletions(-) create mode 100644 config/cppcheck/misra.json delete mode 100644 config/cppcheck/misra_local.py diff --git a/config/clang-tidy/.clang-tidy b/config/clang-tidy/.clang-tidy index 337b9017..db69638c 100644 --- a/config/clang-tidy/.clang-tidy +++ b/config/clang-tidy/.clang-tidy @@ -28,7 +28,7 @@ clang-analyzer-*, WarningsAsErrors: '' HeaderFilterRegex: '.*' #HeaderFilterRegex: '*spdlog/[^f].*' -AnalyzeTemporaryDtors: false +#AnalyzeTemporaryDtors: false FormatStyle: none CheckOptions: diff --git a/config/cppcheck/misra.json b/config/cppcheck/misra.json new file mode 100644 index 00000000..ef34b5d7 --- /dev/null +++ b/config/cppcheck/misra.json @@ -0,0 +1,4 @@ +{ + "script": "misra.py", + "args": [ "--rule-texts=config/cppcheck/misra.txt" ] +} \ No newline at end of file diff --git a/config/cppcheck/misra_local.py b/config/cppcheck/misra_local.py deleted file mode 100644 index f05ee632..00000000 --- a/config/cppcheck/misra_local.py +++ /dev/null @@ -1,4973 +0,0 @@ -#!/usr/bin/env python3 -# -# MISRA C 2012 checkers -# Partially reused for "MISRA C++ 2008" checking -# -# Example usage of this addon (scan a sourcefile main.cpp) -# cppcheck --dump main.cpp -# python misra.py --rule-texts= main.cpp.dump -# -# Limitations: This addon is released as open source. Rule texts can't be freely -# distributed. https://www.misra.org.uk/forum/viewtopic.php?f=56&t=1189 -# -# The MISRA standard documents may be obtained from https://www.misra.org.uk -# -# Total number of rules: 143 - -from __future__ import print_function - -import cppcheckdata -import itertools -import json -import sys -import re -import os -import argparse -import codecs -import string -import copy - -try: - from itertools import izip as zip -except ImportError: - pass - -import misra_9 - -def grouped(iterable, n): - """s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), (s2n,s2n+1,s2n+2,...s3n-1), ...""" - return zip(*[iter(iterable)] * n) - - -INT_TYPES = ['bool', 'char', 'short', 'int', 'long', 'long long'] - - -STDINT_TYPES = ['%s%d_t' % (n, v) for n, v in itertools.product( - ['int', 'uint', 'int_least', 'uint_least', 'int_fast', 'uint_fast'], - [8, 16, 32, 64])] - - -typeBits = { - 'CHAR': None, - 'SHORT': None, - 'INT': None, - 'LONG': None, - 'LONG_LONG': None, - 'POINTER': None -} - - -def isUnsignedType(ty): - return ty == 'unsigned' or ty.startswith('uint') - - -def simpleMatch(token, pattern): - return cppcheckdata.simpleMatch(token, pattern) - - -def rawlink(rawtoken): - if rawtoken.str == '}': - indent = 0 - while rawtoken: - if rawtoken.str == '}': - indent = indent + 1 - elif rawtoken.str == '{': - indent = indent - 1 - if indent == 0: - break - rawtoken = rawtoken.previous - else: - rawtoken = None - return rawtoken - - -# Identifiers described in Section 7 "Library" of C90 Standard -# Based on ISO/IEC9899:1990 Annex D -- Library summary and -# Annex E -- Implementation limits. -C90_STDLIB_IDENTIFIERS = { - # D.1 Errors - 'errno.h': ['EDOM', 'ERANGE', 'errno'], - # D.2 Common definitions - 'stddef.h': ['NULL', 'offsetof', 'ptrdiff_t', 'size_t', 'wchar_t'], - # D.3 Diagnostics - 'assert.h': ['NDEBUG', 'assert'], - # D.4 Character handling - 'ctype.h': [ - 'isalnum', 'isalpha', 'isblank', 'iscntrl', 'isdigit', - 'isgraph', 'islower', 'isprint', 'ispunct', 'isspace', - 'isupper', 'isxdigit', 'tolower', 'toupper', - ], - # D.5 Localization - 'locale.h': [ - 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', - 'LC_NUMERIC', 'LC_TIME', 'NULL', 'lconv', - 'setlocale', 'localeconv', - ], - # D.6 Mathematics - 'math.h': [ - 'HUGE_VAL', 'acos', 'asin' , 'atan2', 'cos', 'sin', 'tan', 'cosh', - 'sinh', 'tanh', 'exp', 'frexp', 'ldexp', 'log', 'loglO', 'modf', - 'pow', 'sqrt', 'ceil', 'fabs', 'floor', 'fmod', - ], - # D.7 Nonlocal jumps - 'setjmp.h': ['jmp_buf', 'setjmp', 'longjmp'], - # D.8 Signal handling - 'signal.h': [ - 'sig_atomic_t', 'SIG_DFL', 'SIG_ERR', 'SIG_IGN', 'SIGABRT', 'SIGFPE', - 'SIGILL', 'SIGINT', 'SIGSEGV', 'SIGTERM', 'signal', 'raise', - ], - # D.9 Variable arguments - 'stdarg.h': ['va_list', 'va_start', 'va_arg', 'va_end'], - # D.10 Input/output - 'stdio.h': [ - '_IOFBF', '_IOLBF', '_IONBF', 'BUFSIZ', 'EOF', 'FILE', 'FILENAME_MAX', - 'FOPEN_MAX', 'fpos_t', 'L_tmpnam', 'NULL', 'SEEK_CUR', 'SEEK_END', - 'SEEK_SET', 'size_t', 'stderr', 'stdin', 'stdout', 'TMP_MAX', - 'remove', 'rename', 'tmpfile', 'tmpnam', 'fclose', 'fflush', 'fopen', - 'freopen', 'setbuf', 'setvbuf', 'fprintf', 'fscanf', 'printf', - 'scanf', 'sprintf', 'sscanf', 'vfprintf', 'vprintf', 'vsprintf', - 'fgetc', 'fgets', 'fputc', 'fputs', 'getc', 'getchar', 'gets', 'putc', - 'putchar', 'puts', 'ungetc', 'fread', 'fwrite', 'fgetpos', 'fseek', - 'fsetpos', 'rewind', 'clearerr', 'feof', 'ferror', 'perror', - ], - # D.11 General utilities - 'stdlib.h': [ - 'EXIT_FAILURE', 'EXIT_SUCCESS', 'MB_CUR_MAX', 'NULL', 'RAND_MAX', - 'div_t', 'ldiv_t', 'wchar_t', 'atof', 'atoi', 'strtod', 'rand', - 'srand', 'calloc', 'free', 'malloc', 'realloc', 'abort', 'atexit', - 'exit', 'getenv', 'system', 'bsearch', 'qsort', 'abs', 'div', 'ldiv', - 'mblen', 'mbtowc', 'wctomb', 'mbstowcs', 'wcstombs', - ], - # D.12 String handling - 'string.h': [ - 'NULL', 'size_t', 'memcpy', 'memmove', 'strcpy', 'strncpy', 'strcat', - 'strncat', 'memcmp', 'strcmp', 'strcoll', 'strncmp', 'strxfrm', - 'memchr', 'strchr', 'strcspn', 'strpbrk', 'strrchr', 'strspn', - 'strstr', 'strtok', 'memset', 'strerror', 'strlen', - ], - # D.13 Date and time - 'time.h': [ - 'CLK_TCK', 'NULL', 'clock_t', 'time_t', 'size_t', 'tm', 'clock', - 'difftime', 'mktime', 'time', 'asctime', 'ctime', 'gmtime', - 'localtime', 'strftime', - ], - # Annex E: Implementation limits - 'limits.h': [ - 'CHAR_BIT', 'SCHAR_MIN', 'SCHAR_MAX', 'UCHAR_MAX', 'CHAR_MIN', - 'CHAR_MAX', 'MB_LEN_MAX', 'SHRT_MIN', 'SHRT_MAX', 'USHRT_MAX', - 'INT_MIN', 'INT_MAX', 'UINT_MAX', 'LONG_MIN', 'LONG_MAX', 'ULONG_MAX', - ], - 'float.h': [ - 'FLT_ROUNDS', 'FLT_RADIX', 'FLT_MANT_DIG', 'DBL_MANT_DIG', - 'LDBL_MANT_DIG', 'DECIMAL_DIG', 'FLT_DIG', 'DBL_DIG', 'LDBL_DIG', - 'DBL_MIN_EXP', 'LDBL_MIN_EXP', 'FLT_MIN_10_EXP', 'DBL_MIN_10_EXP', - 'LDBL_MIN_10_EXP', 'FLT_MAX_EXP', 'DBL_MAX_EXP', 'LDBL_MAX_EXP', - 'FLT_MAX_10_EXP', 'DBL_MAX_10_EXP', 'LDBL_MAX_10_EXP', 'FLT_MAX', - 'DBL_MAX', 'LDBL_MAX', 'FLT_MIN', 'DBL_MIN', 'LDBL_MIN', - 'FLT_EPSILON', 'DBL_EPSILON', 'LDBL_EPSILON' - ], -} - - -# Identifiers described in Section 7 "Library" of C99 Standard -# Based on ISO/IEC 9899 WF14/N1256 Annex B -- Library summary -C99_STDLIB_IDENTIFIERS = { - # B.1 Diagnostics - 'assert.h': C90_STDLIB_IDENTIFIERS['assert.h'], - # B.2 Complex - 'complex.h': [ - 'complex', 'imaginary', 'I', '_Complex_I', '_Imaginary_I', - 'CX_LIMITED_RANGE', - 'cacos', 'cacosf', 'cacosl', - 'casin', 'casinf', 'casinl', - 'catan', 'catanf', 'catanl', - 'ccos', 'ccosf', 'ccosl', - 'csin', 'csinf', 'csinl', - 'ctan', 'ctanf', 'ctanl', - 'cacosh', 'cacoshf', 'cacoshl', - 'casinh', 'casinhf', 'casinhl', - 'catanh', 'catanhf', 'catanhl', - 'ccosh', 'ccoshf', 'ccoshl', - 'csinh', 'csinhf', 'csinhl', - 'ctanh', 'ctanhf', 'ctanhl', - 'cexp', 'cexpf', 'cexpl', - 'clog', 'clogf', 'clogl', - 'cabs', 'cabsf', 'cabsl', - 'cpow', 'cpowf', 'cpowl', - 'csqrt', 'csqrtf', 'csqrtl', - 'carg', 'cargf', 'cargl', - 'cimag', 'cimagf', 'cimagl', - 'conj', 'conjf', 'conjl', - 'cproj', 'cprojf', 'cprojl', - 'creal', 'crealf', 'creall', - ], - # B.3 Character handling - 'ctype.h': C90_STDLIB_IDENTIFIERS['ctype.h'], - # B.4 Errors - 'errno.h': C90_STDLIB_IDENTIFIERS['errno.h'] + ['EILSEQ'], - # B.5 Floating-point environment - 'fenv.h': [ - 'fenv_t', 'FE_OVERFLOW', 'FE_TOWARDZERO', - 'fexcept_t', 'FE_UNDERFLOW', 'FE_UPWARD', - 'FE_DIVBYZERO', 'FE_ALL_EXCEPT', 'FE_DFL_ENV', - 'FE_INEXACT', 'FE_DOWNWARD', - 'FE_INVALID', 'FE_TONEAREST', - 'FENV_ACCESS', - 'feclearexcept', 'fegetexceptflag', 'fegetround', - 'fesetround', 'fegetenv', 'feholdexcept', - 'fesetenv', 'feupdateenv', - ], - # B.6 Characteristics of floating types - 'float.h': C90_STDLIB_IDENTIFIERS['float.h'] + ['FLT_EVAL_METHOD'], - # B.7 Format conversion of integer types - 'inttypes.h': [ - 'imaxdiv_t', 'imaxabs', 'imaxdiv', 'strtoimax', - 'strtoumax', 'wcstoimax', 'wcstoumax', - ], - # B.8 Alternative spellings - 'iso646.h': [ - 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', - 'or', 'or_eq', 'xor', 'xor_eq', - ], - # B.9 Size of integer types - 'limits.h': C90_STDLIB_IDENTIFIERS['limits.h'] + - ['LLONG_MIN', 'LLONG_MAX', 'ULLONG_MAX'], - # B.10 Localization - 'locale.h': C90_STDLIB_IDENTIFIERS['locale.h'], - # B.11 Mathematics - 'math.h': C90_STDLIB_IDENTIFIERS['math.h'] + [ - 'float_t', 'double_t', 'HUGE_VAL', 'HUGE_VALF', 'HUGE_VALL', - 'INFINITY', 'NAN', 'FP_INFINITE', 'FP_NAN', 'FP_NORMAL', - 'FP_SUBNORMAL', 'FP_ZERO', 'FP_FAST_FMA', 'FP_FAST_FMAF', - 'FP_FAST_FMAL', 'FP_ILOGB0', 'FP_ILOGBNAN', 'MATH_ERRNO', - 'MATH_ERREXCEPT', 'math_errhandling', 'FP_CONTRACT', 'fpclassify', - 'isfinite', 'isinf', 'isnan', 'isnormal', 'signbit', 'acosf', 'acosl', - 'asinf', 'asinl', 'atanf', 'atanl', 'atan2', 'atan2f', 'atan2l', - 'cosf', 'cosl', 'sinf', 'sinl', 'tanf', 'tanl', 'acosh', 'acoshf', - 'acoshl', 'asinh', 'asinhf', 'asinhl', 'atanh', 'atanhf', 'atanhl', - 'cosh', 'coshf', 'coshl', 'sinh', 'sinhf', 'sinhl', 'tanh', 'tanhf', - 'tanhl', 'expf', 'expl', 'exp2', 'exp2f', 'exp2l', 'expm1', 'expm1f', - 'expm1l', 'frexpf', 'frexpl', 'ilogb', 'ilogbf', 'ilogbl', 'float', - 'ldexpl', 'logf', 'logl', 'log10f', 'log10l', 'log1p', 'log1pf', - 'log1pl', 'log2', 'log2f', 'log2l', 'logb', 'logbf', 'logbl', 'modff', - 'modfl', 'scalbn', 'scalbnf', 'scalbnl', 'scalbln', 'scalblnf', - 'scalblnl', 'hypotl', 'powf', 'powl', 'sqrtf', 'sqrtl', 'erf', 'erff', - 'erfl', 'erfc', 'erfcf', 'erfcl', 'lgamma', 'lgammaf', 'lgammal', - 'tgamma', 'tgammaf', 'tgammal', 'ceilf', 'ceill', 'floorf', 'floorl', - 'nearbyint', 'nearbyintf', 'nearbyintl', 'rint', 'rintf', 'rintl', - 'lrint', 'lrintf', 'lrintl', 'llrint', 'llrintf', 'llrintl', 'round', - 'roundf', 'roundl', 'lround', 'lroundf', 'lroundl', 'llround', - 'llroundf', 'llroundl', 'trunc', 'truncf', 'truncl', 'fmodf', 'fmodl', - 'remainder', 'remainderf', 'remainderl', 'remquo', 'remquof', - 'remquol', 'copysign', 'copysignf', 'copysignl', 'nan', 'nanf', - 'nanl', 'nextafter', 'nextafterf', 'nextafterl', 'nexttoward', - 'nexttowardf', 'nexttowardl', 'fdim', 'fdimf', 'fdiml', 'fmax', - 'fmaxf', 'fmaxl', 'fmin', 'fminf', 'fminl', 'fmal', 'isgreater', - 'isgreaterequal', 'isless', 'islessequal', 'islessgreater', - 'isunordered', - ], - # B.12 Nonlocal jumps - 'setjmp.h': C90_STDLIB_IDENTIFIERS['setjmp.h'], - # B.13 Signal handling - 'signal.h': C90_STDLIB_IDENTIFIERS['signal.h'], - # B.14 Variable arguments - 'stdarg.h': C90_STDLIB_IDENTIFIERS['stdarg.h'] + ['va_copy'], - # B.15 Boolean type and values - 'stdbool.h': ['bool', 'true', 'false', '__bool_true_false_are_defined'], - # B.16 Common definitions - 'stddef.h': C90_STDLIB_IDENTIFIERS['stddef.h'], - # B.17 Integer types - 'stdint.h': [ - 'intptr_t', 'uintptr_t', 'intmax_t', 'uintmax_t', 'INTN_MIN', - 'INTN_MAX', 'UINTN_MAX', 'INT_LEASTN_MIN', 'INT_LEASTN_MAX', - 'UINT_LEASTN_MAX', 'INT_FASTN_MIN', 'INT_FASTN_MAX', 'UINT_FASTN_MAX', - 'INTPTR_MIN', 'INTPTR_MAX', 'UINTPTR_MAX', 'INTMAX_MIN', 'INTMAX_MAX', - 'UINTMAX_MAX', 'PTRDIFF_MIN', 'PTRDIFF_MAX', 'SIG_ATOMIC_MIN', - 'SIG_ATOMIC_MAX', 'SIZE_MAX', 'WCHAR_MIN', 'WCHAR_MAX', 'WINT_MIN', - 'WINT_MAX', 'INTN_C', 'UINTN_C', 'INTMAX_C', 'UINTMAX_C', - ] + STDINT_TYPES, - # B.18 Input/output - 'stdio.h': C90_STDLIB_IDENTIFIERS['stdio.h'] + [ - 'mode', 'restrict', 'snprintf', 'vfscanf', 'vscanf', - 'vsnprintf', 'vsscanf', - ], - # B.19 General utilities - 'stdlib.h': C90_STDLIB_IDENTIFIERS['stdlib.h'] + [ - '_Exit', 'labs', 'llabs', 'lldiv', 'lldiv_t', 'strtof', 'strtol', - 'strtold', 'strtoll', 'strtoul', 'strtoull' - ], - # B.20 String handling - 'string.h': C90_STDLIB_IDENTIFIERS['string.h'], - # B.21 Type-generic math - 'tgmath.h': [ - 'acos', 'asin', 'atan', 'acosh', 'asinh', 'atanh', 'cos', 'sin', 'tan', - 'cosh', 'sinh', 'tanh', 'exp', 'log', 'pow', 'sqrt', 'fabs', 'atan2', - 'cbrt', 'ceil', 'copysign', 'erf', 'erfc', 'exp2', 'expm1', 'fdim', - 'floor', 'fma', 'fmax', 'fmin', 'fmod', 'frexp', 'hypot', 'ilogb', - 'ldexp', 'lgamma', 'llrint', 'llround', 'log10', 'log1p', 'log2', - 'logb', 'lrint', 'lround', 'nearbyint', 'nextafter', 'nexttoward', - 'remainder', 'remquo', 'rint', 'round', 'scalbn', 'scalbln', 'tgamma', - 'trunc', 'carg', 'cimag', 'conj', 'cproj', 'creal', - ], - # B.22 Date and time - 'time.h': C90_STDLIB_IDENTIFIERS['time.h'] + ['CLOCKS_PER_SEC'], - # B.23 Extended multibyte/wide character utilities - 'wchar.h': [ - 'wchar_t', 'size_t', 'mbstate_t', 'wint_t', 'tm', 'NULL', 'WCHAR_MAX', - 'WCHAR_MIN', 'WEOF', 'fwprintf', 'fwscanf', 'swprintf', 'swscanf', - 'vfwprintf', 'vfwscanf', 'vswprintf', 'vswscanf', 'vwprintf', - 'vwscanf', 'wprintf', 'wscanf', 'fgetwc', 'fgetws', 'fputwc', 'fputws', - 'fwide', 'getwc', 'getwchar', 'putwc', 'putwchar', 'ungetwc', 'wcstod', - 'wcstof', 'double', 'int', 'long', 'long', 'long', 'wcscpy', 'wcsncpy', - 'wmemcpy', 'wmemmove', 'wcscat', 'wcsncat', 'wcscmp', 'wcscoll', - 'wcsncmp', 'wcsxfrm', 'wmemcmp', 'wcschr', 'wcscspn', 'wcspbrk', - 'wcsrchr', 'wcsspn', 'wcsstr', 'wcstok', 'wmemchr', 'wcslen', - 'wmemset', 'wcsftime', 'btowc', 'wctob', 'mbsinit', 'mbrlen', - 'mbrtowc', 'wcrtomb', 'mbsrtowcs', 'wcsrtombs', - ], -} - - -def isStdLibId(id_, standard='c99'): - id_lists = [] - if standard == 'c89': - id_lists = C90_STDLIB_IDENTIFIERS.values() - elif standard in ('c99', 'c11'): - id_lists = C99_STDLIB_IDENTIFIERS.values() - for l in id_lists: - if id_ in l: - return True - return False - - -# Reserved keywords defined in ISO/IEC9899:1990 -- ch 6.1.1 -C90_KEYWORDS = { - 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', - 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', - 'int', 'long', 'register', 'return', 'short', 'signed', - 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', - 'void', 'volatile', 'while' -} - - -# Reserved keywords defined in ISO/IEC 9899 WF14/N1256 -- ch. 6.4.1 -C99_ADDED_KEYWORDS = { - 'inline', 'restrict', '_Bool', '_Complex', '_Imaginary', - 'bool', 'complex', 'imaginary' -} - -C11_ADDED_KEYWORDS = { - '_Alignas', '_Alignof', '_Atomic', '_Generic', '_Noreturn', - '_Statis_assert', '_Thread_local' , - 'alignas', 'alignof', 'noreturn', 'static_assert' -} - -def isKeyword(keyword, standard='c99'): - kw_set = {} - if standard == 'c89': - kw_set = C90_KEYWORDS - elif standard == 'c99': - kw_set = copy.copy(C90_KEYWORDS) - kw_set.update(C99_ADDED_KEYWORDS) - else: - kw_set = copy.copy(C90_KEYWORDS) - kw_set.update(C99_ADDED_KEYWORDS) - kw_set.update(C11_ADDED_KEYWORDS) - return keyword in kw_set - - -def is_source_file(file): - return file.endswith('.c') - - -def is_header(file): - return file.endswith('.h') - - -def is_errno_setting_function(function_name): - return function_name and \ - function_name in ('ftell', 'fgetpos', 'fsetpos', 'fgetwc', 'fputwc' - 'strtoimax', 'strtoumax', 'strtol', 'strtoul', - 'strtoll', 'strtoull', 'strtof', 'strtod', 'strtold' - 'wcstoimax', 'wcstoumax', 'wcstol', 'wcstoul', - 'wcstoll', 'wcstoull', 'wcstof', 'wcstod', 'wcstold' - 'wcrtomb', 'wcsrtombs', 'mbrtowc') - - -def get_type_conversion_to_from(token): - def get_vartok(expr): - while expr: - if isCast(expr): - if expr.astOperand2 is None: - expr = expr.astOperand1 - else: - expr = expr.astOperand2 - elif expr.str in ('.', '::'): - expr = expr.astOperand2 - elif expr.str == '[': - expr = expr.astOperand1 - else: - break - return expr if (expr and expr.variable) else None - - if isCast(token): - vartok = get_vartok(token) - if vartok: - return (token.next, vartok.variable.typeStartToken) - - elif token.str == '=': - lhs = get_vartok(token.astOperand1) - rhs = get_vartok(token.astOperand2) - if lhs and rhs: - return (lhs.variable.typeStartToken, rhs.variable.typeStartToken) - - return None - - -def is_composite_expr(expr, composite_operator=False): - """MISRA C 2012, section 8.10.3""" - if expr is None: - return False - - if not composite_operator: - if (expr.str in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~')): - return is_composite_expr(expr.astOperand1,True) or is_composite_expr(expr.astOperand2, True) - if expr.str == '?' and simpleMatch(expr.astOperand2, ':'): - colon = expr.astOperand2 - return is_composite_expr(colon.astOperand1,True) or is_composite_expr(colon.astOperand2, True) - return False - - # non constant expression? - if expr.isNumber: - return False - if expr.astOperand1 or expr.astOperand2: - return is_composite_expr(expr.astOperand1,True) or is_composite_expr(expr.astOperand2, True) - return True - - -def getEssentialTypeCategory(expr): - if not expr: - return None - if expr.str == ',': - return getEssentialTypeCategory(expr.astOperand2) - if expr.str in ('<', '<=', '==', '!=', '>=', '>', '&&', '||', '!'): - return 'bool' - if expr.str in ('<<', '>>'): - # TODO this is incomplete - return getEssentialTypeCategory(expr.astOperand1) - if len(expr.str) == 1 and expr.str in '+-*/%&|^': - # TODO this is incomplete - e1 = getEssentialTypeCategory(expr.astOperand1) - e2 = getEssentialTypeCategory(expr.astOperand2) - # print('{0}: {1} {2}'.format(expr.str, e1, e2)) - if e1 and e2 and e1 == e2: - return e1 - if expr.valueType: - return expr.valueType.sign - if expr.valueType and expr.valueType.typeScope and expr.valueType.typeScope.className: - return "enum<" + expr.valueType.typeScope.className + ">" - # Unwrap membership, dereferences and array indexing - vartok = expr - while True: - if simpleMatch(vartok, '[') or (vartok and vartok.str == '*' and vartok.astOperand2 is None): - vartok = vartok.astOperand1 - elif simpleMatch(vartok, '.'): - vartok = vartok.astOperand2 - else: - break - if vartok and vartok.variable: - typeToken = vartok.variable.typeStartToken - while typeToken and typeToken.isName: - if typeToken.str == 'char' and not typeToken.isSigned and not typeToken.isUnsigned: - return 'char' - if typeToken.valueType: - if typeToken.valueType.type == 'bool': - return typeToken.valueType.type - if typeToken.valueType.type in ('float', 'double', 'long double'): - return "float" - if typeToken.valueType.sign: - return typeToken.valueType.sign - typeToken = typeToken.next - - # See Appendix D, section D.6, Character constants - if expr.str[0] == "'" and expr.str[-1] == "'": - if len(expr.str) == 3 or (len(expr.str) == 4 and expr.str[1] == '\\'): - return 'char' - return expr.valueType.sign - - if (expr.isCast and expr.str == "("): - castTok = expr.next - while castTok.isName or castTok.str == "*": - if castTok.str == 'char' and not castTok.isSigned and not castTok.isUnsigned: - return 'char' - castTok = castTok.next - - if expr.valueType: - return expr.valueType.sign - return None - - -def getEssentialCategorylist(operand1, operand2): - if not operand1 or not operand2: - return None, None - if (operand1.str in ('++', '--') or - operand2.str in ('++', '--')): - return None, None - if ((operand1.valueType and operand1.valueType.pointer) or - (operand2.valueType and operand2.valueType.pointer)): - return None, None - e1 = getEssentialTypeCategory(operand1) - e2 = getEssentialTypeCategory(operand2) - return e1, e2 - - -def get_essential_type_from_value(value, is_signed): - if value is None: - return None - for t in ('char', 'short', 'int', 'long', 'long long'): - bits = bitsOfEssentialType(t) - if bits >= 64: - continue - if is_signed: - range_min = -(1 << (bits - 1)) - range_max = (1 << (bits - 1)) - 1 - else: - range_min = 0 - range_max = (1 << bits) - 1 - sign = 'signed' if is_signed else 'unsigned' - if is_signed and value < 0 and value >= range_min: - return '%s %s' % (sign, t) - if value >= 0 and value <= range_max: - return '%s %s' % (sign, t) - return None - -def getEssentialType(expr): - if not expr: - return None - - # See Appendix D, section D.6, Character constants - if expr.str[0] == "'" and expr.str[-1] == "'": - if len(expr.str) == 3 or (len(expr.str) == 4 and expr.str[1] == '\\'): - return 'char' - return '%s %s' % (expr.valueType.sign, expr.valueType.type) - - if expr.variable or isCast(expr): - typeToken = expr.variable.typeStartToken if expr.variable else expr.next - while typeToken and typeToken.isName: - if typeToken.str == 'char' and not typeToken.isSigned and not typeToken.isUnsigned: - return 'char' - typeToken = typeToken.next - if expr.valueType: - if expr.valueType.type == 'bool': - return 'bool' - if expr.valueType.isFloat(): - return expr.valueType.type - if expr.valueType.isIntegral(): - if (expr.valueType.sign is None) and expr.valueType.type == 'char': - return 'char' - return '%s %s' % (expr.valueType.sign, expr.valueType.type) - - elif expr.isNumber: - # Appendix D, D.6 The essential type of literal constants - # Integer constants - if expr.valueType.type == 'bool': - return 'bool' - if expr.valueType.isFloat(): - return expr.valueType.type - if expr.valueType.isIntegral(): - if expr.valueType.type != 'int': - return '%s %s' % (expr.valueType.sign, expr.valueType.type) - return get_essential_type_from_value(expr.getKnownIntValue(), expr.valueType.sign == 'signed') - - elif expr.str in ('<', '<=', '>=', '>', '==', '!=', '&&', '||', '!'): - return 'bool' - - elif expr.astOperand1 and expr.astOperand2 and expr.str in ( - '+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":"): - if expr.astOperand1.valueType and expr.astOperand1.valueType.pointer > 0: - return None - if expr.astOperand2.valueType and expr.astOperand2.valueType.pointer > 0: - return None - e1 = getEssentialType(expr.astOperand1) - e2 = getEssentialType(expr.astOperand2) - if e1 is None or e2 is None: - return None - if is_constant_integer_expression(expr): - sign1 = e1.split(' ')[0] - sign2 = e2.split(' ')[0] - if sign1 == sign2 and sign1 in ('signed', 'unsigned'): - e = get_essential_type_from_value(expr.getKnownIntValue(), sign1 == 'signed') - if e: - return e - if bitsOfEssentialType(e2) >= bitsOfEssentialType(e1): - return e2 - else: - return e1 - - elif expr.str == "~": - e1 = getEssentialType(expr.astOperand1) - return e1 - - return None - - -def bitsOfEssentialType(ty): - if ty is None: - return 0 - last_type = ty.split(' ')[-1] - if last_type == 'Boolean': - return 1 - if last_type == 'char': - return typeBits['CHAR'] - if last_type == 'short': - return typeBits['SHORT'] - if last_type == 'int': - return typeBits['INT'] - if ty.endswith('long long'): - return typeBits['LONG_LONG'] - if last_type == 'long': - return typeBits['LONG'] - for sty in STDINT_TYPES: - if ty == sty: - return int(''.join(filter(str.isdigit, sty))) - return 0 - - -def get_function_pointer_type(tok): - ret = '' - par = 0 - while tok and (tok.isName or tok.str == '*'): - ret += ' ' + tok.str - tok = tok.next - if tok is None or tok.str != '(': - return None - tok = tok.link - if not simpleMatch(tok, ') ('): - return None - ret += '(' - tok = tok.next.next - while tok and (tok.str not in '()'): - if tok.varId is None: - ret += ' ' + tok.str - tok = tok.next - if (tok is None) or tok.str != ')': - return None - return ret[1:] + ')' - -def isCast(expr): - if not expr or expr.str != '(' or not expr.astOperand1 or expr.astOperand2: - return False - if simpleMatch(expr, '( )'): - return False - return True - -def is_constant_integer_expression(expr): - if expr is None: - return False - if expr.isInt: - return True - if not expr.isArithmeticalOp: - return False - if expr.astOperand1 and not is_constant_integer_expression(expr.astOperand1): - return False - if expr.astOperand2 and not is_constant_integer_expression(expr.astOperand2): - return False - return True - -def isFunctionCall(expr, std='c99'): - if not expr: - return False - if expr.str != '(' or not expr.astOperand1: - return False - if expr.astOperand1 != expr.previous: - return False - if isKeyword(expr.astOperand1.str, std): - return False - return True - - -def hasExternalLinkage(var): - return var.isGlobal and not var.isStatic - - -def countSideEffects(expr): - if not expr or expr.str in (',', ';'): - return 0 - ret = 0 - if expr.str in ('++', '--', '='): - ret = 1 - return ret + countSideEffects(expr.astOperand1) + countSideEffects(expr.astOperand2) - - -def getForLoopExpressions(forToken): - if not forToken or forToken.str != 'for': - return None - lpar = forToken.next - if not lpar or lpar.str != '(': - return None - if not lpar.astOperand2 or lpar.astOperand2.str != ';': - return None - if not lpar.astOperand2.astOperand2 or lpar.astOperand2.astOperand2.str != ';': - return None - return [lpar.astOperand2.astOperand1, - lpar.astOperand2.astOperand2.astOperand1, - lpar.astOperand2.astOperand2.astOperand2] - - -def get_function_scope(cfg, func): - if func: - for scope in cfg.scopes: - if scope.function == func: - return scope - return None - - -def is_variable_changed(start_token, end_token, var): - """Check if variable is updated between body_start and body_end""" - tok = start_token - while tok != end_token: - if tok.isAssignmentOp: - vartok = tok.astOperand1 - while vartok.astOperand1: - vartok = vartok.astOperand1 - if vartok and vartok.variable == var: - return True - tok = tok.next - return False - -def getForLoopCounterVariables(forToken, cfg): - """ Return a set of Variable objects defined in ``for`` statement and - satisfy requirements to loop counter term from section 8.14 of MISRA - document. - """ - if not forToken or forToken.str != 'for': - return None - tn = forToken.next - if not tn or tn.str != '(': - return None - vars_defined = set() - vars_initialized = set() - vars_exit = set() - vars_modified = set() - cur_clause = 1 - te = tn.link - while tn and tn != te: - if tn.variable: - if cur_clause == 1 and tn.variable.nameToken == tn: - vars_defined.add(tn.variable) - elif cur_clause == 2: - vars_exit.add(tn.variable) - elif cur_clause == 3: - if tn.next and countSideEffectsRecursive(tn.next) > 0: - vars_modified.add(tn.variable) - elif tn.previous and tn.previous.str in ('++', '--'): - tn_ast = tn.astParent - if tn_ast and tn_ast == tn.previous: - vars_modified.add(tn.variable) - elif tn_ast and tn_ast.str == '.' and tn_ast.astOperand2 and tn_ast.astOperand2.variable: - vars_modified.add(tn_ast.astOperand2.variable) - if cur_clause == 1 and tn.isAssignmentOp: - var_token = tn.astOperand1 - while var_token and var_token.str == '.': - var_token = var_token.astOperand2 - if var_token and var_token.variable: - vars_initialized.add(var_token.variable) - if cur_clause == 1 and tn.isName and tn.next.str == '(': - function_args_in_init = getArguments(tn.next) - function_scope = get_function_scope(cfg, tn.function) - for arg_nr in range(len(function_args_in_init)): - init_arg = function_args_in_init[arg_nr] - if init_arg is None or not init_arg.isUnaryOp('&'): - continue - var_token = init_arg.astOperand1 - while var_token and var_token.str == '.': - var_token = var_token.astOperand2 - if var_token is None or var_token.variable is None: - continue - changed = False - if function_scope is None: - changed = True - elif tn.function is None: - changed = True - else: - function_body_start = function_scope.bodyStart - function_body_end = function_scope.bodyEnd - args = tn.function.argument[arg_nr + 1] - if function_scope is None or is_variable_changed(function_body_start, function_body_end, args): - changed = True - if changed: - vars_initialized.add(var_token.variable) - - if tn.str == ';': - cur_clause += 1 - tn = tn.next - return vars_defined | vars_initialized, vars_exit & vars_modified - - -def findCounterTokens(cond): - if not cond: - return [] - if cond.str in ['&&', '||']: - c = findCounterTokens(cond.astOperand1) - c.extend(findCounterTokens(cond.astOperand2)) - return c - ret = [] - if ((cond.isArithmeticalOp and cond.astOperand1 and cond.astOperand2) or - (cond.isComparisonOp and cond.astOperand1 and cond.astOperand2)): - if cond.astOperand1.isName: - ret.append(cond.astOperand1) - if cond.astOperand2.isName: - ret.append(cond.astOperand2) - if cond.astOperand1.isOp: - ret.extend(findCounterTokens(cond.astOperand1)) - if cond.astOperand2.isOp: - ret.extend(findCounterTokens(cond.astOperand2)) - return ret - - -def isFloatCounterInWhileLoop(whileToken): - if not simpleMatch(whileToken, 'while ('): - return False - lpar = whileToken.next - rpar = lpar.link - counterTokens = findCounterTokens(lpar.astOperand2) - whileBodyStart = None - if simpleMatch(rpar, ') {'): - whileBodyStart = rpar.next - elif simpleMatch(whileToken.previous, '} while') and simpleMatch(whileToken.previous.link.previous, 'do {'): - whileBodyStart = whileToken.previous.link - else: - return False - token = whileBodyStart - while token != whileBodyStart.link: - token = token.next - for counterToken in counterTokens: - if not counterToken.valueType or not counterToken.valueType.isFloat(): - continue - if token.isAssignmentOp and token.astOperand1.str == counterToken.str: - return True - if token.str == counterToken.str and token.astParent and token.astParent.str in ('++', '--'): - return True - return False - - -def countSideEffectsRecursive(expr): - if not expr or expr.str == ';': - return 0 - if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '[': - prev = expr.astOperand1.previous - if prev and (prev.str == '{' or prev.str == '{'): - return countSideEffectsRecursive(expr.astOperand2) - if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '.': - e = expr.astOperand1 - while e and e.str == '.' and e.astOperand2: - e = e.astOperand1 - if e and e.str == '.': - return 0 - if expr.isAssignmentOp or expr.str in {'++', '--'}: - return 1 - # Todo: Check function calls - return countSideEffectsRecursive(expr.astOperand1) + countSideEffectsRecursive(expr.astOperand2) - - -def isBoolExpression(expr): - if not expr: - return False - if expr.valueType and (expr.valueType.type == 'bool' or expr.valueType.bits == 1): - return True - return expr.str in ['!', '==', '!=', '<', '<=', '>', '>=', '&&', '||', '0', '1', 'true', 'false'] - - -def isEnumConstant(expr): - if not expr or not expr.values: - return False - values = expr.values - return len(values) == 1 and values[0].valueKind == 'known' - - -def isConstantExpression(expr): - if expr.isNumber: - return True - if expr.isName and not isEnumConstant(expr): - return False - if simpleMatch(expr.previous, 'sizeof ('): - return True - if expr.astOperand1 and not isConstantExpression(expr.astOperand1): - return False - if expr.astOperand2 and not isConstantExpression(expr.astOperand2): - return False - return True - -def isUnknownConstantExpression(expr): - if expr.isName and not isEnumConstant(expr) and expr.variable is None: - return True - if expr.astOperand1 and isUnknownConstantExpression(expr.astOperand1): - return True - if expr.astOperand2 and isUnknownConstantExpression(expr.astOperand2): - return True - return False - -def isUnsignedInt(expr): - return expr and expr.valueType and expr.valueType.type in ('short', 'int') and expr.valueType.sign == 'unsigned' - - -def getPrecedence(expr): - if not expr: - return 16 - if not expr.astOperand1 or not expr.astOperand2: - return 16 - if expr.str in ('*', '/', '%'): - return 12 - if expr.str in ('+', '-'): - return 11 - if expr.str in ('<<', '>>'): - return 10 - if expr.str in ('<', '>', '<=', '>='): - return 9 - if expr.str in ('==', '!='): - return 8 - if expr.str == '&': - return 7 - if expr.str == '^': - return 6 - if expr.str == '|': - return 5 - if expr.str == '&&': - return 4 - if expr.str == '||': - return 3 - if expr.str in ('?', ':'): - return 2 - if expr.isAssignmentOp: - return 1 - if expr.str == ',': - return 0 - return -1 - - -def findRawLink(token): - tok1 = None - tok2 = None - forward = False - - if token.str in '{([': - tok1 = token.str - tok2 = '})]'['{(['.find(token.str)] - forward = True - elif token.str in '})]': - tok1 = token.str - tok2 = '{(['['})]'.find(token.str)] - forward = False - else: - return None - - # try to find link - indent = 0 - while token: - if token.str == tok1: - indent = indent + 1 - elif token.str == tok2: - if indent <= 1: - return token - indent = indent - 1 - if forward is True: - token = token.next - else: - token = token.previous - - # raw link not found - return None - - -def numberOfParentheses(tok1, tok2): - while tok1 and tok1 != tok2: - if tok1.str == '(' or tok1.str == ')': - return False - tok1 = tok1.next - return tok1 == tok2 - - -def findGotoLabel(gotoToken): - label = gotoToken.next.str - tok = gotoToken.next.next - while tok: - if tok.str == '}' and tok.scope.type == 'Function': - break - if tok.str == label and tok.next.str == ':': - return tok - tok = tok.next - return None - - -def findInclude(directives, header): - for directive in directives: - if directive.str == '#include ' + header: - return directive - return None - - -# Get function arguments -def getArgumentsRecursive(tok, arguments): - if tok is None: - return - if tok.str == ',': - getArgumentsRecursive(tok.astOperand1, arguments) - getArgumentsRecursive(tok.astOperand2, arguments) - else: - arguments.append(tok) - - -def getArguments(ftok): - arguments = [] - getArgumentsRecursive(ftok.astOperand2, arguments) - return arguments - - -def isalnum(c): - return c in string.digits or c in string.ascii_letters - - -def isHexEscapeSequence(symbols): - """Checks that given symbols are valid hex escape sequence. - - hexadecimal-escape-sequence: - \\x hexadecimal-digit - hexadecimal-escape-sequence hexadecimal-digit - - Reference: n1570 6.4.4.4""" - if len(symbols) < 3 or symbols[:2] != '\\x': - return False - return all([s in string.hexdigits for s in symbols[2:]]) - - -def isOctalEscapeSequence(symbols): - r"""Checks that given symbols are valid octal escape sequence: - - octal-escape-sequence: - \ octal-digit - \ octal-digit octal-digit - \ octal-digit octal-digit octal-digit - - Reference: n1570 6.4.4.4""" - if len(symbols) not in range(2, 5) or symbols[0] != '\\': - return False - return all([s in string.octdigits for s in symbols[1:]]) - - -def isSimpleEscapeSequence(symbols): - """Checks that given symbols are simple escape sequence. - Reference: n1570 6.4.4.4""" - if len(symbols) != 2 or symbols[0] != '\\': - return False - return symbols[1] in ("'", '"', '?', '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v') - - -def isTernaryOperator(token): - if not token: - return False - if not token.astOperand2: - return False - return token.str == '?' and token.astOperand2.str == ':' - - -def getTernaryOperandsRecursive(token): - """Returns list of ternary operands including nested ones.""" - if not isTernaryOperator(token): - return [] - result = [] - result += getTernaryOperandsRecursive(token.astOperand2.astOperand1) - if token.astOperand2.astOperand1 and not isTernaryOperator(token.astOperand2.astOperand1): - result += [token.astOperand2.astOperand1] - result += getTernaryOperandsRecursive(token.astOperand2.astOperand2) - if token.astOperand2.astOperand2 and not isTernaryOperator(token.astOperand2.astOperand2): - result += [token.astOperand2.astOperand2] - return result - - -def hasNumericEscapeSequence(symbols): - """Check that given string contains octal or hexadecimal escape sequences.""" - if '\\' not in symbols: - return False - for c, cn in grouped(symbols, 2): - if c == '\\' and cn in ('x' + string.octdigits): - return True - return False - - -def isNoReturnScope(tok): - if tok is None or tok.str != '}': - return False - if tok.previous is None or tok.previous.str != ';': - return False - if simpleMatch(tok.previous.previous, 'break ;'): - return True - prev = tok.previous.previous - while prev and prev.str not in ';{}': - if prev.str in '])': - prev = prev.link - prev = prev.previous - if prev and prev.next.str in ['throw', 'return']: - return True - return False - - -# Return the token which the value is assigned to -def getAssignedVariableToken(vartok): - if not vartok: - return None - parent = vartok.astParent - while parent and parent.isArithmeticalOp: - parent = parent.astParent - if parent and parent.isAssignmentOp: - return parent.astOperand1 - return None - -# If the value is used as a return value, return the function definition -def getFunctionUsingReturnValue(valueToken): - if not valueToken: - return None - if not valueToken.astParent: - return None - operator = valueToken.astParent - if operator.str == 'return': - return operator.scope.function - if operator.isArithmeticalOp: - return getFunctionUsingReturnValue(operator) - return None - -# Return true if the token follows a specific sequence of token str values -def tokenFollowsSequence(token, sequence): - if not token: - return False - for i in reversed(sequence): - prev = token.previous - if not prev: - return False - if prev.str != i: - return False - token = prev - return True - -class Define: - def __init__(self, directive): - self.name = '' - self.args = [] - self.expansionList = '' - - res = re.match(r'#define ([A-Za-z0-9_]+)\(([A-Za-z0-9_, ]+)\)[ ]+(.*)', directive.str) - if res: - self.name = res.group(1) - self.args = res.group(2).strip().split(',') - self.expansionList = res.group(3) - else: - res = re.match(r'#define ([A-Za-z0-9_]+)[ ]+(.*)', directive.str) - if res: - self.name = res.group(1) - self.expansionList = res.group(2) - - def __repr__(self): - attrs = ["name", "args", "expansionList"] - return "{}({})".format( - "Define", - ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) - ) - - -def getAddonRules(): - """Returns dict of MISRA rules handled by this addon.""" - addon_rules = [] - compiled = re.compile(r'.*def[ ]+misra_([0-9]+)_([0-9]+)[(].*') - with open(__file__) as f: - for line in f: - res = compiled.match(line) - if res is None: - continue - addon_rules.append(res.group(1) + '.' + res.group(2)) - return addon_rules - - -def getCppcheckRules(): - """Returns list of rules handled by cppcheck.""" - return ['1.3', # - '2.1', # alwaysFalse, duplicateBreak - '2.2', # alwaysTrue, redundantCondition, redundantAssignment, redundantAssignInSwitch, unreadVariable - '2.6', # unusedLabel - '5.3', # shadowVariable - '8.3', # funcArgNamesDifferent - '8.13', # constPointer - '9.1', # uninitvar - '14.3', # alwaysTrue, alwaysFalse, compareValueOutOfTypeRangeError - '13.2', # unknownEvaluationOrder - '13.6', # sizeofCalculation - '17.4', # missingReturn - '17.5', # argumentSize - '18.1', # pointerOutOfBounds - '18.2', # comparePointers - '18.3', # comparePointers - '18.6', # danglingLifetime - '19.1', # overlappingWriteUnion, overlappingWriteFunction - '20.6', # preprocessorErrorDirective - '21.13', # invalidFunctionArg - '21.17', # bufferAccessOutOfBounds - '21.18', # bufferAccessOutOfBounds - '22.1', # memleak, resourceLeak, memleakOnRealloc, leakReturnValNotUsed, leakNoVarFunctionCall - '22.2', # autovarInvalidDeallocation - '22.3', # incompatibleFileOpen - '22.4', # writeReadOnlyFile - '22.6' # useClosedFile - ] - - -def generateTable(): - # print table - numberOfRules = {} - numberOfRules[1] = 3 - numberOfRules[2] = 7 - numberOfRules[3] = 2 - numberOfRules[4] = 2 - numberOfRules[5] = 9 - numberOfRules[6] = 2 - numberOfRules[7] = 4 - numberOfRules[8] = 14 - numberOfRules[9] = 5 - numberOfRules[10] = 8 - numberOfRules[11] = 9 - numberOfRules[12] = 4 - numberOfRules[13] = 6 - numberOfRules[14] = 4 - numberOfRules[15] = 7 - numberOfRules[16] = 7 - numberOfRules[17] = 8 - numberOfRules[18] = 8 - numberOfRules[19] = 2 - numberOfRules[20] = 14 - numberOfRules[21] = 21 - numberOfRules[22] = 10 - - # Rules that can be checked with compilers: - # compiler = ['1.1', '1.2'] - - addon = getAddonRules() - cppcheck = getCppcheckRules() - for i1 in range(1, 23): - for i2 in range(1, numberOfRules[i1] + 1): - num = str(i1) + '.' + str(i2) - s = '' - if num in addon: - s = 'X (Addon)' - elif num in cppcheck: - s = 'X (Cppcheck)' - num = num + ' ' - print(num[:8] + s) - - -def remove_file_prefix(file_path, prefix): - """ - Remove a file path prefix from a give path. leftover - directory separators at the beginning of a file - after the removal are also stripped. - - Example: - '/remove/this/path/file.c' - with a prefix of: - '/remove/this/path' - becomes: - file.c - """ - result = None - if file_path.startswith(prefix): - result = file_path[len(prefix):] - # Remove any leftover directory separators at the - # beginning - result = result.lstrip('\\/') - else: - result = file_path - return result - - -class Rule(object): - """Class to keep rule text and metadata""" - - MISRA_SEVERITY_LEVELS = ['Required', 'Mandatory', 'Advisory'] - - def __init__(self, num1, num2): - self.num1 = num1 - self.num2 = num2 - self.text = '' - self.misra_severity = '' - - @property - def num(self): - return self.num1 * 100 + self.num2 - - @property - def misra_severity(self): - return self._misra_severity - - @misra_severity.setter - def misra_severity(self, val): - if val in self.MISRA_SEVERITY_LEVELS: - self._misra_severity = val - else: - self._misra_severity = '' - - @property - def cppcheck_severity(self): - return 'style' - - def __repr__(self): - return "%d.%d (%s)" % (self.num1, self.num2, self.misra_severity) - - -class MisraSettings(object): - """Hold settings for misra.py script.""" - - __slots__ = ["verify", "quiet", "show_summary"] - - def __init__(self, args): - """ - :param args: Arguments given by argparse. - """ - self.verify = False - self.quiet = False - self.show_summary = True - - if args.verify: - self.verify = True - if args.cli: - self.quiet = True - self.show_summary = False - if args.quiet: - self.quiet = True - if args.no_summary: - self.show_summary = False - - def __repr__(self): - attrs = ["verify", "quiet", "show_summary", "verify"] - return "{}({})".format( - "MisraSettings", - ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) - ) - - -class MisraChecker: - - def __init__(self, settings, stdversion="c89"): - """ - :param settings: misra.py script settings. - """ - - self.settings = settings - - # Test validation rules lists - self.verify_expected = list() - self.verify_actual = list() - - # List of formatted violation messages - self.violations = dict() - - # if --rule-texts is specified this dictionary - # is loaded with descriptions of each rule - # by rule number (in hundreds). - # ie rule 1.2 becomes 102 - self.ruleTexts = dict() - self.ruleText_filename = None - - # Dictionary of dictionaries for rules to suppress - # Dict1 is keyed by rule number in the hundreds format of - # Major * 100 + minor. ie Rule 5.2 = (5*100) + 2 - # Dict 2 is keyed by filename. An entry of None means suppress globally. - # Each file name entry contains a list of tuples of (lineNumber, symbolName) - # or an item of None which indicates suppress rule for the entire file. - # The line and symbol name tuple may have None as either of its elements but - # should not be None for both. - self.suppressedRules = dict() - - # Prefix to ignore when matching suppression files. - self.filePrefix = None - - # Number of all violations suppressed per rule - self.suppressionStats = dict() - - self.stdversion = stdversion - - self.severity = None - - self.existing_violations = set() - - self._ctu_summary_typedefs = False - self._ctu_summary_tagnames = False - self._ctu_summary_identifiers = False - self._ctu_summary_usage = False - - self.path_premium_addon = None - - def __repr__(self): - attrs = ["settings", "verify_expected", "verify_actual", "violations", - "ruleTexts", "suppressedRules", "filePrefix", - "suppressionStats", "stdversion", "severity"] - return "{}({})".format( - "MisraChecker", - ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) - ) - - def get_num_significant_naming_chars(self, cfg): - if cfg.standards and cfg.standards.c == "c89": - return 31 - else: - return 63 - - def _save_ctu_summary_typedefs(self, dumpfile, typedef_info): - if self._ctu_summary_typedefs: - return - - self._ctu_summary_typedefs = True - - summary = [] - for ti in typedef_info: - summary.append({ 'name': ti.name, 'file': ti.file, 'line': ti.linenr, 'column': ti.column, 'used': ti.used }) - if len(summary) > 0: - cppcheckdata.reportSummary(dumpfile, 'MisraTypedefInfo', summary) - - def _save_ctu_summary_tagnames(self, dumpfile, cfg): - if self._ctu_summary_tagnames: - return - - self._ctu_summary_tagnames = True - - summary = [] - # structs/enums - for scope in cfg.scopes: - if scope.className is None: - continue - if scope.className.startswith('Anonymous'): - continue - if scope.type not in ('Struct', 'Enum'): - continue - used = False - tok = scope.bodyEnd - while tok: - if tok.str == scope.className: - used = True - break - tok = tok.next - summary.append({'name': scope.className, 'used':used, 'file': scope.bodyStart.file, 'line': scope.bodyStart.linenr, 'column': scope.bodyStart.column}) - if len(summary) > 0: - cppcheckdata.reportSummary(dumpfile, 'MisraTagName', summary) - - def _save_ctu_summary_identifiers(self, dumpfile, cfg): - if self._ctu_summary_identifiers: - return - self._ctu_summary_identifiers = True - - external_identifiers = [] - internal_identifiers = [] - local_identifiers = [] - - def identifier(nameToken): - return {'name':nameToken.str, 'file':nameToken.file, 'line':nameToken.linenr, 'column':nameToken.column} - - names = [] - - for var in cfg.variables: - if var.nameToken is None: - continue - if var.access != 'Global': - if var.nameToken.str in names: - continue - names.append(var.nameToken.str) - local_identifiers.append(identifier(var.nameToken)) - elif var.isStatic: - names.append(var.nameToken.str) - i = identifier(var.nameToken) - i['inlinefunc'] = False - internal_identifiers.append(i) - else: - names.append(var.nameToken.str) - i = identifier(var.nameToken) - i['decl'] = var.isExtern - external_identifiers.append(i) - - for func in cfg.functions: - if func.tokenDef is None: - continue - if func.isStatic: - i = identifier(func.tokenDef) - i['inlinefunc'] = func.isInlineKeyword - internal_identifiers.append(i) - else: - if func.token is None: - i = identifier(func.tokenDef) - else: - i = identifier(func.token) - i['decl'] = func.token is None - external_identifiers.append(i) - - cppcheckdata.reportSummary(dumpfile, 'MisraExternalIdentifiers', external_identifiers) - cppcheckdata.reportSummary(dumpfile, 'MisraInternalIdentifiers', internal_identifiers) - cppcheckdata.reportSummary(dumpfile, 'MisraLocalIdentifiers', local_identifiers) - - def _save_ctu_summary_usage(self, dumpfile, cfg): - if self._ctu_summary_usage: - return - self._ctu_summary_usage = True - - names = [] - for token in cfg.tokenlist: - if not token.isName: - continue - if token.function and token != token.function.tokenDef: - if (not token.function.isStatic) and (token.str not in names): - names.append({'name': token.str, 'file': token.file}) - elif token.variable: - if token == token.variable.nameToken: - continue - if token.variable.access == 'Global' and (not token.variable.isStatic) and (token.str not in names): - names.append({'name': token.str, 'file': token.file}) - - if len(names) > 0: - cppcheckdata.reportSummary(dumpfile, 'MisraUsage', names) - - - def misra_1_2(self, cfg): - # gcc language extensions: https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html - for token in cfg.tokenlist: - if simpleMatch(token, '? :'): - self.reportError(token, 1, 2) - elif simpleMatch(token, '( {') and simpleMatch(token.next.link.previous, '; } )'): - self.reportError(token, 1, 2) - - - def misra_1_4(self, cfg): - for token in cfg.tokenlist: - if token.str in ('_Atomic', '_Noreturn', '_Generic', '_Thread_local', '_Alignas', '_Alignof'): - self.reportError(token, 1, 4) - if token.str.endswith('_s') and isFunctionCall(token.next): - # See C specification C11 - Annex K, page 578 - if token.str in ('tmpfile_s', 'tmpnam_s', 'fopen_s', 'freopen_s', 'fprintf_s', 'fscanf_s', 'printf_s', 'scanf_s', - 'snprintf_s', 'sprintf_s', 'sscanf_s', 'vfprintf_s', 'vfscanf_s', 'vprintf_s', 'vscanf_s', - 'vsnprintf_s', 'vsprintf_s', 'vsscanf_s', 'gets_s', 'set_constraint_handler_s', 'abort_handler_s', - 'ignore_handler_s', 'getenv_s', 'bsearch_s', 'qsort_s', 'wctomb_s', 'mbstowcs_s', 'wcstombs_s', - 'memcpy_s', 'memmove_s', 'strcpy_s', 'strncpy_s', 'strcat_s', 'strncat_s', 'strtok_s', 'memset_s', - 'strerror_s', 'strerrorlen_s', 'strnlen_s', 'asctime_s', 'ctime_s', 'gmtime_s', 'localtime_s', - 'fwprintf_s', 'fwscanf_s', 'snwprintf_s', 'swprintf_s', 'swscanf_s', 'vfwprintf_s', 'vfwscanf_s', - 'vsnwprintf_s', 'vswprintf_s', 'vswscanf_s', 'vwprintf_s', 'vwscanf_s', 'wprintf_s', 'wscanf_s', - 'wcscpy_s', 'wcsncpy_s', 'wmemcpy_s', 'wmemmove_s', 'wcscat_s', 'wcsncat_s', 'wcstok_s', 'wcsnlen_s', - 'wcrtomb_s', 'mbsrtowcs_s', 'wcsrtombs_s'): - self.reportError(token, 1, 4) - - def misra_2_2(self, cfg): - for token in cfg.tokenlist: - if token.isExpandedMacro: - continue - if (token.str in '+-') and token.astOperand2: - if simpleMatch(token.astOperand1, '0'): - self.reportError(token.astOperand1, 2, 2) - elif simpleMatch(token.astOperand2, '0'): - self.reportError(token.astOperand2, 2, 2) - if token.str == '*' and token.astOperand2: - if simpleMatch(token.astOperand2, '0'): - self.reportError(token.astOperand1, 2, 2) - elif simpleMatch(token.astOperand1, '0'): - self.reportError(token.astOperand2, 2, 2) - elif simpleMatch(token.astOperand1, '1'): - self.reportError(token.astOperand1, 2, 2) - elif simpleMatch(token.astOperand2, '1'): - self.reportError(token.astOperand2, 2, 2) - - def misra_2_3(self, dumpfile, typedefInfo): - self._save_ctu_summary_typedefs(dumpfile, typedefInfo) - - def misra_2_4(self, dumpfile, cfg): - self._save_ctu_summary_tagnames(dumpfile, cfg) - - def misra_2_5(self, dumpfile, cfg): - used_macros = list() - for m in cfg.macro_usage: - used_macros.append(m.name) - summary = [] - for directive in cfg.directives: - res = re.match(r'#define[ \t]+([a-zA-Z_][a-zA-Z_0-9]*).*', directive.str) - if res: - macro_name = res.group(1) - summary.append({'name': macro_name, 'used': (macro_name in used_macros), 'file': directive.file, 'line': directive.linenr, 'column': directive.column}) - if len(summary) > 0: - cppcheckdata.reportSummary(dumpfile, 'MisraMacro', summary) - - def misra_2_7(self, data): - for func in data.functions: - # Skip function with no parameter - if len(func.argument) == 0: - continue - # Setup list of function parameters - func_param_list = list() - for arg in func.argument: - func_arg = func.argument[arg] - if func_arg.typeStartToken and func_arg.typeStartToken.str == '...': - continue - func_param_list.append(func_arg) - # Search for scope of current function - for scope in data.scopes: - if (scope.type == "Function") and (scope.function == func): - # Search function body: remove referenced function parameter from list - token = scope.bodyStart - while token.next is not None and token != scope.bodyEnd and len(func_param_list) > 0: - if token.variable is not None and token.variable in func_param_list: - func_param_list.remove(token.variable) - token = token.next - # Emit a warning for each unused variable, but no more that one warning per line - reported_linenrs = set() - for func_param in func_param_list: - if func_param.nameToken: - linenr = func_param.nameToken - if linenr not in reported_linenrs: - self.reportError(func_param.nameToken, 2, 7) - reported_linenrs.add(linenr) - else: - linenr = func.tokenDef.linenr - if linenr not in reported_linenrs: - self.reportError(func.tokenDef, 2, 7) - reported_linenrs.add(linenr) - - def misra_3_1(self, rawTokens): - for token in rawTokens: - starts_with_double_slash = token.str.startswith('//') - starts_with_block_comment = token.str.startswith("/*") - s = token.str.lstrip('/') - if (starts_with_double_slash or starts_with_block_comment) and "/*" in s: - # Block comment inside of regular comment, violation - self.reportError(token, 3, 1) - elif starts_with_block_comment and "//" in s: - # "//" in block comment, check if it's a uri - while "//" in s: - possible_uri, s = s.split("//", 1) - if not re.search(r"\w+:$", possible_uri): - # Violation if no uri was found - self.reportError(token, 3, 1) - break - - def misra_3_2(self, rawTokens): - for token in rawTokens: - if token.str.startswith('//'): - # Check for comment ends with trigraph which might be replaced - # by a backslash. - if token.str.endswith('??/'): - self.reportError(token, 3, 2) - # Check for comment which has been merged with subsequent line - # because it ends with backslash. - # The last backslash is no more part of the comment token thus - # check if next token exists and compare line numbers. - elif (token.next is not None) and (token.linenr == token.next.linenr): - self.reportError(token, 3, 2) - - def misra_4_1(self, rawTokens): - for token in rawTokens: - if (token.str[0] != '"') and (token.str[0] != '\''): - continue - if len(token.str) < 3: - continue - - delimiter = token.str[0] - symbols = token.str[1:-1] - - # No closing delimiter. This will not compile. - if token.str[-1] != delimiter: - continue - - if len(symbols) < 2: - continue - - if not hasNumericEscapeSequence(symbols): - continue - - # String literals that contains one or more escape sequences. All of them should be - # terminated. - for sequence in ['\\' + t for t in symbols.split('\\')][1:]: - if (isHexEscapeSequence(sequence) or isOctalEscapeSequence(sequence) or - isSimpleEscapeSequence(sequence)): - continue - else: - self.reportError(token, 4, 1) - - def misra_4_2(self, rawTokens): - for token in rawTokens: - if (token.str[0] != '"') or (token.str[-1] != '"'): - continue - # Check for trigraph sequence as defined by ISO/IEC 9899:1999 - for sequence in ['??=', '??(', '??/', '??)', '??\'', '??<', '??!', '??>', '??-']: - if sequence in token.str[1:-1]: - # First trigraph sequence match, report error and leave loop. - self.reportError(token, 4, 2) - break - - def misra_5_1(self, data): - long_vars = {} - num_sign_chars = self.get_num_significant_naming_chars(data) - for var in data.variables: - if var.nameToken is None: - continue - if len(var.nameToken.str) <= num_sign_chars: - continue - if not hasExternalLinkage(var): - continue - long_vars.setdefault(var.nameToken.str[:num_sign_chars], []).append(var.nameToken) - for name_prefix in long_vars: - tokens = long_vars[name_prefix] - if len(tokens) < 2: - continue - for tok in sorted(tokens, key=lambda t: (t.linenr, t.column))[1:]: - self.reportError(tok, 5, 1) - - def misra_5_2(self, data): - scopeVars = {} - num_sign_chars = self.get_num_significant_naming_chars(data) - for var in data.variables: - if var.nameToken is None: - continue - if len(var.nameToken.str) <= num_sign_chars: - continue - if var.nameToken.scope not in scopeVars: - scopeVars.setdefault(var.nameToken.scope, {})["varlist"] = [] - scopeVars.setdefault(var.nameToken.scope, {})["scopelist"] = [] - scopeVars[var.nameToken.scope]["varlist"].append(var) - for scope in data.scopes: - if scope.nestedIn and scope.className: - if scope.nestedIn not in scopeVars: - scopeVars.setdefault(scope.nestedIn, {})["varlist"] = [] - scopeVars.setdefault(scope.nestedIn, {})["scopelist"] = [] - scopeVars[scope.nestedIn]["scopelist"].append(scope) - for scope in scopeVars: - if len(scopeVars[scope]["varlist"]) <= 1: - continue - for i, variable1 in enumerate(scopeVars[scope]["varlist"]): - for variable2 in scopeVars[scope]["varlist"][i + 1:]: - if variable1.isArgument and variable2.isArgument: - continue - if hasExternalLinkage(variable1) or hasExternalLinkage(variable2): - continue - if (variable1.nameToken.str[:num_sign_chars] == variable2.nameToken.str[:num_sign_chars] and - variable1 is not variable2): - if int(variable1.nameToken.linenr) > int(variable2.nameToken.linenr): - self.reportError(variable1.nameToken, 5, 2) - else: - self.reportError(variable2.nameToken, 5, 2) - for innerscope in scopeVars[scope]["scopelist"]: - if variable1.nameToken.str[:num_sign_chars] == innerscope.className[:num_sign_chars]: - if int(variable1.nameToken.linenr) > int(innerscope.bodyStart.linenr): - self.reportError(variable1.nameToken, 5, 2) - else: - self.reportError(innerscope.bodyStart, 5, 2) - if len(scopeVars[scope]["scopelist"]) <= 1: - continue - for i, scopename1 in enumerate(scopeVars[scope]["scopelist"]): - for scopename2 in scopeVars[scope]["scopelist"][i + 1:]: - if scopename1.className[:num_sign_chars] == scopename2.className[:num_sign_chars]: - if int(scopename1.bodyStart.linenr) > int(scopename2.bodyStart.linenr): - self.reportError(scopename1.bodyStart, 5, 2) - else: - self.reportError(scopename2.bodyStart, 5, 2) - - def misra_5_4(self, data): - num_sign_chars = self.get_num_significant_naming_chars(data) - macro = {} - compile_name = re.compile(r'#define ([a-zA-Z0-9_]+)') - compile_param = re.compile(r'#define ([a-zA-Z0-9_]+)[(]([a-zA-Z0-9_, ]+)[)]') - short_names = {} - macro_w_arg = [] - for dir in data.directives: - res1 = compile_name.match(dir.str) - if res1: - if dir not in macro: - macro.setdefault(dir, {})["name"] = [] - macro.setdefault(dir, {})["params"] = [] - full_name = res1.group(1) - macro[dir]["name"] = full_name - short_name = full_name[:num_sign_chars] - if short_name in short_names: - _dir = short_names[short_name] - if full_name != macro[_dir]["name"]: - self.reportError(dir, 5, 4) - else: - short_names[short_name] = dir - res2 = compile_param.match(dir.str) - if res2: - res_gp2 = res2.group(2).split(",") - res_gp2 = [macroname.replace(" ", "") for macroname in res_gp2] - macro[dir]["params"].extend(res_gp2) - macro_w_arg.append(dir) - for mvar in macro_w_arg: - for i, macroparam1 in enumerate(macro[mvar]["params"]): - for j, macroparam2 in enumerate(macro[mvar]["params"]): - if j > i and macroparam1[:num_sign_chars] == macroparam2[:num_sign_chars]: - self.reportError(mvar, 5, 4) - param = macroparam1 - if param[:num_sign_chars] in short_names: - m_var1 = short_names[param[:num_sign_chars]] - if m_var1.linenr > mvar.linenr: - self.reportError(m_var1, 5, 4) - else: - self.reportError(mvar, 5, 4) - - def misra_5_5(self, data): - num_sign_chars = self.get_num_significant_naming_chars(data) - macroNames = {} - compiled = re.compile(r'#define ([A-Za-z0-9_]+)') - for dir in data.directives: - res = compiled.match(dir.str) - if res: - macroNames[res.group(1)[:num_sign_chars]] = dir - for var in data.variables: - if var.nameToken and var.nameToken.str[:num_sign_chars] in macroNames: - self.reportError(var.nameToken, 5, 5) - for scope in data.scopes: - if scope.className and scope.className[:num_sign_chars] in macroNames: - self.reportError(scope.bodyStart, 5, 5) - - - def misra_5_6(self, dumpfile, typedefInfo): - self._save_ctu_summary_typedefs(dumpfile, typedefInfo) - - def misra_5_7(self, dumpfile, cfg): - self._save_ctu_summary_tagnames(dumpfile, cfg) - - def misra_5_8(self, dumpfile, cfg): - self._save_ctu_summary_identifiers(dumpfile, cfg) - - def misra_5_9(self, dumpfile, cfg): - self._save_ctu_summary_identifiers(dumpfile, cfg) - - def misra_6_1(self, data): - # Bitfield type must be bool or explicitly signed/unsigned int - for token in data.tokenlist: - if not token.valueType: - continue - if token.valueType.bits == 0: - continue - if not token.variable: - continue - if not token.scope: - continue - if token.scope.type not in 'Struct': - continue - - if data.standards.c == 'c89': - if token.valueType.type != 'int' and not isUnsignedType(token.variable.typeStartToken.str): - self.reportError(token, 6, 1) - elif data.standards.c == 'c99': - if token.valueType.type == 'bool': - continue - - isExplicitlySignedOrUnsigned = False - typeToken = token.variable.typeStartToken - while typeToken: - if typeToken.isUnsigned or typeToken.isSigned or isUnsignedType(typeToken.str): - isExplicitlySignedOrUnsigned = True - break - - if typeToken is token.variable.typeEndToken: - break - - typeToken = typeToken.next - - if not isExplicitlySignedOrUnsigned: - self.reportError(token, 6, 1) - - - def misra_6_2(self, data): - # Bitfields of size 1 can not be signed - for token in data.tokenlist: - if not token.valueType: - continue - if not token.scope: - continue - if token.scope.type not in 'Struct': - continue - if token.valueType.bits == 1 and token.valueType.sign == 'signed': - self.reportError(token, 6, 2) - - - def misra_7_1(self, rawTokens): - compiled = re.compile(r'^0[0-7]+$') - for tok in rawTokens: - if compiled.match(tok.str): - self.reportError(tok, 7, 1) - - def misra_7_2(self, data): - for token in data.tokenlist: - if token.isInt and ('U' not in token.str.upper()) and token.valueType and token.valueType.sign == 'unsigned': - self.reportError(token, 7, 2) - - def misra_7_3(self, rawTokens): - compiled = re.compile(r'^[0-9.]+[Uu]*l+[Uu]*$') - for tok in rawTokens: - if compiled.match(tok.str): - self.reportError(tok, 7, 3) - - def misra_7_4(self, data): - # A string literal shall not be assigned to an object unless the object's type - # is constant. - def reportErrorIfVariableIsNotConst(variable, stringLiteral): - if variable.valueType: - if (variable.valueType.constness % 2) != 1: - self.reportError(stringLiteral, 7, 4) - - for token in data.tokenlist: - if token.isString: - # Check normal variable assignment - variable = getAssignedVariableToken(token) - if variable: - reportErrorIfVariableIsNotConst(variable, token) - - # Check use as return value - function = getFunctionUsingReturnValue(token) - if function: - # "Primitive" test since there is no info available on return value type - if not tokenFollowsSequence(function.tokenDef, ['const', 'char', '*']): - self.reportError(token, 7, 4) - - # Check use as function parameter - if isFunctionCall(token) and token.astOperand1 and token.astOperand1.function: - functionDeclaration = token.astOperand1.function - - if functionDeclaration.tokenDef: - if functionDeclaration.tokenDef is token.astOperand1: - # Token is not a function call, but it is the definition of the function - continue - - parametersUsed = getArguments(token) - for i in range(len(parametersUsed)): - usedParameter = parametersUsed[i] - parameterDefinition = functionDeclaration.argument.get(i+1) - - if usedParameter.isString and parameterDefinition and parameterDefinition.nameToken: - reportErrorIfVariableIsNotConst(parameterDefinition.nameToken, usedParameter) - - def misra_8_1(self, cfg): - for token in cfg.tokenlist: - if token.isImplicitInt and not token.isUnsigned and not token.isSigned: - self.reportError(token, 8, 1) - - def misra_8_2(self, data, rawTokens): - def getFollowingRawTokens(rawTokens, token, count): - following =[] - for rawToken in rawTokens: - if (rawToken.file == token.file and - rawToken.linenr == token.linenr and - rawToken.column == token.column): - for _ in range(count): - rawToken = rawToken.next - # Skip comments - while rawToken and (rawToken.str.startswith('/*') or rawToken.str.startswith('//')): - rawToken = rawToken.next - if rawToken is None: - break - following.append(rawToken) - return following - - # Zero arguments should be in form ( void ) - def checkZeroArguments(func, startCall, endCall): - if not startCall.isRemovedVoidParameter and len(func.argument) == 0: - if func.tokenDef.next: - self.reportError(func.tokenDef.next, 8, 2) - else: - self.reportError(func.tokenDef, 8, 2) - - def checkDeclarationArgumentsViolations(func, startCall, endCall): - # Collect the tokens for the arguments in function definition - argNameTokens = set() - for arg in func.argument: - argument = func.argument[arg] - typeStartToken = argument.typeStartToken - if typeStartToken is None: - continue - nameToken = argument.nameToken - if nameToken is None: - continue - argNameTokens.add(nameToken) - - # Check if we have the same number of variables in both the - # declaration and the definition. - # - # TODO: We actually need to check if the names of the arguments are - # the same. But we can't do this because we have no links to - # variables in the arguments in function definition in the dump file. - foundVariables = 0 - while startCall and startCall != endCall: - if startCall.varId: - foundVariables += 1 - startCall = startCall.next - - if len(argNameTokens) != foundVariables: - if func.tokenDef.next: - self.reportError(func.tokenDef.next, 8, 2) - else: - self.reportError(func.tokenDef, 8, 2) - - def checkDefinitionArgumentsViolations(func, startCall, endCall): - for arg in func.argument: - argument = func.argument[arg] - typeStartToken = argument.typeStartToken - if typeStartToken is None: - continue - - # Arguments should have a name unless variable length arg - nameToken = argument.nameToken - if nameToken is None and typeStartToken.str != '...': - self.reportError(typeStartToken, 8, 2) - - # Type declaration on next line (old style declaration list) is not allowed - if typeStartToken.linenr > endCall.linenr: - self.reportError(typeStartToken, 8, 2) - - # Check arguments in function declaration - for func in data.functions: - - # Check arguments in function definition - tokenImpl = func.token - if tokenImpl: - startCall = tokenImpl.next - if startCall is None or startCall.str != '(': - continue - endCall = startCall.link - if endCall is None or endCall.str != ')': - continue - checkZeroArguments(func, startCall, endCall) - checkDefinitionArgumentsViolations(func, startCall, endCall) - - # Check arguments in function declaration - tokenDef = func.tokenDef - if tokenDef: - startCall = func.tokenDef.next - if startCall is None or startCall.str != '(': - continue - endCall = startCall.link - if endCall is None or endCall.str != ')': - continue - checkZeroArguments(func, startCall, endCall) - if tokenImpl: - checkDeclarationArgumentsViolations(func, startCall, endCall) - else: - # When there is no function definition, we should execute - # its checks for the declaration token. The point is that without - # a known definition we have no Function.argument list required - # for declaration check. - checkDefinitionArgumentsViolations(func, startCall, endCall) - - # Check arguments in pointer declarations - for var in data.variables: - if not var.isPointer: - continue - - if var.nameToken is None: - continue - - rawTokensFollowingPtr = getFollowingRawTokens(rawTokens, var.nameToken, 3) - if len(rawTokensFollowingPtr) != 3: - continue - - # Compliant: returnType (*ptrName) ( ArgType ) - # Non-compliant: returnType (*ptrName) ( ) - if (rawTokensFollowingPtr[0].str == ')' and - rawTokensFollowingPtr[1].str == '(' and - rawTokensFollowingPtr[2].str == ')'): - self.reportError(var.nameToken, 8, 2) - - - def misra_8_4(self, cfg): - for func in cfg.functions: - if func.isStatic: - continue - if func.token is None: - continue - if not is_source_file(func.token.file): - continue - if func.token != func.tokenDef: - continue - if func.tokenDef.str == 'main': - continue - self.reportError(func.tokenDef, 8, 4) - - extern_vars = [] - var_defs = [] - - for var in cfg.variables: - if not var.isGlobal: - continue - if var.isStatic: - continue - if var.nameToken is None: - continue - if var.isExtern: - extern_vars.append(var.nameToken.str) - else: - var_defs.append(var.nameToken) - for vartok in var_defs: - if vartok.str not in extern_vars: - self.reportError(vartok, 8, 4) - - def misra_8_5(self, dumpfile, cfg): - self._save_ctu_summary_identifiers(dumpfile, cfg) - - def misra_8_6(self, dumpfile, cfg): - self._save_ctu_summary_identifiers(dumpfile, cfg) - - def misra_8_7(self, dumpfile, cfg): - self._save_ctu_summary_usage(dumpfile, cfg) - - def misra_8_8(self, cfg): - vars = {} - for var in cfg.variables: - if var.access != 'Global': - continue - if var.nameToken is None: - continue - varname = var.nameToken.str - if varname in vars: - vars[varname].append(var) - else: - vars[varname] = [var] - for varname, varlist in vars.items(): - static_var = None - extern_var = None - for var in varlist: - if var.isStatic: - static_var = var - elif var.isExtern: - extern_var = var - if static_var and extern_var: - self.reportError(extern_var.nameToken, 8, 8) - - def misra_8_9(self, cfg): - variables = {} - for scope in cfg.scopes: - if scope.type != 'Function': - continue - variables_used_in_scope = [] - tok = scope.bodyStart - while tok != scope.bodyEnd: - if tok.variable and tok.variable.access == 'Global' and tok.variable.isStatic: - if tok.variable not in variables_used_in_scope: - variables_used_in_scope.append(tok.variable) - tok = tok.next - for var in variables_used_in_scope: - if var in variables: - variables[var] += 1 - else: - variables[var] = 1 - for var, count in variables.items(): - if count == 1: - self.reportError(var.nameToken, 8, 9) - - - def misra_8_10(self, cfg): - for func in cfg.functions: - if func.isInlineKeyword and not func.isStatic: - self.reportError(func.tokenDef, 8, 10) - - def misra_8_11(self, data): - for var in data.variables: - if var.isExtern and simpleMatch(var.nameToken.next, '[ ]') and var.nameToken.scope.type == 'Global': - self.reportError(var.nameToken, 8, 11) - - def misra_8_12(self, data): - for scope in data.scopes: - if scope.type != 'Enum': - continue - enum_values = [] - implicit_enum_values = [] - e_token = scope.bodyStart.next - while e_token != scope.bodyEnd: - if e_token.str == '(': - e_token = e_token.link - continue - if e_token.previous.str not in ',{': - e_token = e_token.next - continue - if e_token.isName and e_token.values and e_token.valueType and e_token.valueType.typeScope == scope: - token_values = [v.intvalue for v in e_token.values] - enum_values += token_values - if e_token.next.str != "=": - implicit_enum_values += token_values - e_token = e_token.next - for implicit_enum_value in implicit_enum_values: - if enum_values.count(implicit_enum_value) != 1: - self.reportError(scope.bodyStart, 8, 12) - - def misra_8_14(self, rawTokens): - for token in rawTokens: - if token.str == 'restrict': - self.reportError(token, 8, 14) - - def misra_9_2(self, data): - misra_9.misra_9_x(self, data, 902) - - def misra_9_3(self, data): - misra_9.misra_9_x(self, data, 903) - - def misra_9_4(self, data): - misra_9.misra_9_x(self, data, 904) - - def misra_9_5(self, data, rawTokens): - misra_9.misra_9_x(self, data, 905, rawTokens) - #for token in rawTokens: - # if simpleMatch(token, '[ ] = { ['): - # self.reportError(token, 9, 5) - - def misra_10_1(self, data): - for token in data.tokenlist: - if not token.isOp: - continue - - for t1, t2 in itertools.product( - list(getTernaryOperandsRecursive(token.astOperand1) or [token.astOperand1]), - list(getTernaryOperandsRecursive(token.astOperand2) or [token.astOperand2]), - ): - e1 = getEssentialTypeCategory(t1) - e2 = getEssentialTypeCategory(t2) - if not e1 or not e2: - continue - if token.str in ('<<', '>>'): - if not isUnsignedType(e1): - self.reportError(token, 10, 1) - elif not isUnsignedType(e2) and not token.astOperand2.isNumber: - self.reportError(token, 10, 1) - elif token.str in ('~', '&', '|', '^'): - e1_et = getEssentialType(token.astOperand1) - e2_et = getEssentialType(token.astOperand2) - if e1_et == 'char' or e2_et == 'char': - self.reportError(token, 10, 1) - - def misra_10_2(self, data): - def isEssentiallySignedOrUnsigned(op): - e = getEssentialType(op) - return e and (e.split(' ')[0] in ('unsigned', 'signed')) - - def isEssentiallyChar(op): - if op is None: - return False - if op.str == '+': - return isEssentiallyChar(op.astOperand1) or isEssentiallyChar(op.astOperand2) - return op.isChar - - for token in data.tokenlist: - if token.str not in ('+', '-'): - continue - - if (not isEssentiallyChar(token.astOperand1)) and (not isEssentiallyChar(token.astOperand2)): - continue - - if token.str == '+': - if isEssentiallyChar(token.astOperand1) and not isEssentiallySignedOrUnsigned(token.astOperand2): - self.reportError(token, 10, 2) - if isEssentiallyChar(token.astOperand2) and not isEssentiallySignedOrUnsigned(token.astOperand1): - self.reportError(token, 10, 2) - - if token.str == '-': - e1 = getEssentialType(token.astOperand1) - if e1 and e1.split(' ')[-1] != 'char': - self.reportError(token, 10, 2) - if not isEssentiallyChar(token.astOperand2) and not isEssentiallySignedOrUnsigned(token.astOperand2): - self.reportError(token, 10, 2) - - def misra_10_3(self, cfg): - def get_category(essential_type): - if essential_type: - if essential_type in ('bool', 'char'): - return essential_type - if essential_type.split(' ')[-1] in ('float', 'double'): - return 'floating' - if essential_type.split(' ')[0] in ('unsigned', 'signed'): - return essential_type.split(' ')[0] - return None - for tok in cfg.tokenlist: - if tok.isAssignmentOp: - lhs = getEssentialType(tok.astOperand1) - rhs = getEssentialType(tok.astOperand2) - #print(lhs) - #print(rhs) - if lhs is None or rhs is None: - continue - lhs_category = get_category(lhs) - rhs_category = get_category(rhs) - if lhs_category and rhs_category and lhs_category != rhs_category and rhs_category not in ('signed','unsigned'): - self.reportError(tok, 10, 3) - if bitsOfEssentialType(lhs) < bitsOfEssentialType(rhs): - self.reportError(tok, 10, 3) - - - def misra_10_4(self, data): - op = {'+', '-', '*', '/', '%', '&', '|', '^', '+=', '-=', ':'} - for token in data.tokenlist: - if token.str not in op and not token.isComparisonOp: - continue - if not token.astOperand1 or not token.astOperand2: - continue - if not token.astOperand1.valueType or not token.astOperand2.valueType: - continue - if ((token.astOperand1.str in op or token.astOperand1.isComparisonOp) and - (token.astOperand2.str in op or token.astOperand2.isComparisonOp)): - e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2.astOperand1) - elif token.astOperand1.str in op or token.astOperand1.isComparisonOp: - e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2) - elif token.astOperand2.str in op or token.astOperand2.isComparisonOp: - e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2.astOperand1) - else: - e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2) - if token.str == "+=" or token.str == "+": - if e1 == "char" and (e2 == "signed" or e2 == "unsigned"): - continue - if e2 == "char" and (e1 == "signed" or e1 == "unsigned"): - continue - if token.str == "-=" or token.str == "-": - if e1 == "char" and (e2 == "signed" or e2 == "unsigned"): - continue - if e1 and e2 and (e1.find('Anonymous') != -1 and (e2 == "signed" or e2 == "unsigned")): - continue - if e1 and e2 and (e2.find('Anonymous') != -1 and (e1 == "signed" or e1 == "unsigned")): - continue - if e1 and e2 and e1 != e2: - self.reportError(token, 10, 4) - - def misra_10_5(self, cfg): - def _get_essential_category(token): - essential_type = getEssentialType(token) - #print(essential_type) - if essential_type: - if essential_type in ('bool', 'char'): - return essential_type - if essential_type.split(' ')[-1] in ('float', 'double'): - return 'floating' - if essential_type.split(' ')[0] in ('unsigned', 'signed'): - return essential_type.split(' ')[0] - return None - for token in cfg.tokenlist: - if not isCast(token): - continue - to_type = _get_essential_category(token) - #print(to_type) - if to_type is None: - continue - from_type = _get_essential_category(token.astOperand1) - #print(from_type) - if from_type is None: - continue - if to_type == from_type: - continue - if to_type == 'bool' or from_type == 'bool': - if token.astOperand1.isInt and token.astOperand1.getKnownIntValue() == 1: - # Exception - continue - self.reportError(token, 10, 5) - continue - if to_type == 'enum': - self.reportError(token, 10, 5) - continue - if from_type == 'float' and to_type == 'char': - self.reportError(token, 10, 5) - continue - if from_type == 'char' and to_type == 'float': - self.reportError(token, 10, 5) - continue - - def misra_10_6(self, data): - for token in data.tokenlist: - if token.str != '=' or not token.astOperand1 or not token.astOperand2: - continue - if not is_composite_expr(token.astOperand2): - continue - vt1 = token.astOperand1.valueType - vt2 = token.astOperand2.valueType - if not vt1 or vt1.pointer > 0: - continue - if not vt2 or vt2.pointer > 0: - continue - try: - if isCast(token.astOperand2): - e = vt2.type - else: - e = getEssentialType(token.astOperand2) - if not e: - continue - if e == "char" and vt1.type == "int": - # When arithmetic operations are performed on char values, they are usually promoted to int - continue - lhsbits = vt1.bits if vt1.bits else bitsOfEssentialType(vt1.type) - if lhsbits > bitsOfEssentialType(e): - self.reportError(token, 10, 6) - except ValueError: - pass - - def misra_10_7(self, cfg): - for token in cfg.tokenlist: - if token.astOperand1 is None or token.astOperand2 is None: - continue - if not token.isArithmeticalOp: - continue - if not is_composite_expr(token): - continue - parent = token.astParent - if parent is None: - continue - if not parent.isArithmeticalOp: - if not parent.isAssignmentOp: - continue - if parent.str == '=': - continue - token_type = getEssentialType(token) - if token_type is None: - continue - sibling = parent.astOperand1 if (token == parent.astOperand2) else parent.astOperand2 - sibling_type = getEssentialType(sibling) - if sibling_type is None: - continue - b1 = bitsOfEssentialType(token_type) - b2 = bitsOfEssentialType(sibling_type) - if b1 > 0 and b1 < b2: - self.reportError(token, 10, 7) - - def misra_10_8(self, data): - for token in data.tokenlist: - if not isCast(token): - continue - if not token.valueType or token.valueType.pointer > 0: - continue - if not token.astOperand1.valueType or token.astOperand1.valueType.pointer > 0: - continue - if not token.astOperand1.astOperand1: - continue - if token.astOperand1.str not in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~'): - continue - if token.astOperand1.str != '~' and not token.astOperand1.astOperand2: - continue - if token.astOperand1.str == '~': - e2 = getEssentialTypeCategory(token.astOperand1.astOperand1) - else: - e2, e3 = getEssentialCategorylist(token.astOperand1.astOperand1, token.astOperand1.astOperand2) - if e2 != e3: - continue - e1 = getEssentialTypeCategory(token) - if e1 != e2: - self.reportError(token, 10, 8) - else: - try: - e = getEssentialType(token.astOperand1) - if not e: - continue - if bitsOfEssentialType(token.valueType.type) > bitsOfEssentialType(e): - self.reportError(token, 10, 8) - except ValueError: - pass - - def misra_11_1(self, data): - for token in data.tokenlist: - to_from = get_type_conversion_to_from(token) - if to_from is None: - continue - from_type = get_function_pointer_type(to_from[1]) - if from_type is None: - continue - to_type = get_function_pointer_type(to_from[0]) - if to_type is None or to_type != from_type: - self.reportError(token, 11, 1) - - def misra_11_2(self, data): - def get_pointer_type(type_token): - while type_token and (type_token.str in ('const', 'struct')): - type_token = type_token.next - if type_token is None: - return None - if not type_token.isName: - return None - return type_token if (type_token.next and type_token.next.str == '*') else None - - incomplete_types = [] - - for token in data.tokenlist: - if token.str == 'struct' and token.next and token.next.next and token.next.isName and token.next.next.str == ';': - incomplete_types.append(token.next.str) - to_from = get_type_conversion_to_from(token) - if to_from is None: - continue - to_pointer_type_token = get_pointer_type(to_from[0]) - if to_pointer_type_token is None: - continue - from_pointer_type_token = get_pointer_type(to_from[1]) - if from_pointer_type_token is None: - continue - if to_pointer_type_token.str == from_pointer_type_token.str: - continue - if from_pointer_type_token.typeScope is None and (from_pointer_type_token.str in incomplete_types): - self.reportError(token, 11, 2) - elif to_pointer_type_token.typeScope is None and (to_pointer_type_token.str in incomplete_types): - self.reportError(token, 11, 2) - - def misra_11_3(self, data): - for token in data.tokenlist: - if not isCast(token): - continue - vt1 = token.valueType - vt2 = token.astOperand1.valueType - if not vt1 or not vt2: - continue - if vt1.type == 'void' or vt2.type == 'void': - continue - if (vt1.pointer > 0 and vt1.type == 'record' and - vt2.pointer > 0 and vt2.type == 'record' and - vt1.typeScopeId != vt2.typeScopeId): - self.reportError(token, 11, 3) - elif (vt1.pointer == vt2.pointer and vt1.pointer > 0 and - vt1.type != vt2.type and vt1.type != 'char'): - self.reportError(token, 11, 3) - - def misra_11_4(self, data): - # Get list of macro definitions - macros = {} - for directive in data.directives: - #define X ((peripheral_t *)0x40000U) - res = re.match(r'#define ([A-Za-z0-9_]+).*', directive.str) - if res: - if res.group(1) in macros: - macros[res.group(1)].append(directive) - else: - macros[res.group(1)] = [directive] - - # If macro definition is non-compliant then warn about the macro definition instead of - # the macro usages. To reduce diagnostics for a non-compliant macro. - bad_macros = [] - for token in data.tokenlist: - if not isCast(token): - continue - vt1 = token.valueType - vt2 = token.astOperand1.valueType - if not vt1 or not vt2: - continue - if vt2.pointer > 0 and vt1.pointer == 0 and (vt1.isIntegral() or vt1.isEnum()) and vt2.type != 'void': - self.reportError(token, 11, 4) - elif vt1.pointer > 0 and vt2.pointer == 0 and (vt2.isIntegral() or vt2.isEnum()) and vt1.type != 'void': - if token.macroName is not None and \ - token.macroName == token.astOperand1.macroName and \ - token.astOperand1.isInt and \ - token.link.previous.str == '*' and \ - token.macroName == token.link.previous.macroName and \ - token.macroName in macros and \ - len(macros[token.macroName]) == 1: - if token.macroName not in bad_macros: - bad_macros.append(token.macroName) - self.reportError(macros[token.macroName][0], 11, 4) - continue - self.reportError(token, 11, 4) - - def misra_11_5(self, data): - for token in data.tokenlist: - if not isCast(token): - if token.astOperand1 and token.astOperand2 and token.str == "=" and token.next.str != "(": - vt1 = token.astOperand1.valueType - vt2 = token.astOperand2.valueType - if not vt1 or not vt2: - continue - if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void': - self.reportError(token, 11, 5) - continue - if token.astOperand1.astOperand1 and token.astOperand1.astOperand1.str in ( - 'malloc', 'calloc', 'realloc', 'free'): - continue - vt1 = token.valueType - vt2 = token.astOperand1.valueType - if not vt1 or not vt2: - continue - if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void': - self.reportError(token, 11, 5) - - def misra_11_6(self, data): - for token in data.tokenlist: - if not isCast(token): - continue - if token.astOperand1.astOperand1: - continue - vt1 = token.valueType - vt2 = token.astOperand1.valueType - if not vt1 or not vt2: - continue - if vt1.pointer == 1 and vt1.type == 'void' and vt2.pointer == 0 and token.astOperand1.getKnownIntValue() != 0: - self.reportError(token, 11, 6) - elif vt1.pointer == 0 and vt1.type != 'void' and vt2.pointer == 1 and vt2.type == 'void': - self.reportError(token, 11, 6) - - def misra_11_7(self, data): - for token in data.tokenlist: - if not isCast(token): - continue - vt1 = token.valueType - vt2 = token.astOperand1.valueType - if not vt1 or not vt2: - continue - if token.astOperand1.astOperand1: - continue - if (vt2.pointer > 0 and vt1.pointer == 0 and - not vt1.isIntegral() and not vt1.isEnum() and - vt1.type != 'void'): - self.reportError(token, 11, 7) - elif (vt1.pointer > 0 and vt2.pointer == 0 and - not vt2.isIntegral() and not vt2.isEnum() and - vt1.type != 'void'): - self.reportError(token, 11, 7) - - def misra_11_8(self, data): - # TODO: reuse code in CERT-EXP05 - for token in data.tokenlist: - if isCast(token): - # C-style cast - if not token.valueType: - continue - if not token.astOperand1.valueType: - continue - if token.valueType.pointer == 0: - continue - if token.astOperand1.valueType.pointer == 0: - continue - const1 = token.valueType.constness - const2 = token.astOperand1.valueType.constness - if (const1 % 2) < (const2 % 2): - self.reportError(token, 11, 8) - - elif token.str == '(' and token.astOperand1 and token.astOperand2 and token.astOperand1.function: - # Function call - function = token.astOperand1.function - arguments = getArguments(token) - for argnr, argvar in function.argument.items(): - if argnr < 1 or argnr > len(arguments): - continue - if not argvar.isPointer: - continue - argtok = arguments[argnr - 1] - if not argtok.valueType: - continue - if argtok.valueType.pointer == 0: - continue - const1 = argvar.constness - const2 = arguments[argnr - 1].valueType.constness - if (const1 % 2) < (const2 % 2): - self.reportError(token, 11, 8) - - def misra_11_9(self, data): - for token in data.tokenlist: - if token.astOperand1 and token.astOperand2 and token.str in ["=", "==", "!=", "?", ":"]: - vt1 = token.astOperand1.valueType - vt2 = token.astOperand2.valueType - if not vt1 or not vt2: - continue - if vt1.pointer > 0 and vt2.pointer == 0 and token.astOperand2.str == "NULL": - continue - if (token.astOperand2.values and vt1.pointer > 0 and - vt2.pointer == 0 and token.astOperand2.values): - if token.astOperand2.getValue(0): - self.reportError(token, 11, 9) - - def misra_12_1_sizeof(self, rawTokens): - state = 0 - compiled = re.compile(r'^[a-zA-Z_]') - for tok in rawTokens: - if tok.str.startswith('//') or tok.str.startswith('/*'): - continue - if tok.str == 'sizeof': - state = 1 - elif state == 1: - if compiled.match(tok.str): - state = 2 - else: - state = 0 - elif state == 2: - if tok.str in ('+', '-', '*', '/', '%'): - self.reportError(tok, 12, 1) - else: - state = 0 - - def misra_12_1(self, data): - for token in data.tokenlist: - p = getPrecedence(token) - if p < 2 or p > 12: - continue - p1 = getPrecedence(token.astOperand1) - if p < p1 <= 12 and numberOfParentheses(token.astOperand1, token): - self.reportError(token, 12, 1) - continue - p2 = getPrecedence(token.astOperand2) - if p < p2 <= 12 and numberOfParentheses(token, token.astOperand2): - self.reportError(token, 12, 1) - continue - - def misra_12_2(self, data): - for token in data.tokenlist: - if not (token.str in ('<<', '>>')): - continue - if (not token.astOperand2) or (not token.astOperand2.values): - continue - maxval = 0 - for val in token.astOperand2.values: - if val.intvalue and val.intvalue > maxval: - maxval = val.intvalue - if maxval == 0: - continue - sz = bitsOfEssentialType(getEssentialType(token.astOperand1)) - if sz <= 0: - continue - if maxval >= sz: - self.reportError(token, 12, 2) - - def misra_12_3(self, data): - for token in data.tokenlist: - if token.str == ';' and (token.isSplittedVarDeclComma is True): - self.reportError(token, 12, 3) - if token.str == ',' and token.astParent and token.astParent.str == ';': - self.reportError(token, 12, 3) - if token.str == ',' and token.astParent is None: - if token.scope.type in ('Class', 'Struct'): - # Is this initlist.. - tok = token - while tok and tok.str == ',': - tok = tok.next - if tok and tok.next and tok.isName and tok.next.str == '(': - tok = tok.next.link.next - if tok.str == '{': - # This comma is used in initlist, do not warn - continue - prev = token.previous - while prev: - if prev.str == ';': - self.reportError(token, 12, 3) - break - elif prev.str in ')}]': - prev = prev.link - elif prev.str in '({[': - break - prev = prev.previous - - def misra_12_4(self, cfg): - for expr in cfg.tokenlist: - if not expr.astOperand2 or not expr.astOperand1: - continue - if expr.valueType is None: - continue - if expr.valueType.sign is None or expr.valueType.sign != 'unsigned': - continue - if expr.valueType.pointer > 0: - continue - if not expr.valueType.isIntegral(): - continue - op1 = expr.astOperand1.getKnownIntValue() - if op1 is None: - continue - op2 = expr.astOperand2.getKnownIntValue() - if op2 is None: - continue - bits = bitsOfEssentialType('unsigned ' + expr.valueType.type) - if bits <= 0 or bits >= 64: - continue - max_value = (1 << bits) - 1 - if not is_constant_integer_expression(expr): - continue - if expr.str == '+' and op1 + op2 > max_value: - self.reportError(expr, 12, 4) - elif expr.str == '-' and op1 - op2 < 0: - self.reportError(expr, 12, 4) - elif expr.str == '*' and op1 * op2 > max_value: - self.reportError(expr, 12, 4) - - - def misra_13_1(self, data): - for token in data.tokenlist: - if simpleMatch(token, ") {") and token.next.astParent == token.link: - pass - elif not simpleMatch(token, '= {'): - continue - init = token.next - end = init.link - if not end: - continue # syntax is broken - - tn = init - while tn and tn != end: - if tn.str == '[' and tn.link: - tn = tn.link - if tn and tn.next and tn.next.str == '=': - tn = tn.next.next - continue - else: - break - if tn.str == '.' and tn.next and tn.next.isName: - tn = tn.next - if tn.next and tn.next.str == '=': - tn = tn.next.next - continue - if tn.str in {'++', '--'} or tn.isAssignmentOp: - self.reportError(init, 13, 1) - tn = tn.next - - def misra_13_3(self, data): - for token in data.tokenlist: - if token.str not in ('++', '--'): - continue - astTop = token - while astTop.astParent and astTop.astParent.str not in (',', ';'): - astTop = astTop.astParent - if countSideEffects(astTop) >= 2: - self.reportError(astTop, 13, 3) - - def misra_13_4(self, data): - for token in data.tokenlist: - if token.str != '=': - continue - if not token.astParent: - continue - if (token.astOperand1 is None) or (token.astOperand2 is None): - continue - if token.astOperand1.str == '[' and token.astOperand1.previous.str in ('{', ','): - continue - if not (token.astParent.str in [',', ';', '{']): - self.reportError(token, 13, 4) - - def misra_13_5(self, data): - for token in data.tokenlist: - if token.isLogicalOp and countSideEffectsRecursive(token.astOperand2) > 0: - self.reportError(token, 13, 5) - - def misra_13_6(self, data): - for token in data.tokenlist: - if token.str == 'sizeof' and countSideEffectsRecursive(token.next) > 0: - self.reportError(token, 13, 6) - - def misra_14_1(self, data): - for token in data.tokenlist: - if token.str == 'for': - exprs = getForLoopExpressions(token) - if not exprs: - continue - for counter in findCounterTokens(exprs[1]): - if counter.valueType and counter.valueType.isFloat(): - self.reportError(token, 14, 1) - elif token.str == 'while': - if isFloatCounterInWhileLoop(token): - self.reportError(token, 14, 1) - - def misra_14_2(self, data): - for token in data.tokenlist: - if token.str == 'for': - expressions = getForLoopExpressions(token) - if not expressions: - continue - if expressions[0] and not expressions[0].isAssignmentOp: - if expressions[0].str != "(" or not expressions[0].previous.isName: - self.reportError(token, 14, 2) - if countSideEffectsRecursive(expressions[1]) > 0: - self.reportError(token, 14, 2) - if countSideEffectsRecursive(expressions[2]) > 1: - self.reportError(token, 14, 2) - - counter_vars_first_clause, counter_vars_exit_modified = getForLoopCounterVariables(token, data) - if len(counter_vars_exit_modified) == 0: - # if it's not possible to identify a loop counter, all 3 clauses must be empty - for idx in range(len(expressions)): - if expressions[idx]: - self.reportError(token, 14, 2) - break - elif len(counter_vars_exit_modified) > 1: - # there shall be a single loop counter - self.reportError(token, 14, 2) - else: # len(counter_vars_exit_modified) == 1: - loop_counter = counter_vars_exit_modified.pop() - # if the first clause is not empty, then it shall (declare and) initialize the loop counter - if expressions[0] is not None and loop_counter not in counter_vars_first_clause: - self.reportError(token, 14, 2) - - # Inspect modification of loop counter in loop body - body_scope = token.next.link.next.scope - if not body_scope: - continue - tn = body_scope.bodyStart - while tn and tn != body_scope.bodyEnd: - if tn.variable == loop_counter: - if tn.next: - # TODO: Check modifications in function calls - if countSideEffectsRecursive(tn.next) > 0: - self.reportError(tn, 14, 2) - tn = tn.next - - def misra_14_4(self, data): - for token in data.tokenlist: - if token.str != '(': - continue - if not token.astOperand1 or not (token.astOperand1.str in ['if', 'while']): - continue - if isBoolExpression(token.astOperand2): - continue - if token.astOperand2.valueType: - self.reportError(token, 14, 4) - - def misra_15_1(self, data): - for token in data.tokenlist: - if token.str == "goto": - self.reportError(token, 15, 1) - - def misra_15_2(self, data): - for token in data.tokenlist: - if token.str != 'goto': - continue - if (not token.next) or (not token.next.isName): - continue - if not findGotoLabel(token): - self.reportError(token, 15, 2) - - def misra_15_3(self, data): - for token in data.tokenlist: - if token.str != 'goto': - continue - if (not token.next) or (not token.next.isName): - continue - tok = findGotoLabel(token) - if not tok: - continue - scope = token.scope - while scope and scope != tok.scope: - scope = scope.nestedIn - if not scope: - self.reportError(token, 15, 3) - # Jump crosses from one switch-clause to another is non-compliant - elif scope.type == 'Switch': - # Search for start of a current case block - tcase_start = token - while tcase_start and tcase_start.str not in ('case', 'default'): - tcase_start = tcase_start.previous - # Make sure that goto label doesn't occurs in the other - # switch-clauses - if tcase_start: - t = scope.bodyStart - in_this_case = False - while t and t != scope.bodyEnd: - if t == tcase_start: - in_this_case = True - if in_this_case and t.str not in ('case', 'default'): - in_this_case = False - if t == tok and not in_this_case: - self.reportError(token, 15, 3) - break - t = t.next - - def misra_15_4(self, data): - # Return a list of scopes affected by a break or goto - def getLoopsAffectedByBreak(knownLoops, scope, isGoto): - if scope and scope.type and scope.type not in ['Global', 'Function']: - if not isGoto and scope.type == 'Switch': - return - if scope.type in ['For', 'While', 'Do']: - knownLoops.append(scope) - if not isGoto: - return - getLoopsAffectedByBreak(knownLoops, scope.nestedIn, isGoto) - - loopWithBreaks = {} - for token in data.tokenlist: - if token.str not in ['break', 'goto']: - continue - - affectedLoopScopes = [] - getLoopsAffectedByBreak(affectedLoopScopes, token.scope, token.str == 'goto') - for scope in affectedLoopScopes: - if scope in loopWithBreaks: - loopWithBreaks[scope] += 1 - else: - loopWithBreaks[scope] = 1 - - for scope, breakCount in loopWithBreaks.items(): - if breakCount > 1: - self.reportError(scope.bodyStart, 15, 4) - - def misra_15_5(self, data): - for token in data.tokenlist: - if token.str == 'return' and token.scope.type != 'Function': - self.reportError(token, 15, 5) - - def misra_15_6(self, rawTokens): - state = 0 - indent = 0 - tok1 = None - def tokAt(tok,i): - while i < 0 and tok: - tok = tok.previous - if tok.str.startswith('//') or tok.str.startswith('/*'): - continue - i += 1 - while i > 0 and tok: - tok = tok.next - if tok.str.startswith('//') or tok.str.startswith('/*'): - continue - i -= 1 - return tok - - def strtokens(tok, i1, i2): - tok1 = tokAt(tok, i1) - tok2 = tokAt(tok, i2) - tok = tok1 - s = '' - while tok != tok2: - if tok.str.startswith('//') or tok.str.startswith('/*'): - tok = tok.next - continue - s += ' ' + tok.str - tok = tok.next - s += ' ' + tok.str - return s[1:] - - for token in rawTokens: - if token.str in ['if', 'for', 'while']: - if strtokens(token,-1,0) == '# if': - continue - if strtokens(token,-1,0) == "} while": - # is there a 'do { .. } while'? - start = rawlink(tokAt(token,-1)) - if start and strtokens(start, -1, 0) == 'do {': - continue - if state == 2: - self.reportError(tok1, 15, 6) - state = 1 - indent = 0 - tok1 = token - elif token.str == 'else': - if strtokens(token,-1,0) == '# else': - continue - if strtokens(token,0,1) == 'else if': - continue - if state == 2: - self.reportError(tok1, 15, 6) - state = 2 - indent = 0 - tok1 = token - elif state == 1: - if indent == 0 and token.str != '(': - state = 0 - continue - if token.str == '(': - indent = indent + 1 - elif token.str == ')': - if indent == 0: - state = 0 - elif indent == 1: - state = 2 - indent = indent - 1 - elif state == 2: - if token.str.startswith('//') or token.str.startswith('/*'): - continue - state = 0 - if token.str not in ('{', '#'): - self.reportError(tok1, 15, 6) - - def misra_15_7(self, data): - for scope in data.scopes: - if scope.type != 'Else': - continue - if not simpleMatch(scope.bodyStart, '{ if ('): - continue - if scope.bodyStart.column > 0: - continue - tok = scope.bodyStart.next.next.link - if not simpleMatch(tok, ') {'): - continue - tok = tok.next.link - if not simpleMatch(tok, '} else'): - self.reportError(tok, 15, 7) - - def misra_16_1(self, cfg): - for scope in cfg.scopes: - if scope.type != 'Switch': - continue - in_case_or_default = False - tok = scope.bodyStart.next - while tok != scope.bodyEnd: - if not in_case_or_default: - if tok.str not in ('case', 'default'): - self.reportError(tok, 16, 1) - else: - in_case_or_default = True - else: - if simpleMatch(tok, 'break ;'): - in_case_or_default = False - tok = tok.next - if tok.str == '{': - tok = tok.link - if tok.scope.type == 'Unconditional' and simpleMatch(tok.previous.previous, 'break ;'): - in_case_or_default = False - tok = tok.next - - def misra_16_2(self, data): - for token in data.tokenlist: - if token.str == 'case' and token.scope.type != 'Switch': - self.reportError(token, 16, 2) - - def misra_16_3(self, rawTokens): - STATE_NONE = 0 # default state, not in switch case/default block - STATE_BREAK = 1 # break/comment is seen but not its ';' - STATE_OK = 2 # a case/default is allowed (we have seen 'break;'/'comment'/'{'/attribute) - STATE_SWITCH = 3 # walking through switch statement scope - - define = None - state = STATE_NONE - end_switch_token = None # end '}' for the switch scope - for token in rawTokens: - if simpleMatch(token, '# define'): - define = token - if define: - if token.linenr != define.linenr: - define = None - else: - continue - - # Find switch scope borders - if token.str == 'switch': - state = STATE_SWITCH - if state == STATE_SWITCH: - if token.str == '{': - end_switch_token = findRawLink(token) - else: - continue - - if token.str == 'break' or token.str == 'return' or token.str == 'throw': - state = STATE_BREAK - elif token.str == ';': - if state == STATE_BREAK: - state = STATE_OK - elif token.next and token.next == end_switch_token: - self.reportError(token.next, 16, 3) - else: - state = STATE_NONE - elif token.str.startswith('/*') or token.str.startswith('//'): - if 'fallthrough' in token.str.lower(): - state = STATE_OK - elif simpleMatch(token, '[ [ fallthrough ] ] ;'): - state = STATE_BREAK - elif token.str == '{': - state = STATE_OK - elif token.str == '}' and state == STATE_OK: - # is this {} an unconditional block of code? - prev = findRawLink(token) - if prev: - prev = prev.previous - while prev and prev.str[:2] in ('//', '/*'): - prev = prev.previous - if (prev is None) or (prev.str not in ':;{}'): - state = STATE_NONE - elif token.str == 'case' or token.str == 'default': - if state != STATE_OK: - self.reportError(token, 16, 3) - state = STATE_OK - - def misra_16_4(self, data): - for token in data.tokenlist: - if token.str != 'switch': - continue - if not simpleMatch(token, 'switch ('): - continue - if not simpleMatch(token.next.link, ') {'): - continue - startTok = token.next.link.next - tok = startTok.next - while tok and tok.str != '}': - if tok.str == '{': - tok = tok.link - elif tok.str == 'default': - break - tok = tok.next - if tok and tok.str != 'default': - self.reportError(token, 16, 4) - - def misra_16_5(self, data): - for token in data.tokenlist: - if token.str != 'default': - continue - if token.previous and token.previous.str == '{': - continue - tok2 = token - while tok2: - if tok2.str in ('}', 'case'): - break - if tok2.str == '{': - tok2 = tok2.link - tok2 = tok2.next - if tok2 and tok2.str == 'case': - self.reportError(token, 16, 5) - - def misra_16_6(self, data): - for token in data.tokenlist: - if not (simpleMatch(token, 'switch (') and simpleMatch(token.next.link, ') {')): - continue - tok = token.next.link.next.next - count = 0 - while tok: - if tok.str in ['break', 'return', 'throw']: - count = count + 1 - elif tok.str == '{': - tok = tok.link - if isNoReturnScope(tok): - count = count + 1 - elif tok.str == '}': - break - tok = tok.next - if count < 2: - self.reportError(token, 16, 6) - - def misra_16_7(self, data): - for token in data.tokenlist: - if simpleMatch(token, 'switch (') and isBoolExpression(token.next.astOperand2): - self.reportError(token, 16, 7) - - def misra_17_1(self, data): - for token in data.tokenlist: - if isFunctionCall(token) and token.astOperand1.str in ( - 'va_list', 'va_arg', 'va_start', 'va_end', 'va_copy'): - self.reportError(token, 17, 1) - elif token.str == 'va_list': - self.reportError(token, 17, 1) - - def misra_17_2(self, data): - # find recursions.. - def find_recursive_call(search_for_function, direct_call, calls_map, visited=None): - if visited is None: - visited = set() - if direct_call == search_for_function: - return True - for indirect_call in calls_map.get(direct_call, []): - if indirect_call == search_for_function: - return True - if indirect_call in visited: - # This has already been handled - continue - visited.add(indirect_call) - if find_recursive_call(search_for_function, indirect_call, calls_map, visited): - return True - return False - - # List functions called in each function - function_calls = {} - for scope in data.scopes: - if scope.type != 'Function': - continue - calls = [] - tok = scope.bodyStart - while tok != scope.bodyEnd: - tok = tok.next - if not isFunctionCall(tok, data.standards.c): - continue - f = tok.astOperand1.function - if f is not None and f not in calls: - calls.append(f) - function_calls[scope.function] = calls - - # Report warnings for all recursions.. - for func in function_calls: - for call in function_calls[func]: - if not find_recursive_call(func, call, function_calls): - # Function call is not recursive - continue - # Warn about all functions calls.. - for scope in data.scopes: - if scope.type != 'Function' or scope.function != func: - continue - tok = scope.bodyStart - while tok != scope.bodyEnd: - if tok.function and tok.function == call: - self.reportError(tok, 17, 2) - tok = tok.next - - def misra_17_3(self, cfg): - for w in cfg.clang_warnings: - if w['message'].endswith('[-Wimplicit-function-declaration]'): - self.reportError(cppcheckdata.Location(w), 17, 3) - for token in cfg.tokenlist: - if token.str not in ["while", "if"]: - continue - if token.next.str != "(": - continue - tok = token.next - end_token = token.next.link - while tok != end_token: - if tok.isName and tok.function is None and tok.valueType is None and tok.next.str == "(" and \ - tok.next.valueType is None and not isKeyword(tok.str) and not isStdLibId(tok.str): - self.reportError(tok, 17, 3) - break - tok = tok.next - - def misra_config(self, data): - for var in data.variables: - if not var.isArray or var.nameToken is None or not cppcheckdata.simpleMatch(var.nameToken.next, '['): - continue - tok = var.nameToken.next - while tok.str == '[': - sz = tok.astOperand2 - if sz and sz.getKnownIntValue() is None: - has_var = False - unknown_constant = False - tokens = [sz] - while len(tokens) > 0: - t = tokens[-1] - tokens = tokens[:-1] - if t: - if t.isName and t.getKnownIntValue() is None: - if t.varId or t.variable: - has_var = True - continue - unknown_constant = True - self.report_config_error(tok, 'Unknown constant {}, please review configuration'.format(t.str)) - if t.isArithmeticalOp: - tokens += [t.astOperand1, t.astOperand2] - if not unknown_constant and not has_var: - self.report_config_error(tok, 'Unknown array size, please review configuration') - tok = tok.link.next - - for token in data.tokenlist: - if token.str not in ("while", "if"): - continue - tok = token.next - if token is None or tok.str != "(": - continue - end_token = tok.link - while tok != end_token: - tok = tok.next - if tok.str == "(" and tok.isCast: - tok = tok.link - continue - if not tok.isName: - continue - if tok.function or tok.variable or tok.varId or tok.valueType: - continue - if tok.next.str == "(" or tok.str in ["EOF"]: - continue - if isKeyword(tok.str) or isStdLibId(tok.str): - continue - if tok.astParent is None: - continue - if tok.astParent.str == "." and tok.astParent.valueType: - continue - self.report_config_error(tok, "Variable '%s' is unknown" % tok.str) - - def misra_17_6(self, rawTokens): - for token in rawTokens: - if simpleMatch(token, '[ static'): - self.reportError(token, 17, 6) - - def misra_17_7(self, data): - for token in data.tokenlist: - if not token.scope.isExecutable: - continue - if token.str != '(' or token.astParent: - continue - if token.astOperand1 is None or not token.astOperand1.isName: - continue - if token.astOperand1.varId and (token.astOperand1.variable is None or get_function_pointer_type(token.astOperand1.variable.typeStartToken) is None): - continue - if token.valueType is None: - continue - if token.valueType.type == 'void' and token.valueType.pointer == 0: - continue - self.reportError(token, 17, 7) - - def misra_17_8(self, data): - for token in data.tokenlist: - if not (token.isAssignmentOp or (token.str in ('++', '--'))): - continue - if not token.astOperand1: - continue - var = token.astOperand1.variable - if var and var.isArgument: - self.reportError(token, 17, 8) - - def misra_18_4(self, data): - for token in data.tokenlist: - if token.str not in ('+', '-', '+=', '-='): - continue - if token.astOperand1 is None or token.astOperand2 is None: - continue - vt1 = token.astOperand1.valueType - vt2 = token.astOperand2.valueType - if vt1 and vt1.pointer > 0: - self.reportError(token, 18, 4) - elif vt2 and vt2.pointer > 0: - self.reportError(token, 18, 4) - - def misra_18_5(self, data): - for var in data.variables: - if not var.isPointer: - continue - typetok = var.nameToken - count = 0 - while typetok: - if typetok.str == '*': - count = count + 1 - elif not typetok.isName: - break - typetok = typetok.previous - if count > 2: - self.reportError(var.nameToken, 18, 5) - - def misra_18_7(self, data): - for scope in data.scopes: - if scope.type != 'Struct': - continue - - token = scope.bodyStart.next - while token != scope.bodyEnd and token is not None: - # Handle nested structures to not duplicate an error. - if token.str == '{': - token = token.link - - # skip function pointer parameter types - if token.astOperand1 is None: - pass - elif cppcheckdata.simpleMatch(token, "[ ]"): - self.reportError(token, 18, 7) - break - token = token.next - - def misra_18_8(self, data): - for var in data.variables: - if not var.isArray or not var.isLocal: - continue - # TODO Array dimensions are not available in dump, must look in tokens - typetok = var.nameToken.next - if not typetok or typetok.str != '[': - continue - # Unknown define or syntax error - if not typetok.astOperand2: - continue - if not isConstantExpression(typetok.astOperand2) and not isUnknownConstantExpression(typetok.astOperand2): - self.reportError(var.nameToken, 18, 8) - - def misra_19_2(self, data): - for token in data.tokenlist: - if token.str == 'union': - self.reportError(token, 19, 2) - - def misra_20_1(self, data): - token_in_file = {} - for token in data.tokenlist: - if token.file not in token_in_file: - token_in_file[token.file] = int(token.linenr) - else: - token_in_file[token.file] = min(token_in_file[token.file], int(token.linenr)) - - for directive in data.directives: - if not directive.str.startswith('#include'): - continue - if directive.file not in token_in_file: - continue - if token_in_file[directive.file] < int(directive.linenr): - self.reportError(directive, 20, 1) - - def misra_20_2(self, data): - for directive in data.directives: - if not directive.str.startswith('#include '): - continue - for pattern in ('\\', '//', '/*', ',', "'"): - if pattern in directive.str: - self.reportError(directive, 20, 2) - break - - def misra_20_3(self, data): - for directive in data.directives: - if not directive.str.startswith('#include '): - continue - - words = directive.str.split(' ') - - # If include directive contains more than two words, here would be - # violation anyway. - if len(words) > 2: - self.reportError(directive, 20, 3) - - # Handle include directives with not quoted argument - elif len(words) > 1: - filename = words[1] - if not ((filename.startswith('"') and - filename.endswith('"')) or - (filename.startswith('<') and - filename.endswith('>'))): - # We are handle only directly included files in the - # following format: #include file.h - # Cases with macro expansion provided by MISRA document are - # skipped because we don't always have access to directive - # definition. - if '.' in filename: - self.reportError(directive, 20, 3) - - def misra_20_4(self, data): - for directive in data.directives: - res = re.search(r'#define ([a-z][a-z0-9_]+)', directive.str) - if res and isKeyword(res.group(1), data.standards.c): - self.reportError(directive, 20, 4) - - def misra_20_5(self, data): - for directive in data.directives: - if directive.str.startswith('#undef '): - self.reportError(directive, 20, 5) - - def misra_20_7(self, data): - def find_string_concat(exp, arg, directive_args): - # Handle concatenation of string literals, e.g.: - # #define MACRO(A, B) (A " " B) - # Addon should not report errors for both macro arguments. - arg_pos = exp.find(arg, 0) - need_check = False - skip_next = False - state_in_string = False - pos_search = arg_pos + 1 - directive_args = [a.strip() for a in directive_args if a != arg] - arg = arg.strip() - while pos_search < len(exp): - if exp[pos_search] == '"': - if state_in_string: - state_in_string = False - else: - state_in_string = True - pos_search += 1 - elif exp[pos_search].isalnum(): - word = "" - while pos_search < len(exp) and exp[pos_search].isalnum(): - word += exp[pos_search] - pos_search += 1 - if word == arg: - pos_search += 1 - elif word in directive_args: - skip_next = True - break - elif exp[pos_search] == ' ': - pos_search += 1 - elif state_in_string: - pos_search += 1 - else: - need_check = True - break - return need_check, skip_next - - for directive in data.directives: - d = Define(directive) - exp = '(' + d.expansionList + ')' - skip_next = False - for arg in d.args: - if skip_next: - _, skip_next = find_string_concat(exp, arg, d.args) - continue - need_check, skip_next = find_string_concat(exp, arg, d.args) - if not need_check: - continue - - pos = 0 - while pos < len(exp): - pos = exp.find(arg, pos) - if pos < 0: - break - # is 'arg' used at position pos - pos1 = pos - 1 - pos2 = pos + len(arg) - pos = pos2 - if pos1 >= 0 and (isalnum(exp[pos1]) or exp[pos1] == '_'): - continue - if pos2 < len(exp) and (isalnum(exp[pos2]) or exp[pos2] == '_'): - continue - - while pos1 >= 0 and exp[pos1] == ' ': - pos1 -= 1 - if exp[pos1] == '#': - continue - if exp[pos1] not in '([,.': - self.reportError(directive, 20, 7) - break - while pos2 < len(exp) and exp[pos2] == ' ': - pos2 += 1 - if pos2 < len(exp) and exp[pos2] not in ')]#,': - self.reportError(directive, 20, 7) - break - - def misra_20_8(self, cfg): - for cond in cfg.preprocessor_if_conditions: - #print(cond) - if cond.result and cond.result not in (0,1): - self.reportError(cond, 20, 8) - - def misra_20_9(self, cfg): - for cond in cfg.preprocessor_if_conditions: - if cond.E is None: - continue - defined = [] - for directive in cfg.directives: - if directive.file == cond.file and directive.linenr == cond.linenr: - for name in re.findall(r'[^_a-zA-Z0-9]defined[ ]*\([ ]*([_a-zA-Z0-9]+)[ ]*\)', directive.str): - defined.append(name) - for name in re.findall(r'[^_a-zA-Z0-9]defined[ ]*([_a-zA-Z0-9]+)', directive.str): - defined.append(name) - break - for s in cond.E.split(' '): - if (s[0] >= 'A' and s[0] <= 'Z') or (s[0] >= 'a' and s[0] <= 'z'): - if isKeyword(s): - continue - if s in defined: - continue - self.reportError(cond, 20, 9) - - def misra_20_10(self, data): - for directive in data.directives: - d = Define(directive) - if d.expansionList.find('#') >= 0: - self.reportError(directive, 20, 10) - - def misra_20_11(self, cfg): - for directive in cfg.directives: - d = Define(directive) - for arg in d.args: - res = re.search(r'[^#]#[ ]*%s[ ]*##' % arg, ' ' + d.expansionList) - if res: - self.reportError(directive, 20, 11) - - def misra_20_12(self, cfg): - def _is_hash_hash_op(expansion_list, arg): - return re.search(r'##[ ]*%s[^a-zA-Z0-9_]' % arg, expansion_list) or \ - re.search(r'[^a-zA-Z0-9_]%s[ ]*##' % arg, expansion_list) - - def _is_other_op(expansion_list, arg): - pos = expansion_list.find(arg) - while pos >= 0: - pos1 = pos - 1 - pos2 = pos + len(arg) - pos = expansion_list.find(arg, pos2) - if isalnum(expansion_list[pos1]) or expansion_list[pos1] == '_': - continue - if isalnum(expansion_list[pos2]) or expansion_list[pos2] == '_': - continue - while expansion_list[pos1] == ' ': - pos1 = pos1 - 1 - if expansion_list[pos1] == '#': - continue - while expansion_list[pos2] == ' ': - pos2 = pos2 + 1 - if expansion_list[pos2] == '#': - continue - return True - return False - - def _is_arg_macro_usage(directive, arg): - for macro_usage in cfg.macro_usage: - if macro_usage.file == directive.file and macro_usage.linenr == directive.linenr: - for macro_usage_arg in cfg.macro_usage: - if macro_usage_arg == macro_usage: - continue - if (macro_usage.usefile == macro_usage_arg.usefile and - macro_usage.uselinenr == macro_usage_arg.uselinenr and - macro_usage.usecolumn == macro_usage_arg.usecolumn): - # TODO: check arg better - return True - return False - - for directive in cfg.directives: - define = Define(directive) - expansion_list = '(%s)' % define.expansionList - for arg in define.args: - if not _is_hash_hash_op(expansion_list, arg): - continue - if not _is_other_op(expansion_list, arg): - continue - if _is_arg_macro_usage(directive, arg): - self.reportError(directive, 20, 12) - break - - def misra_20_13(self, data): - dir_pattern = re.compile(r'#[ ]*([^ (<]*)') - for directive in data.directives: - dir = directive.str - mo = dir_pattern.match(dir) - if mo: - dir = mo.group(1) - if dir not in ['define', 'elif', 'else', 'endif', 'error', 'if', 'ifdef', 'ifndef', 'include', - 'pragma', 'undef', 'warning']: - self.reportError(directive, 20, 13) - - def misra_20_14(self, data): - # stack for #if blocks. contains the #if directive until the corresponding #endif is seen. - # the size increases when there are inner #if directives. - ifStack = [] - for directive in data.directives: - if directive.str.startswith('#if ') or directive.str.startswith('#ifdef ') or directive.str.startswith( - '#ifndef '): - ifStack.append(directive) - elif directive.str == '#else' or directive.str.startswith('#elif '): - if len(ifStack) == 0: - self.reportError(directive, 20, 14) - ifStack.append(directive) - elif directive.file != ifStack[-1].file: - self.reportError(directive, 20, 14) - elif directive.str == '#endif': - if len(ifStack) == 0: - self.reportError(directive, 20, 14) - elif directive.file != ifStack[-1].file: - self.reportError(directive, 20, 14) - ifStack.pop() - - def misra_21_1(self, data): - re_forbidden_macro = re.compile(r'#(?:define|undef) _[_A-Z]+') - re_macro_name = re.compile(r'#(?:define|undef) (.+)[ $]') - - for d in data.directives: - # Search for forbidden identifiers - m = re.search(re_forbidden_macro, d.str) - if m: - self.reportError(d, 21, 1) - continue - - # Search standard library identifiers in macro names - m = re.search(re_macro_name, d.str) - if not m: - continue - name = m.group(1) - if isStdLibId(name, data.standards.c): - self.reportError(d, 21, 1) - - def misra_21_2(self, cfg): - for directive in cfg.directives: - define = Define(directive) - if re.match(r'_+BUILTIN_.*', define.name.upper()): - self.reportError(directive, 21, 2) - for func in cfg.functions: - if isStdLibId(func.name, cfg.standards.c): - tok = func.tokenDef if func.tokenDef else func.token - self.reportError(tok, 21, 2) - - def misra_21_3(self, data): - for token in data.tokenlist: - if isFunctionCall(token) and (token.astOperand1.str in ('malloc', 'calloc', 'realloc', 'free')): - self.reportError(token, 21, 3) - - def misra_21_4(self, data): - directive = findInclude(data.directives, '') - if directive: - self.reportError(directive, 21, 4) - - def misra_21_5(self, data): - directive = findInclude(data.directives, '') - if directive: - self.reportError(directive, 21, 5) - - def misra_21_6(self, data): - dir_stdio = findInclude(data.directives, '') - dir_wchar = findInclude(data.directives, '') - if dir_stdio: - self.reportError(dir_stdio, 21, 6) - if dir_wchar: - self.reportError(dir_wchar, 21, 6) - - def misra_21_7(self, data): - for token in data.tokenlist: - if isFunctionCall(token) and (token.astOperand1.str in ('atof', 'atoi', 'atol', 'atoll')): - self.reportError(token, 21, 7) - - def misra_21_8(self, data): - for token in data.tokenlist: - if isFunctionCall(token) and (token.astOperand1.str in ('abort', 'exit', 'getenv')): - self.reportError(token, 21, 8) - - def misra_21_9(self, data): - for token in data.tokenlist: - if (token.str in ('bsearch', 'qsort')) and token.next and token.next.str == '(': - self.reportError(token, 21, 9) - - def misra_21_10(self, data): - directive = findInclude(data.directives, '') - if directive: - self.reportError(directive, 21, 10) - - for token in data.tokenlist: - if (token.str == 'wcsftime') and token.next and token.next.str == '(': - self.reportError(token, 21, 10) - - def misra_21_11(self, data): - directive = findInclude(data.directives, '') - if directive: - self.reportError(directive, 21, 11) - - def misra_21_12(self, data): - if findInclude(data.directives, ''): - for token in data.tokenlist: - if token.str == 'fexcept_t' and token.isName: - self.reportError(token, 21, 12) - if isFunctionCall(token) and (token.astOperand1.str in ( - 'feclearexcept', - 'fegetexceptflag', - 'feraiseexcept', - 'fesetexceptflag', - 'fetestexcept')): - self.reportError(token, 21, 12) - - def misra_21_14(self, data): - # buffers used in strcpy/strlen/etc function calls - string_buffers = [] - for token in data.tokenlist: - if token.str[0] == 's' and isFunctionCall(token.next): - name, args = cppcheckdata.get_function_call_name_args(token) - if name is None: - continue - def _get_string_buffers(match, args, argnum): - if not match: - return [] - ret = [] - for a in argnum: - if a < len(args): - arg = args[a] - while arg and arg.str in ('.', '::'): - arg = arg.astOperand2 - if arg and arg.varId != 0 and arg.varId not in ret: - ret.append(arg.varId) - return ret - string_buffers += _get_string_buffers(name == 'strcpy', args, [0, 1]) - string_buffers += _get_string_buffers(name == 'strncpy', args, [0, 1]) - string_buffers += _get_string_buffers(name == 'strlen', args, [0]) - string_buffers += _get_string_buffers(name == 'strcmp', args, [0, 1]) - string_buffers += _get_string_buffers(name == 'sprintf', args, [0]) - string_buffers += _get_string_buffers(name == 'snprintf', args, [0, 3]) - - for token in data.tokenlist: - if token.str != 'memcmp': - continue - name, args = cppcheckdata.get_function_call_name_args(token) - if name is None: - continue - if len(args) != 3: - continue - for arg in args[:2]: - if arg.str[-1] == '\"': - self.reportError(arg, 21, 14) - continue - while arg and arg.str in ('.', '::'): - arg = arg.astOperand2 - if arg and arg.varId and arg.varId in string_buffers: - self.reportError(arg, 21, 14) - - def misra_21_15(self, data): - for token in data.tokenlist: - if token.str not in ('memcpy', 'memmove', 'memcmp'): - continue - name, args = cppcheckdata.get_function_call_name_args(token) - if name is None: - continue - if len(args) != 3: - continue - if args[0].valueType is None or args[1].valueType is None: - continue - if args[0].valueType.type == args[1].valueType.type: - continue - if args[0].valueType.type == 'void' or args[1].valueType.type == 'void': - continue - self.reportError(token, 21, 15) - - def misra_21_16(self, cfg): - for token in cfg.tokenlist: - if token.str != 'memcmp': - continue - name, args = cppcheckdata.get_function_call_name_args(token) - if name is None: - continue - if len(args) != 3: - continue - for arg in args[:2]: - if arg.valueType is None: - continue - if arg.valueType.pointer > 1: - continue - if arg.valueType.sign in ('unsigned', 'signed'): - continue - if arg.valueType.isEnum(): - continue - self.reportError(token, 21, 16) - - def misra_21_19(self, cfg): - for token in cfg.tokenlist: - if token.str in ('localeconv', 'getenv', 'setlocale', 'strerror') and simpleMatch(token.next, '('): - name, _ = cppcheckdata.get_function_call_name_args(token) - if name is None or name != token.str: - continue - parent = token.next - while simpleMatch(parent.astParent, '+'): - parent = parent.astParent - # x = f() - if simpleMatch(parent.astParent, '=') and parent == parent.astParent.astOperand2: - lhs = parent.astParent.astOperand1 - if lhs and lhs.valueType and lhs.valueType.pointer > 0 and lhs.valueType.constness == 0: - self.reportError(token, 21, 19) - if token.str == '=': - lhs = token.astOperand1 - while simpleMatch(lhs, '*') and lhs.astOperand2 is None: - lhs = lhs.astOperand1 - if not simpleMatch(lhs, '.'): - continue - while simpleMatch(lhs, '.'): - lhs = lhs.astOperand1 - if lhs and lhs.variable and simpleMatch(lhs.variable.typeStartToken, 'lconv'): - self.reportError(token, 21, 19) - - def misra_21_20(self, cfg): - assigned = {} - invalid = [] - for token in cfg.tokenlist: - # No sophisticated data flow analysis, bail out if control flow is "interrupted" - if token.str in ('{', '}', 'break', 'continue', 'return'): - assigned = {} - invalid = [] - continue - - # When pointer is assigned, remove it from 'assigned' and 'invalid' - if token.varId and token.varId > 0 and simpleMatch(token.next, '='): - for name in assigned.keys(): - while token.varId in assigned[name]: - assigned[name].remove(token.varId) - while token.varId in invalid: - invalid.remove(token.varId) - continue - - # Calling dangerous function - if token.str in ('asctime', 'ctime', 'gmtime', 'localtime', 'localeconv', 'getenv', 'setlocale', 'strerror'): - name, args = cppcheckdata.get_function_call_name_args(token) - if name and name == token.str: - # make assigned pointers invalid - for varId in assigned.get(name, ()): - if varId not in invalid: - invalid.append(varId) - - # assign pointer - parent = token.next - while parent.astParent and (parent.astParent.str == '+' or isCast(parent.astParent)): - parent = parent.astParent - if simpleMatch(parent.astParent, '='): - eq = parent.astParent - vartok = eq.previous - if vartok and vartok.varId and vartok.varId > 0: - if name not in assigned: - assigned[name] = [vartok.varId] - elif vartok.varId not in assigned[name]: - assigned[name].append(vartok.varId) - continue - - # taking value of invalid pointer.. - if token.astParent and token.varId: - if token.varId in invalid: - self.reportError(token, 21, 20) - - def misra_21_21(self, cfg): - for token in cfg.tokenlist: - if token.str == 'system': - name, args = cppcheckdata.get_function_call_name_args(token) - if name == 'system' and len(args) == 1: - self.reportError(token, 21, 21) - - def misra_22_5(self, cfg): - for token in cfg.tokenlist: - if token.isUnaryOp("*") or (token.isBinaryOp() and token.str == '.'): - fileptr = token.astOperand1 - if fileptr.variable and cppcheckdata.simpleMatch(fileptr.variable.typeStartToken, 'FILE *'): - self.reportError(token, 22, 5) - - def misra_22_7(self, cfg): - for eofToken in cfg.tokenlist: - if eofToken.str != 'EOF': - continue - if eofToken.astParent is None or not eofToken.astParent.isComparisonOp: - continue - if eofToken.astParent.astOperand1 == eofToken: - eofTokenSibling = eofToken.astParent.astOperand2 - else: - eofTokenSibling = eofToken.astParent.astOperand1 - while isCast(eofTokenSibling) and eofTokenSibling.valueType and eofTokenSibling.valueType.type and eofTokenSibling.valueType.type == 'int': - eofTokenSibling = eofTokenSibling.astOperand2 if eofTokenSibling.astOperand2 else eofTokenSibling.astOperand1 - if eofTokenSibling is not None and eofTokenSibling.valueType and eofTokenSibling.valueType and eofTokenSibling.valueType.type in ('bool', 'char', 'short'): - self.reportError(eofToken, 22, 7) - - def misra_22_8(self, cfg): - is_zero = False - for token in cfg.tokenlist: - if simpleMatch(token, 'errno = 0'): - is_zero = True - if token.str == '(' and not simpleMatch(token.link, ') {'): - name, _ = cppcheckdata.get_function_call_name_args(token.previous) - if name is None: - continue - if is_errno_setting_function(name): - if not is_zero: - self.reportError(token, 22, 8) - else: - is_zero = False - - def misra_22_9(self, cfg): - errno_is_set = False - for token in cfg.tokenlist: - if token.str == '(' and not simpleMatch(token.link, ') {'): - name, args = cppcheckdata.get_function_call_name_args(token.previous) - if name is None: - continue - errno_is_set = is_errno_setting_function(name) - if errno_is_set and token.str in '{};': - errno_is_set = False - tok = token.next - while tok and tok.str not in ('{','}',';','errno'): - tok = tok.next - if tok is None or tok.str != 'errno': - self.reportError(token, 22, 9) - elif (tok.astParent is None) or (not tok.astParent.isComparisonOp): - self.reportError(token, 22, 9) - - def misra_22_10(self, cfg): - last_function_call = None - for token in cfg.tokenlist: - if token.isName and token.next.str == '(' and not simpleMatch(token.next.link, ') {'): - name, args = cppcheckdata.get_function_call_name_args(token) - last_function_call = name - if token.str == '}': - last_function_call = None - if token.str == 'errno' and token.astParent and token.astParent.isComparisonOp: - if last_function_call is None: - self.reportError(token, 22, 10) - elif not is_errno_setting_function(last_function_call): - self.reportError(token, 22, 10) - - - def get_verify_expected(self): - """Return the list of expected violations in the verify test""" - return self.verify_expected - - def get_verify_actual(self): - """Return the list of actual violations in for the verify test""" - return self.verify_actual - - def get_violations(self, violation_type=None): - """Return the list of violations for a normal checker run""" - if violation_type is None: - return self.violations.items() - else: - return self.violations[violation_type] - - def get_violation_types(self): - """Return the list of violations for a normal checker run""" - return self.violations.keys() - - def addSuppressedRule(self, ruleNum, - fileName=None, - lineNumber=None, - symbolName=None): - """ - Add a suppression to the suppressions data structure - - Suppressions are stored in a dictionary of dictionaries that - contains a list of tuples. - - The first dictionary is keyed by the MISRA rule in hundreds - format. The value of that dictionary is a dictionary of filenames. - If the value is None then the rule is assumed to be suppressed for - all files. - If the filename exists then the value of that dictionary contains a list - with the scope of the suppression. If the list contains an item of None - then the rule is assumed to be suppressed for the entire file. Otherwise - the list contains line number, symbol name tuples. - For each tuple either line number or symbol name can can be none. - - """ - normalized_filename = None - - if fileName is not None: - normalized_filename = os.path.expanduser(fileName) - normalized_filename = os.path.normpath(normalized_filename) - - if lineNumber is not None or symbolName is not None: - line_symbol = (lineNumber, symbolName) - else: - line_symbol = None - - # If the rule is not in the dict already then add it - if ruleNum not in self.suppressedRules: - ruleItemList = list() - ruleItemList.append(line_symbol) - - fileDict = dict() - fileDict[normalized_filename] = ruleItemList - - self.suppressedRules[ruleNum] = fileDict - - # Rule is added. Done. - return - - # Rule existed in the dictionary. Check for - # filename entries. - - # Get the dictionary for the rule number - fileDict = self.suppressedRules[ruleNum] - - # If the filename is not in the dict already add it - if normalized_filename not in fileDict: - ruleItemList = list() - ruleItemList.append(line_symbol) - - fileDict[normalized_filename] = ruleItemList - - # Rule is added with a file scope. Done - return - - # Rule has a matching filename. Get the rule item list. - - # Check the lists of rule items - # to see if this (lineNumber, symbolName) combination - # or None already exists. - ruleItemList = fileDict[normalized_filename] - - if line_symbol is None: - # is it already in the list? - if line_symbol not in ruleItemList: - ruleItemList.append(line_symbol) - else: - # Check the list looking for matches - matched = False - for each in ruleItemList: - if each is not None: - if (each[0] == line_symbol[0]) and (each[1] == line_symbol[1]): - matched = True - - # Append the rule item if it was not already found - if not matched: - ruleItemList.append(line_symbol) - - def isRuleSuppressed(self, file_path, linenr, ruleNum): - """ - Check to see if a rule is suppressed. - - :param ruleNum: is the rule number in hundreds format - :param file_path: File path of checked location - :param linenr: Line number of checked location - - If the rule exists in the dict then check for a filename - If the filename is None then rule is suppressed globally - for all files. - If the filename exists then look for list of - line number, symbol name tuples. If the list is None then - the rule is suppressed for the entire file - If the list of tuples exists then search the list looking for - matching line numbers. Symbol names are currently ignored - because they can include regular expressions. - TODO: Support symbol names and expression matching. - - """ - ruleIsSuppressed = False - - # Remove any prefix listed in command arguments from the filename. - filename = None - if file_path is not None: - if self.filePrefix is not None: - filename = remove_file_prefix(file_path, self.filePrefix) - else: - filename = os.path.basename(file_path) - - if ruleNum in self.suppressedRules: - fileDict = self.suppressedRules[ruleNum] - - # a file name entry of None means that the rule is suppressed - # globally - if None in fileDict: - ruleIsSuppressed = True - else: - # Does the filename match one of the names in - # the file list - if filename in fileDict: - # Get the list of ruleItems - ruleItemList = fileDict[filename] - - if None in ruleItemList: - # Entry of None in the ruleItemList means the rule is - # suppressed for all lines in the filename - ruleIsSuppressed = True - else: - # Iterate though the the list of line numbers - # and symbols looking for a match of the line - # number. Matching the symbol is a TODO: - for each in ruleItemList: - if each is not None: - if each[0] == linenr: - ruleIsSuppressed = True - - return ruleIsSuppressed - - def isRuleGloballySuppressed(self, rule_num): - """ - Check to see if a rule is globally suppressed. - :param rule_num: is the rule number in hundreds format - """ - if rule_num not in self.suppressedRules: - return False - return None in self.suppressedRules[rule_num] - - def showSuppressedRules(self): - """ - Print out rules in suppression list sorted by Rule Number - """ - print("Suppressed Rules List:") - outlist = list() - - for ruleNum in self.suppressedRules: - fileDict = self.suppressedRules[ruleNum] - - for fname in fileDict: - ruleItemList = fileDict[fname] - - for item in ruleItemList: - if item is None: - item_str = "None" - else: - item_str = str(item[0]) - - outlist.append("%s: %s: %s (%d locations suppressed)" % ( - float(ruleNum) / 100, fname, item_str, self.suppressionStats.get(ruleNum, 0))) - - for line in sorted(outlist, reverse=True): - print(" %s" % line) - - def setFilePrefix(self, prefix): - """ - Set the file prefix to ignore from files when matching - suppression files - """ - self.filePrefix = prefix - - def setSeverity(self, severity): - """ - Set the severity for all errors. - """ - self.severity = severity - - def setSuppressionList(self, suppressionlist): - num1 = 0 - num2 = 0 - rule_pattern = re.compile(r'([0-9]+).([0-9]+)') - strlist = suppressionlist.split(",") - - # build ignore list - for item in strlist: - res = rule_pattern.match(item) - if res: - num1 = int(res.group(1)) - num2 = int(res.group(2)) - ruleNum = (num1 * 100) + num2 - - self.addSuppressedRule(ruleNum) - - def report_config_error(self, location, errmsg): - cppcheck_severity = 'error' - error_id = 'config' - if self.settings.verify: - self.verify_actual.append('%s:%d %s' % (location.file, location.linenr, error_id)) - else: - cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', error_id) - - def reportError(self, location, num1, num2): - ruleNum = num1 * 100 + num2 - - if self.isRuleGloballySuppressed(ruleNum): - return - - if self.settings.verify: - self.verify_actual.append('%s:%d %d.%d' % (location.file, location.linenr, num1, num2)) - elif self.isRuleSuppressed(location.file, location.linenr, ruleNum): - # Error is suppressed. Ignore - self.suppressionStats.setdefault(ruleNum, 0) - self.suppressionStats[ruleNum] += 1 - return - else: - errorId = 'c2012-' + str(num1) + '.' + str(num2) - misra_severity = 'Undefined' - cppcheck_severity = 'style' - if self.path_premium_addon and ruleNum not in self.ruleTexts: - for line in cppcheckdata.cmd_output([self.path_premium_addon, '--cli', '--get-rule-text=' + errorId]).split('\n'): - if len(line) > 1 and not line.startswith('{'): - errmsg = line.strip() - rule = Rule(num1, num2) - rule.text = errmsg - self.ruleTexts[rule.num] = rule - break - if ruleNum in self.ruleTexts: - errmsg = self.ruleTexts[ruleNum].text - if self.ruleTexts[ruleNum].misra_severity: - misra_severity = self.ruleTexts[ruleNum].misra_severity - cppcheck_severity = self.ruleTexts[ruleNum].cppcheck_severity - elif len(self.ruleTexts) == 0: - if self.ruleText_filename is None: - errmsg = 'misra violation (use --rule-texts= to get proper output)' - else: - errmsg = 'misra violation (rule-texts-file not found: ' + self.ruleText_filename + ')' - else: - errmsg = 'misra violation %s with no text in the supplied rule-texts-file' % (ruleNum) - - if self.severity: - cppcheck_severity = self.severity - - this_violation = '{}-{}-{}-{}'.format(location.file, location.linenr, location.column, ruleNum) - - # If this is new violation then record it and show it. If not then - # skip it since it has already been displayed. - if this_violation not in self.existing_violations: - self.existing_violations.add(this_violation) - cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId, misra_severity) - - if misra_severity not in self.violations: - self.violations[misra_severity] = [] - self.violations[misra_severity].append('misra-' + errorId) - - def loadRuleTexts(self, filename): - num1 = 0 - num2 = 0 - appendixA = False - ruleText = False - expect_more = False - - Rule_pattern = re.compile(r'^Rule ([0-9]+).([0-9]+)') - severity_pattern = re.compile(r'.*[ ]*(Advisory|Required|Mandatory)$') - xA_Z_pattern = re.compile(r'^[#A-Z].*') - a_z_pattern = re.compile(r'^[a-z].*') - # Try to detect the file encoding - file_stream = None - encodings = ['ascii', 'utf-8', 'windows-1250', 'windows-1252'] - for e in encodings: - try: - file_stream = codecs.open(filename, 'r', encoding=e) - file_stream.readlines() - file_stream.seek(0) - except UnicodeDecodeError: - file_stream.close() - file_stream = None - else: - break - if not file_stream: - print('Could not find a suitable codec for "' + filename + '".') - print('If you know the codec please report it to the developers so the list can be enhanced.') - print('Trying with default codec now and ignoring errors if possible ...') - try: - file_stream = open(filename, 'rt', errors='ignore') - except TypeError: - # Python 2 does not support the errors parameter - file_stream = open(filename, 'rt') - - rule = None - have_severity = False - severity_loc = 0 - - for line in file_stream: - - line = line.replace('\r', '').replace('\n', '') - - if not appendixA: - if line.find('Appendix A') >= 0 and line.find('Summary of guidelines') >= 10: - appendixA = True - continue - if line.find('Appendix B') >= 0: - break - if len(line) == 0: - continue - - # Parse rule declaration. - res = Rule_pattern.match(line) - - if res: - have_severity = False - expect_more = False - severity_loc = 0 - num1 = int(res.group(1)) - num2 = int(res.group(2)) - rule = Rule(num1, num2) - - if not have_severity and rule is not None: - res = severity_pattern.match(line) - - if res: - rule.misra_severity = res.group(1) - have_severity = True - else: - severity_loc += 1 - - # Only look for severity on the Rule line - # or the next non-blank line after - # If it's not in either of those locations then - # assume a severity was not provided. - - if severity_loc < 2: - continue - else: - rule.misra_severity = '' - have_severity = True - - if rule is None: - continue - - # Parse continuing of rule text. - if expect_more: - if a_z_pattern.match(line): - self.ruleTexts[rule.num].text += ' ' + line - continue - - expect_more = False - continue - - # Parse beginning of rule text. - if xA_Z_pattern.match(line): - rule.text = line - self.ruleTexts[rule.num] = rule - expect_more = True - - file_stream.close() - - def verifyRuleTexts(self): - """Prints rule numbers without rule text.""" - rule_texts_rules = [] - for rule_num in self.ruleTexts: - rule = self.ruleTexts[rule_num] - rule_texts_rules.append(str(rule.num1) + '.' + str(rule.num2)) - - all_rules = list(getAddonRules() + getCppcheckRules()) - - missing_rules = list(set(all_rules) - set(rule_texts_rules)) - if len(missing_rules) == 0: - print("Rule texts are correct.") - else: - print("Missing rule texts: " + ', '.join(missing_rules)) - - def printStatus(self, *args, **kwargs): - if not self.settings.quiet: - print(*args, **kwargs) - - def executeCheck(self, rule_num, check_function, *args): - """Execute check function for a single MISRA rule. - - :param rule_num: Number of rule in hundreds format - :param check_function: Check function to execute - :param args: Check function arguments - """ - if not self.isRuleGloballySuppressed(rule_num): - check_function(*args) - # misra_cpp = ( - # 202, # misra-c2012-2.3 : misra c++2008 0-1-9 - # 203, # misra-c2012-2.3 : misra c++2008 0-1-5 - # 402, # misra-c2012-4.2 : misra c++2008 2-3-1 - # 701, # misra-c2012-7.1 : misra c++2008 2-3-1 - # 702, # misra-c2012-7.2 : misra c++2008 2-13-2 - # 1203, # misra-c2012-12.3 : misra c++2008 5-14-1 - # 1204, # misra-c2012-12.4 : misra c++2008 5-18-1 - # 1305, # misra-c2012-13.5 : misra c++2008 5-19-1 - # 1702, # misra-c2012-17.2 : misra c++2008 7-5-4 - # 1901) # misra-c2012-19.1 : misra c++2008 2-13-3 - - # if (not self.is_cpp) or rule_num in misra_cpp: - # check_function(*args) - - def parseDump(self, dumpfile, path_premium_addon=None): - def fillVerifyExpected(verify_expected, tok): - """Add expected suppressions to verify_expected list.""" - rule_re = re.compile(r'[0-9]+\.[0-9]+') - if tok.str.startswith('//') and 'TODO' not in tok.str: - for word in tok.str[2:].split(' '): - if rule_re.match(word) or word == "config": - verify_expected.append('%s:%d %s' % (tok.file, tok.linenr, word)) - - data = cppcheckdata.parsedump(dumpfile) - typeBits['CHAR'] = data.platform.char_bit - typeBits['SHORT'] = data.platform.short_bit - typeBits['INT'] = data.platform.int_bit - typeBits['LONG'] = data.platform.long_bit - typeBits['LONG_LONG'] = data.platform.long_long_bit - typeBits['POINTER'] = data.platform.pointer_bit - - if self.settings.verify: - # Add suppressions from the current file - for tok in data.rawTokens: - fillVerifyExpected(self.verify_expected, tok) - # Add suppressions from the included headers - include_re = re.compile(r'^#include [<"]([a-zA-Z0-9]+[a-zA-Z\-_./\\0-9]*)[">]$') - dump_dir = os.path.dirname(data.filename) - for conf in data.configurations: - for directive in conf.directives: - m = re.match(include_re, directive.str) - if not m: - continue - header_dump_path = os.path.join(dump_dir, m.group(1) + '.dump') - if not os.path.exists(header_dump_path): - continue - header_data = cppcheckdata.parsedump(header_dump_path) - for tok in header_data.rawTokens: - fillVerifyExpected(self.verify_expected, tok) - else: - self.printStatus('Checking ' + dumpfile + '...') - - self.is_cpp = data.files and data.files[0].endswith('.cpp') - self.printStatus('1', self.is_cpp) - - for cfgNumber, cfg in enumerate(data.iterconfigurations()): - if not self.settings.quiet: - self.printStatus('Checking %s, config %s...' % (dumpfile, cfg.name)) - - self.printStatus('2') - self.executeCheck(102, self.misra_1_2, cfg) - if not path_premium_addon: - self.executeCheck(104, self.misra_1_4, cfg) - self.executeCheck(202, self.misra_2_2, cfg) - self.executeCheck(203, self.misra_2_3, dumpfile, cfg.typedefInfo) - self.executeCheck(204, self.misra_2_4, dumpfile, cfg) - self.executeCheck(205, self.misra_2_5, dumpfile, cfg) - self.executeCheck(207, self.misra_2_7, cfg) - # data.rawTokens is same for all configurations - if cfgNumber == 0: - self.executeCheck(301, self.misra_3_1, data.rawTokens) - self.executeCheck(302, self.misra_3_2, data.rawTokens) - self.executeCheck(401, self.misra_4_1, data.rawTokens) - self.executeCheck(402, self.misra_4_2, data.rawTokens) - self.executeCheck(501, self.misra_5_1, cfg) - self.executeCheck(502, self.misra_5_2, cfg) - self.executeCheck(504, self.misra_5_4, cfg) - self.executeCheck(505, self.misra_5_5, cfg) - self.executeCheck(506, self.misra_5_6, dumpfile, cfg.typedefInfo) - self.executeCheck(507, self.misra_5_7, dumpfile, cfg) - self.executeCheck(508, self.misra_5_8, dumpfile, cfg) - self.executeCheck(509, self.misra_5_9, dumpfile, cfg) - self.executeCheck(601, self.misra_6_1, cfg) - self.executeCheck(602, self.misra_6_2, cfg) - if cfgNumber == 0: - self.executeCheck(701, self.misra_7_1, data.rawTokens) - self.executeCheck(702, self.misra_7_2, cfg) - if cfgNumber == 0: - self.executeCheck(703, self.misra_7_3, data.rawTokens) - self.executeCheck(704, self.misra_7_4, cfg) - self.executeCheck(801, self.misra_8_1, cfg) - if cfgNumber == 0: - self.executeCheck(802, self.misra_8_2, cfg, data.rawTokens) - self.executeCheck(804, self.misra_8_4, cfg) - self.executeCheck(805, self.misra_8_5, dumpfile, cfg) - self.executeCheck(806, self.misra_8_6, dumpfile, cfg) - self.executeCheck(807, self.misra_8_7, dumpfile, cfg) - self.executeCheck(808, self.misra_8_8, cfg) - self.executeCheck(809, self.misra_8_9, cfg) - self.executeCheck(810, self.misra_8_10, cfg) - self.executeCheck(811, self.misra_8_11, cfg) - self.executeCheck(812, self.misra_8_12, cfg) - if cfgNumber == 0: - self.executeCheck(814, self.misra_8_14, data.rawTokens) - self.executeCheck(902, self.misra_9_2, cfg) - self.executeCheck(903, self.misra_9_3, cfg) - self.executeCheck(904, self.misra_9_4, cfg) - if cfgNumber == 0: - self.executeCheck(905, self.misra_9_5, cfg, data.rawTokens) - if not path_premium_addon: - self.executeCheck(1001, self.misra_10_1, cfg) - self.executeCheck(1002, self.misra_10_2, cfg) - self.executeCheck(1003, self.misra_10_3, cfg) - self.executeCheck(1004, self.misra_10_4, cfg) - self.executeCheck(1005, self.misra_10_5, cfg) - self.executeCheck(1006, self.misra_10_6, cfg) - self.executeCheck(1007, self.misra_10_7, cfg) - self.executeCheck(1008, self.misra_10_8, cfg) - self.executeCheck(1101, self.misra_11_1, cfg) - self.executeCheck(1102, self.misra_11_2, cfg) - self.executeCheck(1103, self.misra_11_3, cfg) - self.executeCheck(1104, self.misra_11_4, cfg) - self.executeCheck(1105, self.misra_11_5, cfg) - self.executeCheck(1106, self.misra_11_6, cfg) - self.executeCheck(1107, self.misra_11_7, cfg) - self.executeCheck(1108, self.misra_11_8, cfg) - self.executeCheck(1109, self.misra_11_9, cfg) - if cfgNumber == 0: - self.executeCheck(1201, self.misra_12_1_sizeof, data.rawTokens) - self.executeCheck(1201, self.misra_12_1, cfg) - self.executeCheck(1202, self.misra_12_2, cfg) - self.executeCheck(1203, self.misra_12_3, cfg) - self.executeCheck(1204, self.misra_12_4, cfg) - self.executeCheck(1301, self.misra_13_1, cfg) - self.executeCheck(1303, self.misra_13_3, cfg) - self.executeCheck(1304, self.misra_13_4, cfg) - self.executeCheck(1305, self.misra_13_5, cfg) - self.executeCheck(1306, self.misra_13_6, cfg) - self.executeCheck(1401, self.misra_14_1, cfg) - self.executeCheck(1402, self.misra_14_2, cfg) - self.executeCheck(1404, self.misra_14_4, cfg) - self.executeCheck(1501, self.misra_15_1, cfg) - self.executeCheck(1502, self.misra_15_2, cfg) - self.executeCheck(1503, self.misra_15_3, cfg) - self.executeCheck(1504, self.misra_15_4, cfg) - self.executeCheck(1505, self.misra_15_5, cfg) - if cfgNumber == 0: - self.executeCheck(1506, self.misra_15_6, data.rawTokens) - self.executeCheck(1507, self.misra_15_7, cfg) - self.executeCheck(1601, self.misra_16_1, cfg) - self.executeCheck(1602, self.misra_16_2, cfg) - if cfgNumber == 0: - self.executeCheck(1603, self.misra_16_3, data.rawTokens) - self.executeCheck(1604, self.misra_16_4, cfg) - self.executeCheck(1605, self.misra_16_5, cfg) - self.executeCheck(1606, self.misra_16_6, cfg) - self.executeCheck(1607, self.misra_16_7, cfg) - self.executeCheck(1701, self.misra_17_1, cfg) - self.executeCheck(1702, self.misra_17_2, cfg) - self.executeCheck(1703, self.misra_17_3, cfg) - self.misra_config(cfg) - if cfgNumber == 0: - self.executeCheck(1706, self.misra_17_6, data.rawTokens) - self.executeCheck(1707, self.misra_17_7, cfg) - self.executeCheck(1708, self.misra_17_8, cfg) - self.executeCheck(1804, self.misra_18_4, cfg) - self.executeCheck(1805, self.misra_18_5, cfg) - self.executeCheck(1807, self.misra_18_7, cfg) - self.executeCheck(1808, self.misra_18_8, cfg) - self.executeCheck(1902, self.misra_19_2, cfg) - self.executeCheck(2001, self.misra_20_1, cfg) - self.executeCheck(2002, self.misra_20_2, cfg) - self.executeCheck(2003, self.misra_20_3, cfg) - self.executeCheck(2004, self.misra_20_4, cfg) - self.executeCheck(2005, self.misra_20_5, cfg) - self.executeCheck(2007, self.misra_20_7, cfg) - self.executeCheck(2008, self.misra_20_8, cfg) - self.executeCheck(2009, self.misra_20_9, cfg) - self.executeCheck(2010, self.misra_20_10, cfg) - self.executeCheck(2011, self.misra_20_11, cfg) - self.executeCheck(2012, self.misra_20_12, cfg) - self.executeCheck(2013, self.misra_20_13, cfg) - self.executeCheck(2014, self.misra_20_14, cfg) - self.executeCheck(2101, self.misra_21_1, cfg) - self.executeCheck(2102, self.misra_21_2, cfg) - self.executeCheck(2103, self.misra_21_3, cfg) - self.executeCheck(2104, self.misra_21_4, cfg) - self.executeCheck(2105, self.misra_21_5, cfg) - self.executeCheck(2106, self.misra_21_6, cfg) - self.executeCheck(2107, self.misra_21_7, cfg) - self.executeCheck(2108, self.misra_21_8, cfg) - self.executeCheck(2109, self.misra_21_9, cfg) - self.executeCheck(2110, self.misra_21_10, cfg) - self.executeCheck(2111, self.misra_21_11, cfg) - self.executeCheck(2112, self.misra_21_12, cfg) - self.executeCheck(2114, self.misra_21_14, cfg) - self.executeCheck(2115, self.misra_21_15, cfg) - self.executeCheck(2116, self.misra_21_16, cfg) - self.executeCheck(2119, self.misra_21_19, cfg) - self.executeCheck(2120, self.misra_21_20, cfg) - self.executeCheck(2121, self.misra_21_21, cfg) - # 22.4 is already covered by Cppcheck writeReadOnlyFile - self.executeCheck(2205, self.misra_22_5, cfg) - self.executeCheck(2207, self.misra_22_7, cfg) - self.executeCheck(2208, self.misra_22_8, cfg) - self.executeCheck(2209, self.misra_22_9, cfg) - self.executeCheck(2210, self.misra_22_10, cfg) - - def read_ctu_info_line(self, line): - if not line.startswith('{'): - return None - try: - ctu_info = json.loads(line) - except json.decoder.JSONDecodeError: - return None - if 'summary' not in ctu_info: - return None - if 'data' not in ctu_info: - return None - return ctu_info - - def analyse_ctu_info(self, ctu_info_files): - all_typedef_info = {} - all_tagname_info = {} - all_macro_info = {} - all_external_identifiers_decl = {} - all_external_identifiers_def = {} - all_internal_identifiers = {} - all_local_identifiers = {} - all_usage_files = {} - - from cppcheckdata import Location - - def is_different_location(loc1, loc2): - return loc1['file'] != loc2['file'] or loc1['line'] != loc2['line'] - - def is_different_file(loc1, loc2): - return loc1['file'] != loc2['file'] - - try: - for filename in ctu_info_files: - for line in open(filename, 'rt'): - s = self.read_ctu_info_line(line) - if s is None: - continue - summary_type = s.get('summary', '') - summary_data = s.get('data', None) - - if summary_type == 'MisraTypedefInfo': - for new_typedef_info in summary_data: - key = new_typedef_info['name'] - existing_typedef_info = all_typedef_info.get(key, None) - if existing_typedef_info: - if is_different_location(existing_typedef_info, new_typedef_info): - self.reportError(Location(existing_typedef_info), 5, 6) - self.reportError(Location(new_typedef_info), 5, 6) - else: - existing_typedef_info['used'] = existing_typedef_info['used'] or new_typedef_info['used'] - else: - all_typedef_info[key] = new_typedef_info - - if summary_type == 'MisraTagName': - for new_tagname_info in summary_data: - key = new_tagname_info['name'] - existing_tagname_info = all_tagname_info.get(key, None) - if existing_tagname_info: - if is_different_location(existing_tagname_info, new_tagname_info): - self.reportError(Location(existing_tagname_info), 5, 7) - self.reportError(Location(new_tagname_info), 5, 7) - else: - existing_tagname_info['used'] = existing_tagname_info['used'] or new_tagname_info['used'] - else: - all_tagname_info[key] = new_tagname_info - - if summary_type == 'MisraMacro': - for new_macro in summary_data: - key = new_macro['name'] - existing_macro = all_macro_info.get(key, None) - if existing_macro: - existing_macro['used'] = existing_macro['used'] or new_macro['used'] - else: - all_macro_info[key] = new_macro - - if summary_type == 'MisraExternalIdentifiers': - for s in sorted(summary_data, key=lambda d: "%s %s %s" %(d['file'],d['line'], d['column'] )): - is_declaration = s['decl'] - if is_declaration: - all_external_identifiers = all_external_identifiers_decl - else: - all_external_identifiers = all_external_identifiers_def - - name = s['name'] - if name in all_external_identifiers: - if is_declaration and is_different_location(s, all_external_identifiers[name]): - self.reportError(Location(s), 8, 5) - self.reportError(Location(all_external_identifiers[name]), 8, 5) - elif is_different_file(s, all_external_identifiers[name]): - self.reportError(Location(s), 8, 6) - self.reportError(Location(all_external_identifiers[name]), 8, 6) - all_external_identifiers[name] = s - - if summary_type == 'MisraInternalIdentifiers': - for s in summary_data: - if s['name'] in all_internal_identifiers: - if not s['inlinefunc'] or s['file'] != all_internal_identifiers[s['name']]['file']: - self.reportError(Location(s), 5, 9) - self.reportError(Location(all_internal_identifiers[s['name']]), 5, 9) - all_internal_identifiers[s['name']] = s - - if summary_type == 'MisraLocalIdentifiers': - for s in summary_data: - all_local_identifiers[s['name']] = s - - if summary_type == 'MisraUsage': - for s in summary_data: - if s['name'] in all_usage_files: - all_usage_files[s['name']].append(s['file']) - else: - all_usage_files[s['name']] = [s['file']] - - except FileNotFoundError: - return - - unused_typedefs = [tdi for tdi in all_typedef_info.values() if not tdi['used']] - for tdi in unused_typedefs: - self.reportError(Location(tdi), 2, 3) - - unused_tags = [tag for tag in all_tagname_info.values() if not tag['used']] - for tag in unused_tags: - self.reportError(Location(tag), 2, 4) - - unused_macros = [m for m in all_macro_info.values() if not m['used']] - for m in unused_macros: - self.reportError(Location(m), 2, 5) - - all_external_identifiers = all_external_identifiers_decl - all_external_identifiers.update(all_external_identifiers_def) - for name, external_identifier in all_external_identifiers.items(): - internal_identifier = all_internal_identifiers.get(name) - if internal_identifier: - self.reportError(Location(internal_identifier), 5, 8) - self.reportError(Location(external_identifier), 5, 8) - - local_identifier = all_local_identifiers.get(name) - if local_identifier: - self.reportError(Location(local_identifier), 5, 8) - self.reportError(Location(external_identifier), 5, 8) - - for name, files in all_usage_files.items(): - #print('%s:%i' % (name, count)) - count = len(files) - if count != 1 or name not in all_external_identifiers_def: - continue - if files[0] != Location(all_external_identifiers_def[name]).file: - continue - if name in all_external_identifiers: - self.reportError(Location(all_external_identifiers[name]), 8, 7) - -RULE_TEXTS_HELP = '''Path to text file of MISRA rules - -If you have the tool 'pdftotext' you might be able -to generate this textfile with such command: - - pdftotext MISRA_C_2012.pdf MISRA_C_2012.txt - -Otherwise you can more or less copy/paste the chapter -Appendix A Summary of guidelines -from the MISRA pdf. You can buy the MISRA pdf from -http://www.misra.org.uk/ - -Format: - -<..arbitrary text..> -Appendix A Summary of guidelines -Rule 1.1 Required -Rule text for 1.1 -continuation of rule text for 1.1 -Rule 1.2 Mandatory -Rule text for 1.2 -continuation of rule text for 1.2 -<...> - -''' - -SUPPRESS_RULES_HELP = '''MISRA rules to suppress (comma-separated) - -For example, if you'd like to suppress rules 15.1, 11.3, -and 20.13, run: - - python misra.py --suppress-rules 15.1,11.3,20.13 ... - -''' - - -def get_args_parser(): - """Generates list of command-line arguments acceptable by misra.py script.""" - parser = cppcheckdata.ArgumentParser() - parser.add_argument("--rule-texts", type=str, help=RULE_TEXTS_HELP) - parser.add_argument("--verify-rule-texts", - help="Verify that all supported rules texts are present in given file and exit.", - action="store_true") - parser.add_argument("--suppress-rules", type=str, help=SUPPRESS_RULES_HELP) - parser.add_argument("--no-summary", help="Hide summary of violations", action="store_true") - parser.add_argument("--show-suppressed-rules", help="Print rule suppression list", action="store_true") - parser.add_argument("-P", "--file-prefix", type=str, help="Prefix to strip when matching suppression file rules") - parser.add_argument("-generate-table", help=argparse.SUPPRESS, action="store_true") - parser.add_argument("-verify", help=argparse.SUPPRESS, action="store_true") - parser.add_argument("--severity", type=str, help="Set a custom severity string, for example 'error' or 'warning'. ") - return parser - - -def main(): - parser = get_args_parser() - args = parser.parse_args() - settings = MisraSettings(args) - checker = MisraChecker(settings) - - checker.path_premium_addon = cppcheckdata.get_path_premium_addon() - - if args.generate_table: - generateTable() - sys.exit(0) - - # args.rule_texts = "/home/jensb/pull_requests/3d_magnetic/magnetic-3d-sensor-tlx493d/misra.txt" - if not args.rule_texts: - args.rule_texts = os.environ.get('RULE_TEXTS') - - if args.rule_texts: - filename = os.path.expanduser(args.rule_texts) - filename = os.path.normpath(filename) - checker.ruleText_filename = filename - if os.path.isfile(filename): - checker.loadRuleTexts(filename) - if args.verify_rule_texts: - checker.verifyRuleTexts() - sys.exit(0) - else: - if args.verify_rule_texts: - print('Fatal error: file is not found: ' + filename) - sys.exit(1) - - - if args.verify_rule_texts and not args.rule_texts: - print("Error: Please specify rule texts file with --rule-texts=") - sys.exit(1) - - if args.suppress_rules: - checker.setSuppressionList(args.suppress_rules) - - if args.file_prefix: - checker.setFilePrefix(args.file_prefix) - - dump_files, ctu_info_files = cppcheckdata.get_files(args) - - if (not dump_files) and (not ctu_info_files): - if not args.quiet: - print("No input files.") - sys.exit(0) - - if args.severity: - checker.setSeverity(args.severity) - - for item in dump_files: - checker.parseDump(item,checker.path_premium_addon) - - if settings.verify: - verify_expected = checker.get_verify_expected() - verify_actual = checker.get_verify_actual() - - exitCode = 0 - for expected in verify_expected: - if expected not in verify_actual: - print('Expected but not seen: ' + expected) - exitCode = 1 - for actual in verify_actual: - if actual not in verify_expected: - print('Not expected: ' + actual) - exitCode = 1 - - # Existing behavior of verify mode is to exit - # on the first un-expected output. - # TODO: Is this required? or can it be moved to after - # all input files have been processed - if exitCode != 0: - sys.exit(exitCode) - - checker.analyse_ctu_info(ctu_info_files) - - if settings.verify: - sys.exit(exitCode) - - number_of_violations = len(checker.get_violations()) - if number_of_violations > 0: - if settings.show_summary: - print("\nMISRA rules violations found:\n\t%s\n" % ( - "\n\t".join(["%s: %d" % (viol, len(checker.get_violations(viol))) for viol in - checker.get_violation_types()]))) - - rules_violated = {} - for severity, ids in checker.get_violations(): - for misra_id in ids: - rules_violated[misra_id] = rules_violated.get(misra_id, 0) + 1 - print("MISRA rules violated:") - convert = lambda text: int(text) if text.isdigit() else 0 - misra_sort = lambda key: [convert(c) for c in re.split(r'[\.-]([0-9]*)', key)] - for misra_id in sorted(rules_violated.keys(), key=misra_sort): - res = re.match(r'misra-c2012-([0-9]+)\\.([0-9]+)', misra_id) - if res is None: - num = 0 - else: - num = int(res.group(1)) * 100 + int(res.group(2)) - severity = '-' - if num in checker.ruleTexts: - severity = checker.ruleTexts[num].cppcheck_severity - print("\t%15s (%s): %d" % (misra_id, severity, rules_violated[misra_id])) - - if args.show_suppressed_rules: - checker.showSuppressedRules() - - -if __name__ == '__main__': - main() - sys.exit(cppcheckdata.EXIT_CODE) diff --git a/libraries/CAN/Makefile b/libraries/CAN/Makefile index c60d049c..d2680af1 100644 --- a/libraries/CAN/Makefile +++ b/libraries/CAN/Makefile @@ -2,10 +2,12 @@ FQBN ?= PORT ?= TESTS ?= UNITY_PATH ?= +BAUD_RATE ?= 115200 $(info FQBN : $(FQBN)) $(info PORT : $(PORT)) $(info UNITY_PATH : $(UNITY_PATH)) +$(info BAUD_RATE : $(BAUD_RATE)) TESTS_CONNECTED=-DTEST_CAN_CONNECTED @@ -110,17 +112,6 @@ endif ifeq ($(FQBN),) $(error "Must set variable FQBN in order to be able to flash Arduino sketches !") else - arduino-cli.exe monitor -c baudrate=9600 -p $(PORT) --fqbn $(FQBN) -endif - - -test-monitor: -ifeq ($(PORT),) - $(error "Must set variable PORT (Windows port naming convention, ie COM16) in order to be able to flash Arduino sketches !") -endif -ifeq ($(FQBN),) - $(error "Must set variable FQBN in order to be able to flash Arduino sketches !") -else - arduino-cli.exe monitor -c baudrate=115200 -p $(PORT) --fqbn $(FQBN) + arduino-cli.exe monitor -c baudrate=$(BAUD_RATE) -p $(PORT) --fqbn $(FQBN) endif From d33d6a3a31e680181ad6b02e856b8fa76a303171 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Mon, 14 Oct 2024 09:25:50 +0200 Subject: [PATCH 04/13] Refactor: define the CAN message object --- libraries/CAN/src/CANXMC.cpp | 46 +++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index 88220b63..821ce0f1 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -5,33 +5,35 @@ static volatile bool can_frame_received = false; /* construct with configuration of different target */ -CANXMC::CANXMC(XMC_ARD_CAN_t *conf) { +CANXMC::CANXMC(XMC_ARD_CAN_t *conf) : _XMC_CAN_config(conf) { + memset(&CAN_msg_rx, 0, sizeof(CAN_msg_rx)); + memset(&CAN_msg_tx, 0, sizeof(CAN_msg_tx)); + + /* CAN Transmit Message Object definition */ + + CAN_msg_tx.can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO1; + CAN_msg_tx.can_identifier = 0xFFFU; + CAN_msg_tx.can_id_mode = XMC_CAN_FRAME_TYPE_STANDARD_11BITS; // Extended ID mode + CAN_msg_tx.can_priority = XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1; // Priority level 1 + CAN_msg_tx.can_id_mask = 0x00000000U; // Identifier mask + CAN_msg_tx.can_ide_mask = 0U; // Identifier extension mask + CAN_msg_tx.can_data_length = 0U; // Data length of 0 + CAN_msg_tx.can_mo_type = XMC_CAN_MO_TYPE_TRANSMSGOBJ; // Message Object type + - _XMC_CAN_config = conf; /* CAN Receive Message Object definition, can also be used for transmit remote * frame*/ - CAN_msg_tx = { - .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO1, - {0xFFFU, - XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode - XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} - {0x1FFFFFFFU, 0U}, // {can_id_mask, can_ide_mask} - .can_data_length = 0U, - .can_mo_type = XMC_CAN_MO_TYPE_TRANSMSGOBJ, - }; - - /* CAN Transmit Message Object definition */ - CAN_msg_rx = { - .can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO0, - {0xFFFU, - XMC_CAN_FRAME_TYPE_STANDARD_11BITS, // {can_identifier, can_id_mode - XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1}, // can_priority} - {0x00000000U, 0U}, // {can_id_mask, can_ide_mask} - .can_data_length = 0U, - .can_mo_type = XMC_CAN_MO_TYPE_RECMSGOBJ, - }; + CAN_msg_rx.can_mo_ptr = (CAN_MO_TypeDef *)CAN_MO0; + CAN_msg_rx.can_identifier = 0xFFFU; + CAN_msg_rx.can_id_mode = XMC_CAN_FRAME_TYPE_STANDARD_11BITS; + CAN_msg_rx.can_priority = XMC_CAN_ARBITRATION_MODE_ORDER_BASED_PRIO_1; + CAN_msg_rx.can_id_mask = 0x00000000U; + CAN_msg_rx.can_ide_mask = 0U; + CAN_msg_rx.can_data_length = 0U; + CAN_msg_rx.can_mo_type = XMC_CAN_MO_TYPE_RECMSGOBJ; } + CANXMC::~CANXMC() { end(); } /* Getter function for CAN_msg_tx */ From 007038ec548aa5ae3c86ed804c251f445a6d399f Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Mon, 21 Oct 2024 16:10:35 +0200 Subject: [PATCH 05/13] checkpoint: CAN connect test v1 --- libraries/CAN/Makefile | 4 +- libraries/CAN/Makefile.test | 12 ++- .../src/features/Test_CAN_connected_node1.hpp | 66 +++++++++++++++++ .../src/features/Test_CAN_connected_node2.hpp | 74 +++++++++++++++++++ .../corelibs/Test_CAN_connected_node1.cpp | 34 +++++++++ .../corelibs/Test_CAN_connected_node2.cpp | 44 +++++++++++ .../framework/arduino/corelibs/Test_main.ino | 12 +++ 7 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp create mode 100644 libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp create mode 100644 libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp diff --git a/libraries/CAN/Makefile b/libraries/CAN/Makefile index d2680af1..47afeaa9 100644 --- a/libraries/CAN/Makefile +++ b/libraries/CAN/Makefile @@ -15,7 +15,9 @@ TESTS_NOT_CONNECTED=-DTEST_CAN CAN: TESTS=-DTEST_CAN CAN_connected: TESTS=-DTEST_CAN -DTEST_CAN_CONNECTED -CAN_connected CAN: unity_corelibs flash +CAN_connected_node1: TESTS=-DTEST_CAN_CONNECTED_NODE1 +CAN_connected_node2: TESTS=-DTEST_CAN_CONNECTED_NODE2 +CAN_connected CAN CAN_connected_node1 CAN_connected_node2: unity_corelibs flash test_all: TESTS=$(TESTS_CONNECTED) $(TESTS_NOT_CONNECTED) test_connected: TESTS=$(TESTS_CONNECTED) diff --git a/libraries/CAN/Makefile.test b/libraries/CAN/Makefile.test index 00762480..55d033f4 100644 --- a/libraries/CAN/Makefile.test +++ b/libraries/CAN/Makefile.test @@ -12,18 +12,22 @@ make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM24 CANSender flash monitor make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM24 CANLoopBack flash monitor ## XMC1400_XMC2GO -make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM41 CANReceiver flash monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO BAUD_RATE=9600 PORT=COM41 CANReceiver flash monitor -make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM41 CANReceiverCallback flash monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO BAUD_RATE=9600 PORT=COM41 CANReceiverCallback flash monitor -make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 CANSender flash monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO BAUD_RATE=9600 PORT=COM42 CANSender flash monitor -make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 CANLoopBack flash monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO BAUD_RATE=9600 PORT=COM42 CANLoopBack flash monitor ### Unit tests make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN test-monitor +make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN_connected_node2 monitor + +make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM41 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN_connected_node1 monitor + make FQBN=Infineon:xmc:XMC1400_XMC2GO PORT=COM42 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN_connected monitor make FQBN=Infineon:xmc:XMC4700_Relax_Kit PORT=COM20 UNITY_PATH=/home/zhanglinjing/repo/Unity CAN test-monitor \ No newline at end of file diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp new file mode 100644 index 00000000..ffef29f8 --- /dev/null +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp @@ -0,0 +1,66 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +void CAN_connected_node1_suiteSetup(void); +void CAN_connected_node1_suiteTearDown(void); + + +// variables used in the tests below that have to be accessed in the setup and tear down methods + + +// test includes that may require dut + + +// define test group name +TEST_GROUP(CAN_connected_node1); +TEST_GROUP(CAN_connected_node1Internal); + + +// Setup method called by Unity before every individual test defined for this test group. +static TEST_SETUP(CAN_connected_node1Internal) +{ +} + + +// Tear down method called by Unity after every individual test defined for this test group. +static TEST_TEAR_DOWN(CAN_connected_node1Internal) +{ +} + +static bool node1Send(const void *data, const uint8_t size, bool isChar = false) { + CAN.beginPacket(node1_id); + + TEST_ASSERT_EQUAL_UINT8(size, CAN.write(static_cast(data), size)); + CAN.endPacket(); + // TEST_ASSERT_EQUAL_UINT8(0U, CAN.endPacket()); + return true; +} + +// Define tests for supported common functionality. These should return true to indicate this. +TEST_IFX(CAN_connected_node1Internal, node1Sending) +{ + TEST_ASSERT_TRUE(node1Send(node1data, 5)); +} + + +static TEST_GROUP_RUNNER(CAN_connected_node1Internal) +{ + RUN_TEST_CASE(CAN_connected_node1Internal, node1Sending); + +} + + +// Bundle all tests to be executed for this test group +TEST_GROUP_RUNNER(CAN_connected_node1) +{ + CAN_connected_node1_suiteSetup(); + + RUN_TEST_GROUP(CAN_connected_node1Internal); + + CAN_connected_node1_suiteTearDown(); +} diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp new file mode 100644 index 00000000..d8fd4094 --- /dev/null +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp @@ -0,0 +1,74 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +void CAN_connected_node2_suiteSetup(void); +void CAN_connected_node2_suiteTearDown(void); + + +// variables used in the tests below that have to be accessed in the setup and tear down methods + + +// test includes that may require dut + + +// define test group name +TEST_GROUP(CAN_connected_node2); +TEST_GROUP(CAN_connected_node2Internal); + + +// Setup method called by Unity before every individual test defined for this test group. +static TEST_SETUP(CAN_connected_node2Internal) +{ +} + + +// Tear down method called by Unity after every individual test defined for this test group. +static TEST_TEAR_DOWN(CAN_connected_node2Internal) +{ +}; +static bool node2Send(const void *data, const uint8_t size, bool isChar = false) { + CAN.beginPacket(node2_id); + + TEST_ASSERT_EQUAL_UINT8(size, CAN.write(static_cast(data), size)); + CAN.endPacket(); + // TEST_ASSERT_EQUAL_UINT8(0U, CAN.endPacket()); + return true; +} + +static bool node2Receive(const void *data, const uint8_t size, bool isChar = false) { + CAN.beginPacket(node2_id); + + TEST_ASSERT_EQUAL_UINT8(size, CAN.write(static_cast(data), size)); + CAN.endPacket(); + // TEST_ASSERT_EQUAL_UINT8(0U, CAN.endPacket()); + return true; +} + +// Define tests for supported common functionality. These should return true to indicate this. +TEST_IFX(CAN_connected_node2Internal, node2Sending) +{ + TEST_ASSERT_TRUE(node2Receive(node2data, 5)); +} + + +static TEST_GROUP_RUNNER(CAN_connected_node2Internal) +{ + RUN_TEST_CASE(CAN_connected_node2Internal, node2Sending); + +} + + +// Bundle all tests to be executed for this test group +TEST_GROUP_RUNNER(CAN_connected_node2) +{ + CAN_connected_node2_suiteSetup(); + + RUN_TEST_GROUP(CAN_connected_node2Internal); + + CAN_connected_node2_suiteTearDown(); +} diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp new file mode 100644 index 00000000..79d92fd2 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp @@ -0,0 +1,34 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +// defines +#define TRACE_OUTPUT + + +// variables +static uint8_t node1Data[8]; +static uint8_t node1Increment = 1; +static uint8_t node2Increment = 10; +static uint8_t globalQuantity = 8; + + +// test feature includes requiring the above defined variables +#include "Test_CAN_connected_node1.hpp" + + +// Method invoked before a test suite is run. +void CAN_connected_node1_suiteSetup() { + extern CANXMC CAN; + CAN.begin(); +} + + +// Method invoked after a test suite is run. +void CAN_connected_node1_suiteTearDown() { + CAN.end(); +} diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp new file mode 100644 index 00000000..4ee6e460 --- /dev/null +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp @@ -0,0 +1,44 @@ +// std includes + +// test includes +#include "Test_includes.hpp" + +// project includes + + +// defines +#define TRACE_OUTPUT + +// variables +const static int node1_id = 0x01; +const static uint8_t node1Increment = 1U; +static char node1data[5] = {'h', 'e', 'l', 'l', 'o'}; + +const static int node2_id = 0x02; +const static uint8_t node2Increment = 2U; +static char node2data[5] = {'w', 'o', 'r', 'l', 'd'}; + +// test feature includes requiring the above defined variables +#include "Test_CAN_connected_node2.hpp" + + +static void ReceiveEvent(int packetSize) { + while (CAN.available()) { + Serial.print((char)CAN.read()); + } + Serial.println(); +} + +// Method invoked before a test suite is run. +void CAN_connected_node2_suiteSetup() { + extern CANXMC CAN; + CAN.begin(); + CAN.onReceive(ReceiveEvent); +} + + +// Method invoked after a test suite is run. +void CAN_connected_node2_suiteTearDown() { + CAN.end(); +} + diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino index 80e9eaf7..fd2e9223 100644 --- a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_main.ino @@ -26,6 +26,18 @@ void RunAllTests(void) #endif +#ifdef TEST_CAN_CONNECTED_NODE1 + + RUN_TEST_GROUP(CAN_connected_node1); + +#endif + +#ifdef TEST_CAN_CONNECTED_NODE2 + + RUN_TEST_GROUP(CAN_connected_node2); + +#endif + } From 093588b55d2846622046d77392163cc1f9fbab09 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 10:41:52 +0200 Subject: [PATCH 06/13] test: can pingpong test v1 --- libraries/CAN/src/CANXMC.cpp | 4 +- .../src/features/Test_CAN_connected_node1.hpp | 40 +++++++++----- .../src/features/Test_CAN_connected_node2.hpp | 52 +++++++++++-------- .../corelibs/Test_CAN_connected_node1.cpp | 22 +++++--- .../corelibs/Test_CAN_connected_node2.cpp | 39 ++++++++------ 5 files changed, 98 insertions(+), 59 deletions(-) diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index 821ce0f1..512f85f9 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -359,7 +359,7 @@ int CANXMC::loopback() { * @return true if the CAN module is successfully put into sleep mode, false * otherwise. */ -int CANXMC::sleep() { +bool CANXMC::sleep() { CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0; }; @@ -370,7 +370,7 @@ int CANXMC::sleep() { * the CLC register. * @return true if the CAN module is successfully woken up, false otherwise. */ -int CANXMC::wakeup() { +bool CANXMC::wakeup() { CAN_xmc->CLC &= CAN_CLC_EDIS_Msk; return CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0; }; diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp index ffef29f8..e4367f14 100644 --- a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp @@ -26,32 +26,48 @@ static TEST_SETUP(CAN_connected_node1Internal) { } - // Tear down method called by Unity after every individual test defined for this test group. static TEST_TEAR_DOWN(CAN_connected_node1Internal) { } -static bool node1Send(const void *data, const uint8_t size, bool isChar = false) { - CAN.beginPacket(node1_id); +void testSendReceive() +{ - TEST_ASSERT_EQUAL_UINT8(size, CAN.write(static_cast(data), size)); + CAN.beginPacket(CAN_ID_1); + for (uint8_t i = 0; i < globalQuantity; ++i) { + CAN.write(node1Data[i]); + } CAN.endPacket(); - // TEST_ASSERT_EQUAL_UINT8(0U, CAN.endPacket()); - return true; +#ifdef TRACE_OUTPUT + printArray("Sent Data", node1Data, globalQuantity); +#endif + while (!newDataReceivedNode1) { + } + + if (newDataReceivedNode1) { + for (uint8_t i = 0; i < globalQuantity; ++i) { + UNITY_TEST_ASSERT_EQUAL_UINT8(node1Data[i] + node1Increment, receivedData[i], __LINE__, "Data mismatch"); + } + newDataReceivedNode1 = false; + +#ifdef TRACE_OUTPUT + printArray("Sent Data", node1Data, globalQuantity); + printArray("Received Data", receivedData, globalQuantity); +#endif + } } -// Define tests for supported common functionality. These should return true to indicate this. -TEST_IFX(CAN_connected_node1Internal, node1Sending) + +TEST_IFX(CAN_connected_node1Internal, checkPingPong) { - TEST_ASSERT_TRUE(node1Send(node1data, 5)); + testSendReceive(); } -static TEST_GROUP_RUNNER(CAN_connected_node1Internal) +static TEST_GROUP_RUNNER(CAN_connected_node1Internal) { - RUN_TEST_CASE(CAN_connected_node1Internal, node1Sending); - + RUN_TEST_CASE(CAN_connected_node1Internal, checkPingPong); } diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp index d8fd4094..cdfc2cc3 100644 --- a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp @@ -20,7 +20,29 @@ void CAN_connected_node2_suiteTearDown(void); TEST_GROUP(CAN_connected_node2); TEST_GROUP(CAN_connected_node2Internal); - +void processReceivedMessagesNode2() +{ + if (newDataReceivedNode2) { + // Process the received data + for (uint8_t i = 0; i < globalQuantity; ++i) { + node2Data[i] = receivedData[i] + node2Increment; + } + + // Send processed data back to Node1 + CAN.beginPacket(CAN_ID_2); + for (uint8_t i = 0; i < globalQuantity; ++i) { + CAN.write(node2Data[i]); + } + CAN.endPacket(); + + // Clear flag + newDataReceivedNode2 = false; + +#ifdef TRACE_OUTPUT + printArray("Processed Data", node2Data, globalQuantity); +#endif + } +} // Setup method called by Unity before every individual test defined for this test group. static TEST_SETUP(CAN_connected_node2Internal) { @@ -30,35 +52,21 @@ static TEST_SETUP(CAN_connected_node2Internal) // Tear down method called by Unity after every individual test defined for this test group. static TEST_TEAR_DOWN(CAN_connected_node2Internal) { -}; -static bool node2Send(const void *data, const uint8_t size, bool isChar = false) { - CAN.beginPacket(node2_id); - - TEST_ASSERT_EQUAL_UINT8(size, CAN.write(static_cast(data), size)); - CAN.endPacket(); - // TEST_ASSERT_EQUAL_UINT8(0U, CAN.endPacket()); - return true; } -static bool node2Receive(const void *data, const uint8_t size, bool isChar = false) { - CAN.beginPacket(node2_id); - - TEST_ASSERT_EQUAL_UINT8(size, CAN.write(static_cast(data), size)); - CAN.endPacket(); - // TEST_ASSERT_EQUAL_UINT8(0U, CAN.endPacket()); - return true; -} - -// Define tests for supported common functionality. These should return true to indicate this. -TEST_IFX(CAN_connected_node2Internal, node2Sending) +TEST_IFX(CAN_connected_node2Internal, checkPingPong) { - TEST_ASSERT_TRUE(node2Receive(node2data, 5)); + processReceivedMessagesNode2(); + +#ifdef TRACE_OUTPUT + printArray("\nNode2 data", node2Data, globalQuantity); +#endif } static TEST_GROUP_RUNNER(CAN_connected_node2Internal) { - RUN_TEST_CASE(CAN_connected_node2Internal, node2Sending); + RUN_TEST_CASE(CAN_connected_node2Internal, checkPingPong); } diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp index 79d92fd2..0b64561e 100644 --- a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp @@ -8,23 +8,33 @@ // defines #define TRACE_OUTPUT - +#define CAN_ID_1 0x123 +#define CAN_ID_2 0x321 // variables -static uint8_t node1Data[8]; +static uint8_t node1Data[8] = {0, 1, 2, 3, 4, 5, 6, 7}; static uint8_t node1Increment = 1; -static uint8_t node2Increment = 10; static uint8_t globalQuantity = 8; +volatile bool newDataReceivedNode1 = false; +static uint8_t receivedData[8]; // test feature includes requiring the above defined variables #include "Test_CAN_connected_node1.hpp" - - +extern CANXMC CAN; + +void receiveEventNode1(int packetSize) { + uint8_t count = 0; + while (CAN.available() && count < 8) { + receivedData[count++] = CAN.read(); + } + newDataReceivedNode1 = true; +} // Method invoked before a test suite is run. void CAN_connected_node1_suiteSetup() { - extern CANXMC CAN; CAN.begin(); + CAN.filter(CAN_ID_2, 0x7FF); // Set filter to receive messages with CAN_ID_2 + CAN.onReceive(receiveEventNode1); } diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp index 4ee6e460..7576c8c8 100644 --- a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp @@ -8,37 +8,42 @@ // defines #define TRACE_OUTPUT +#define CAN_ID_1 0x123 +#define CAN_ID_2 0x321 // variables -const static int node1_id = 0x01; -const static uint8_t node1Increment = 1U; -static char node1data[5] = {'h', 'e', 'l', 'l', 'o'}; +static uint8_t node2Data[8]; +static uint8_t node2Increment = 1; +static uint8_t globalQuantity = 8; -const static int node2_id = 0x02; -const static uint8_t node2Increment = 2U; -static char node2data[5] = {'w', 'o', 'r', 'l', 'd'}; +volatile bool newDataReceivedNode2 = false; +static uint8_t receivedData[8]; // test feature includes requiring the above defined variables #include "Test_CAN_connected_node2.hpp" - - -static void ReceiveEvent(int packetSize) { - while (CAN.available()) { - Serial.print((char)CAN.read()); - } - Serial.println(); +extern CANXMC CAN; + +void receiveEventNode2(int packetSize) +{ + uint8_t count = 0; + while (CAN.available() && count < 8) { + receivedData[count++] = CAN.read(); + } + newDataReceivedNode2 = true; } // Method invoked before a test suite is run. -void CAN_connected_node2_suiteSetup() { - extern CANXMC CAN; +void CAN_connected_node2_suiteSetup() +{ CAN.begin(); - CAN.onReceive(ReceiveEvent); + CAN.filter(CAN_ID_1, 0x7FF); // Set filter to receive messages with CAN_ID_1 + CAN.onReceive(receiveEventNode2); } // Method invoked after a test suite is run. -void CAN_connected_node2_suiteTearDown() { +void CAN_connected_node2_suiteTearDown() +{ CAN.end(); } From 7d9207166de1f4cf8153f949fc768d5548f68e05 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 11:06:32 +0200 Subject: [PATCH 07/13] Fix: CAN: remove report files for code check and fix wakeup function --- .gitignore | 6 +- libraries/CAN/cppcheck.checkers | 225 ----------------- libraries/CAN/err.xml | 418 -------------------------------- libraries/CAN/src/CANXMC.cpp | 6 +- 4 files changed, 8 insertions(+), 647 deletions(-) delete mode 100644 libraries/CAN/cppcheck.checkers delete mode 100644 libraries/CAN/err.xml diff --git a/.gitignore b/.gitignore index 7d34a051..5a0dda69 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,9 @@ build # Code-check reports clang-tidy-report.txt + scan-build-reports/ -cppcheck_reports/ \ No newline at end of file + +cppcheck_reports/ +cppcheck.checkers +err.xml \ No newline at end of file diff --git a/libraries/CAN/cppcheck.checkers b/libraries/CAN/cppcheck.checkers deleted file mode 100644 index 203c1217..00000000 --- a/libraries/CAN/cppcheck.checkers +++ /dev/null @@ -1,225 +0,0 @@ -Critical errors ---------------- -No critical errors, all files were checked. -Important: Analysis is still not guaranteed to be 'complete' it is possible there are false negatives. - - -Open source checkers --------------------- -Yes Check64BitPortability::pointerassignment -Yes CheckAssert::assertWithSideEffects -Yes CheckAutoVariables::assignFunctionArg -Yes CheckAutoVariables::autoVariables -Yes CheckAutoVariables::checkVarLifetime -Yes CheckBool::checkAssignBoolToFloat -Yes CheckBool::checkAssignBoolToPointer -Yes CheckBool::checkBitwiseOnBoolean -Yes CheckBool::checkComparisonOfBoolExpressionWithInt -Yes CheckBool::checkComparisonOfBoolWithBool -Yes CheckBool::checkComparisonOfBoolWithInt -Yes CheckBool::checkComparisonOfFuncReturningBool -Yes CheckBool::checkIncrementBoolean -Yes CheckBool::pointerArithBool -Yes CheckBool::returnValueOfFunctionReturningBool -Yes CheckBoost::checkBoostForeachModification -Yes CheckBufferOverrun::analyseWholeProgram -Yes CheckBufferOverrun::argumentSize -Yes CheckBufferOverrun::arrayIndex -Yes CheckBufferOverrun::arrayIndexThenCheck -Yes CheckBufferOverrun::bufferOverflow -Yes CheckBufferOverrun::negativeArraySize -Yes CheckBufferOverrun::objectIndex -Yes CheckBufferOverrun::pointerArithmetic -Yes CheckBufferOverrun::stringNotZeroTerminated -Yes CheckClass::analyseWholeProgram -Yes CheckClass::checkConst -Yes CheckClass::checkConstructors -Yes CheckClass::checkCopyConstructors -Yes CheckClass::checkDuplInheritedMembers -Yes CheckClass::checkExplicitConstructors -Yes CheckClass::checkMemset -Yes CheckClass::checkMissingOverride -Yes CheckClass::checkReturnByReference -Yes CheckClass::checkSelfInitialization -Yes CheckClass::checkThisUseAfterFree -No CheckClass::checkUnsafeClassRefMember require:warning,safeChecks -Yes CheckClass::checkUselessOverride -Yes CheckClass::checkVirtualFunctionCallInConstructor -Yes CheckClass::initializationListUsage -Yes CheckClass::initializerListOrder -Yes CheckClass::operatorEqRetRefThis -Yes CheckClass::operatorEqToSelf -Yes CheckClass::privateFunctions -Yes CheckClass::thisSubtraction -Yes CheckClass::virtualDestructor -Yes CheckCondition::alwaysTrueFalse -Yes CheckCondition::assignIf -Yes CheckCondition::checkAssignmentInCondition -Yes CheckCondition::checkBadBitmaskCheck -No CheckCondition::checkCompareValueOutOfTypeRange require:style,platform -Yes CheckCondition::checkDuplicateConditionalAssign -Yes CheckCondition::checkIncorrectLogicOperator -Yes CheckCondition::checkInvalidTestForOverflow -Yes CheckCondition::checkModuloAlwaysTrueFalse -Yes CheckCondition::checkPointerAdditionResultNotNull -Yes CheckCondition::clarifyCondition -Yes CheckCondition::comparison -Yes CheckCondition::duplicateCondition -Yes CheckCondition::multiCondition -Yes CheckCondition::multiCondition2 -Yes CheckExceptionSafety::checkCatchExceptionByValue -Yes CheckExceptionSafety::checkRethrowCopy -Yes CheckExceptionSafety::deallocThrow -Yes CheckExceptionSafety::destructors -Yes CheckExceptionSafety::nothrowThrows -Yes CheckExceptionSafety::rethrowNoCurrentException -Yes CheckExceptionSafety::unhandledExceptionSpecification -Yes CheckFunctions::checkIgnoredReturnValue -Yes CheckFunctions::checkMathFunctions -Yes CheckFunctions::checkMissingReturn -Yes CheckFunctions::checkProhibitedFunctions -Yes CheckFunctions::invalidFunctionUsage -Yes CheckFunctions::memsetInvalid2ndParam -Yes CheckFunctions::memsetZeroBytes -Yes CheckFunctions::returnLocalStdMove -Yes CheckFunctions::useStandardLibrary -Yes CheckIO::checkCoutCerrMisusage -Yes CheckIO::checkFileUsage -Yes CheckIO::checkWrongPrintfScanfArguments -Yes CheckIO::invalidScanf -Yes CheckLeakAutoVar::check -Yes CheckMemoryLeakInClass::check -Yes CheckMemoryLeakInFunction::checkReallocUsage -Yes CheckMemoryLeakNoVar::check -Yes CheckMemoryLeakNoVar::checkForUnsafeArgAlloc -Yes CheckMemoryLeakStructMember::check -Yes CheckNullPointer::analyseWholeProgram -Yes CheckNullPointer::arithmetic -Yes CheckNullPointer::nullConstantDereference -Yes CheckNullPointer::nullPointer -Yes CheckOther::checkAccessOfMovedVariable -Yes CheckOther::checkCastIntToCharAndBack -Yes CheckOther::checkCharVariable -Yes CheckOther::checkComparePointers -Yes CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse -Yes CheckOther::checkConstPointer -Yes CheckOther::checkConstVariable -Yes CheckOther::checkDuplicateBranch -Yes CheckOther::checkDuplicateExpression -Yes CheckOther::checkEvaluationOrder -Yes CheckOther::checkFuncArgNamesDifferent -Yes CheckOther::checkIncompleteArrayFill -Yes CheckOther::checkIncompleteStatement -No CheckOther::checkInterlockedDecrement require:windows-platform -Yes CheckOther::checkInvalidFree -Yes CheckOther::checkKnownArgument -Yes CheckOther::checkKnownPointerToBool -Yes CheckOther::checkMisusedScopedObject -Yes CheckOther::checkModuloOfOne -Yes CheckOther::checkNanInArithmeticExpression -Yes CheckOther::checkNegativeBitwiseShift -Yes CheckOther::checkOverlappingWrite -Yes CheckOther::checkPassByReference -Yes CheckOther::checkRedundantAssignment -Yes CheckOther::checkRedundantCopy -Yes CheckOther::checkRedundantPointerOp -Yes CheckOther::checkShadowVariables -Yes CheckOther::checkSignOfUnsignedVariable -Yes CheckOther::checkSuspiciousCaseInSwitch -Yes CheckOther::checkSuspiciousSemicolon -Yes CheckOther::checkUnreachableCode -Yes CheckOther::checkUnusedLabel -Yes CheckOther::checkVarFuncNullUB -Yes CheckOther::checkVariableScope -Yes CheckOther::checkZeroDivision -Yes CheckOther::clarifyCalculation -Yes CheckOther::clarifyStatement -Yes CheckOther::invalidPointerCast -Yes CheckOther::redundantBitwiseOperationInSwitch -Yes CheckOther::warningOldStylePointerCast -Yes CheckPostfixOperator::postfixOperator -Yes CheckSizeof::checkSizeofForArrayParameter -Yes CheckSizeof::checkSizeofForNumericParameter -Yes CheckSizeof::checkSizeofForPointerSize -Yes CheckSizeof::sizeofCalculation -Yes CheckSizeof::sizeofFunction -Yes CheckSizeof::sizeofVoid -Yes CheckSizeof::sizeofsizeof -Yes CheckSizeof::suspiciousSizeofCalculation -Yes CheckStl::checkDereferenceInvalidIterator -Yes CheckStl::checkDereferenceInvalidIterator2 -Yes CheckStl::checkFindInsert -Yes CheckStl::checkMutexes -Yes CheckStl::erase -Yes CheckStl::eraseIteratorOutOfBounds -Yes CheckStl::if_find -Yes CheckStl::invalidContainer -Yes CheckStl::iterators -Yes CheckStl::knownEmptyContainer -Yes CheckStl::misMatchingContainerIterator -Yes CheckStl::misMatchingContainers -Yes CheckStl::missingComparison -Yes CheckStl::negativeIndex -Yes CheckStl::outOfBounds -Yes CheckStl::outOfBoundsIndexExpression -Yes CheckStl::redundantCondition -No CheckStl::size require:performance,c++03 -Yes CheckStl::stlBoundaries -Yes CheckStl::stlOutOfBounds -Yes CheckStl::string_c_str -Yes CheckStl::useStlAlgorithm -Yes CheckStl::uselessCalls -Yes CheckString::checkAlwaysTrueOrFalseStringCompare -Yes CheckString::checkIncorrectStringCompare -Yes CheckString::checkSuspiciousStringCompare -Yes CheckString::overlappingStrcmp -Yes CheckString::sprintfOverlappingData -Yes CheckString::strPlusChar -Yes CheckString::stringLiteralWrite -Yes CheckType::checkFloatToIntegerOverflow -Yes CheckType::checkIntegerOverflow -Yes CheckType::checkLongCast -Yes CheckType::checkSignConversion -Yes CheckType::checkTooBigBitwiseShift -Yes CheckUninitVar::check -Yes CheckUninitVar::valueFlowUninit -Yes CheckUnusedFunctions::check -Yes CheckUnusedVar::checkFunctionVariableUsage -Yes CheckUnusedVar::checkStructMemberUsage -Yes CheckVaarg::va_list_usage -Yes CheckVaarg::va_start_argument - - -Premium checkers ----------------- -Not available, Cppcheck Premium is not used - - -Autosar -------- -Not available, Cppcheck Premium is not used - - -Cert C ------- -Not available, Cppcheck Premium is not used - - -Cert C++ --------- -Not available, Cppcheck Premium is not used - - -Misra C -------- -Misra is not enabled - - -Misra C++ 2008 --------------- -Not available, Cppcheck Premium is not used - - -Misra C++ 2023 --------------- -Not available, Cppcheck Premium is not used diff --git a/libraries/CAN/err.xml b/libraries/CAN/err.xml deleted file mode 100644 index fc09c501..00000000 --- a/libraries/CAN/err.xml +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - - - - CANControllerClass::_txData - - - - CANControllerClass::_rxData - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CAN_msg_tx - - - - CAN_msg_rx - - - - CANXMC - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - beginPacket - - - - beginExtendedPacket - - - - packetId - - - - packetExtended - - - - packetRtr - - - - packetDlc - - - - getTxMessage - - - - getRxMessage - - - - begin - - - - observe - - - - loopback - - - - sleep - - - - wakeup - - - - diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index 512f85f9..5f00b6db 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -359,7 +359,7 @@ int CANXMC::loopback() { * @return true if the CAN module is successfully put into sleep mode, false * otherwise. */ -bool CANXMC::sleep() { +int CANXMC::sleep() { CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0; }; @@ -370,8 +370,8 @@ bool CANXMC::sleep() { * the CLC register. * @return true if the CAN module is successfully woken up, false otherwise. */ -bool CANXMC::wakeup() { - CAN_xmc->CLC &= CAN_CLC_EDIS_Msk; +int CANXMC::wakeup() { + CAN_xmc->CLC &= ~CAN_CLC_EDIS_Msk; return CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0; }; From 5194027ae4bfd5d1053fee798b8807eb866937e2 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 13:34:30 +0200 Subject: [PATCH 08/13] Fix: remove arduino template and modify makefile for CAN accorfing misra.json config gor cppcheck --- config/arduinoLibraryTemplate/LICENSE.md | 0 config/arduinoLibraryTemplate/README.md | 0 config/arduinoLibraryTemplate/keywords.txt | 0 config/arduinoLibraryTemplate/library.json | 0 config/arduinoLibraryTemplate/library.properties | 10 ---------- config/arduinoLibraryTemplate/src/dummy.txt | 0 libraries/CAN/Makefile.codechecks.mk | 10 +++++----- 7 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 config/arduinoLibraryTemplate/LICENSE.md delete mode 100644 config/arduinoLibraryTemplate/README.md delete mode 100644 config/arduinoLibraryTemplate/keywords.txt delete mode 100644 config/arduinoLibraryTemplate/library.json delete mode 100644 config/arduinoLibraryTemplate/library.properties delete mode 100644 config/arduinoLibraryTemplate/src/dummy.txt diff --git a/config/arduinoLibraryTemplate/LICENSE.md b/config/arduinoLibraryTemplate/LICENSE.md deleted file mode 100644 index e69de29b..00000000 diff --git a/config/arduinoLibraryTemplate/README.md b/config/arduinoLibraryTemplate/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/config/arduinoLibraryTemplate/keywords.txt b/config/arduinoLibraryTemplate/keywords.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/config/arduinoLibraryTemplate/library.json b/config/arduinoLibraryTemplate/library.json deleted file mode 100644 index e69de29b..00000000 diff --git a/config/arduinoLibraryTemplate/library.properties b/config/arduinoLibraryTemplate/library.properties deleted file mode 100644 index 7ed21a58..00000000 --- a/config/arduinoLibraryTemplate/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=CAN -version=1.0.0 -author=Infineon Technologies -maintainer=Infineon Technologies -sentence=C++ CAN library -paragraph=C++ CAN library -category=Library -url=https://github.com/Infineon/ -architectures=* -includes=CAN.hpp \ No newline at end of file diff --git a/config/arduinoLibraryTemplate/src/dummy.txt b/config/arduinoLibraryTemplate/src/dummy.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/libraries/CAN/Makefile.codechecks.mk b/libraries/CAN/Makefile.codechecks.mk index fb8669bb..6c13ae28 100644 --- a/libraries/CAN/Makefile.codechecks.mk +++ b/libraries/CAN/Makefile.codechecks.mk @@ -53,9 +53,9 @@ cppcheck: export RULE_TEXTS=$(CONFIG_DIR)/cppcheck/misra.txt $(CPPCHECK_PATH)/build/bin/cppcheck -i build -i examples -i test \ -I$(C_CPP_SOURCES) \ - --checkers-report=cppcheck.checkers --check-level=exhaustive --xml --enable=all --inconclusive \ - --addon=$(CONFIG_DIR)/cppcheck/misra_local.py --addon=misc \ - --max-configs=100 ./ 2> ./err.xml - $(CPPCHECK_PATH)/htmlreport/cppcheck-htmlreport --file=err.xml --title=TLx493D --report-dir=cppcheck_reports --source-dir=. - firefox cppcheck_reports/index.html + --check-level=exhaustive --enable=all --inconclusive \ + --addon=config/cppcheck/misra.json --addon=misc --std=c++20 \ + --checkers-report=cppcheck.checkers --xml --max-configs=50 2> ./err.xml + $(CPPCHECK_PATH)/htmlreport/cppcheck-htmlreport --file=err.xml --title=TestCPPCheck --report-dir=cppcheck-reports --source-dir=. + firefox cppcheck-reports/index.html From ba7e1a1508bec581a350f5ebab2404083539c0eb Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 13:42:25 +0200 Subject: [PATCH 09/13] fix: return value for CAN.sleep() and CAN.wakeup() --- libraries/CAN/src/CANXMC.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index 5f00b6db..b2e960c5 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -356,23 +356,31 @@ int CANXMC::loopback() { * Puts the CAN module into sleep mode. * This function disables the CAN module by setting the EDIS bit in the CLC * register. - * @return true if the CAN module is successfully put into sleep mode, false + * @return 1 if the CAN module is successfully put into sleep mode, 0 * otherwise. */ int CANXMC::sleep() { CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; - return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0; + if (CAN_xmc->CLC & CAN_CLC_EDIS_Msk) { + return 1; + } else { + return 0; + } }; /** * @brief Wakes up the CAN module. * This function is used to wake up the CAN module by clearing the EDIS bit in * the CLC register. - * @return true if the CAN module is successfully woken up, false otherwise. + * @return 1 if the CAN module is successfully woken up, 0 otherwise. */ int CANXMC::wakeup() { CAN_xmc->CLC &= ~CAN_CLC_EDIS_Msk; - return CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0; + if (CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0) { + return 1; + } else { + return 0; + } }; /** From 893d92a7a1f7fa11fc8bac37ac935a10afcd8989 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 17:05:17 +0200 Subject: [PATCH 10/13] Refact: elagant way to return value in CAN.wakeup() and sleep() --- libraries/CAN/src/CANXMC.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index b2e960c5..e0b96747 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -361,11 +361,7 @@ int CANXMC::loopback() { */ int CANXMC::sleep() { CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; - if (CAN_xmc->CLC & CAN_CLC_EDIS_Msk) { - return 1; - } else { - return 0; - } + return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0 ? 1 : 0 }; /** @@ -376,11 +372,7 @@ int CANXMC::sleep() { */ int CANXMC::wakeup() { CAN_xmc->CLC &= ~CAN_CLC_EDIS_Msk; - if (CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0) { - return 1; - } else { - return 0; - } + return CAN_xmc->CLC & CAN_CLC_EDIS_Msk == 0 ? 1 : 0; }; /** From ea527219b1fb1ee9a172419011e698122a451888 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 17:05:37 +0200 Subject: [PATCH 11/13] test: CAN pingpong test v2, with loop --- .../src/features/Test_CAN_connected_node1.hpp | 48 ++++++++++--------- .../src/features/Test_CAN_connected_node2.hpp | 12 ++--- .../corelibs/Test_CAN_connected_node1.cpp | 14 ++++-- .../corelibs/Test_CAN_connected_node2.cpp | 13 +++-- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp index e4367f14..ae3353f3 100644 --- a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node1.hpp @@ -24,6 +24,8 @@ TEST_GROUP(CAN_connected_node1Internal); // Setup method called by Unity before every individual test defined for this test group. static TEST_SETUP(CAN_connected_node1Internal) { + memset(node1Data, 0, sizeof(node1Data)); + memset(receivedData, 0, sizeof(receivedData)); } // Tear down method called by Unity after every individual test defined for this test group. @@ -31,37 +33,39 @@ static TEST_TEAR_DOWN(CAN_connected_node1Internal) { } -void testSendReceive() +void testSendReceive(uint8_t dataLength, uint8_t iterations) { - - CAN.beginPacket(CAN_ID_1); - for (uint8_t i = 0; i < globalQuantity; ++i) { - CAN.write(node1Data[i]); - } - CAN.endPacket(); -#ifdef TRACE_OUTPUT - printArray("Sent Data", node1Data, globalQuantity); -#endif - while (!newDataReceivedNode1) { - } - - if (newDataReceivedNode1) { - for (uint8_t i = 0; i < globalQuantity; ++i) { - UNITY_TEST_ASSERT_EQUAL_UINT8(node1Data[i] + node1Increment, receivedData[i], __LINE__, "Data mismatch"); + canDataLength = dataLength; + + for(uint8_t loop = 0; loop < iterations; ++loop) { + TEST_ASSERT_TRUE(CAN.beginPacket(CAN_ID_1)); + TEST_ASSERT_EQUAL_UINT8(canDataLength, CAN.write(node1Data, canDataLength)); + TEST_ASSERT_EQUAL(1, CAN.endPacket()); + + while (!newDataReceivedNode1) { } - newDataReceivedNode1 = false; -#ifdef TRACE_OUTPUT - printArray("Sent Data", node1Data, globalQuantity); - printArray("Received Data", receivedData, globalQuantity); -#endif + if (newDataReceivedNode1) { + for (uint8_t i = 0; i < canDataLength; ++i) { + TEST_ASSERT_EQUAL_UINT8(node1Data[i] + node2Increment, receivedData[i]); + } + newDataReceivedNode1 = false; + + #ifdef TRACE_OUTPUT + printArray("\nSent Data", node1Data, canDataLength); + printArray("Received Data", receivedData, canDataLength); + #endif + for(uint8_t i = 0; i < canDataLength; ++i) { + node1Data[i] = receivedData[i] + node1Increment; + } + } } } TEST_IFX(CAN_connected_node1Internal, checkPingPong) { - testSendReceive(); + testSendReceive(3,5); } diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp index cdfc2cc3..9acfde8e 100644 --- a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp @@ -24,13 +24,14 @@ void processReceivedMessagesNode2() { if (newDataReceivedNode2) { // Process the received data - for (uint8_t i = 0; i < globalQuantity; ++i) { + canDataLength = CAN.packetDlc(); + for (uint8_t i = 0; i < canDataLength; ++i) { node2Data[i] = receivedData[i] + node2Increment; } // Send processed data back to Node1 CAN.beginPacket(CAN_ID_2); - for (uint8_t i = 0; i < globalQuantity; ++i) { + for (uint8_t i = 0; i < canDataLength; ++i) { CAN.write(node2Data[i]); } CAN.endPacket(); @@ -39,7 +40,8 @@ void processReceivedMessagesNode2() newDataReceivedNode2 = false; #ifdef TRACE_OUTPUT - printArray("Processed Data", node2Data, globalQuantity); + printArray("\nReceived Data", receivedData, canDataLength); + printArray("Sent Data", node2Data, canDataLength); #endif } } @@ -57,10 +59,6 @@ static TEST_TEAR_DOWN(CAN_connected_node2Internal) TEST_IFX(CAN_connected_node2Internal, checkPingPong) { processReceivedMessagesNode2(); - -#ifdef TRACE_OUTPUT - printArray("\nNode2 data", node2Data, globalQuantity); -#endif } diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp index 0b64561e..1f19686e 100644 --- a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node1.cpp @@ -12,12 +12,18 @@ #define CAN_ID_2 0x321 // variables -static uint8_t node1Data[8] = {0, 1, 2, 3, 4, 5, 6, 7}; -static uint8_t node1Increment = 1; -static uint8_t globalQuantity = 8; + + +const static uint8_t node1Increment = 10; +const static uint8_t node2Increment = 1; +const static uint8_t canDataLengthMax = 8; + +static uint8_t node1Data[canDataLengthMax]; +static uint8_t receivedData[canDataLengthMax]; +static uint8_t canDataLength = canDataLengthMax; volatile bool newDataReceivedNode1 = false; -static uint8_t receivedData[8]; + // test feature includes requiring the above defined variables #include "Test_CAN_connected_node1.hpp" diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp index 7576c8c8..57dd733b 100644 --- a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp @@ -12,12 +12,17 @@ #define CAN_ID_2 0x321 // variables -static uint8_t node2Data[8]; -static uint8_t node2Increment = 1; -static uint8_t globalQuantity = 8; + +const static uint8_t node1Increment = 10; +const static uint8_t node2Increment = 1; +const static uint8_t canDataLengthMax = 8; + +static uint8_t canDataLength = canDataLengthMax; +static uint8_t node2Data[canDataLengthMax] = {0}; +static uint8_t receivedData[canDataLengthMax] = {0}; volatile bool newDataReceivedNode2 = false; -static uint8_t receivedData[8]; + // test feature includes requiring the above defined variables #include "Test_CAN_connected_node2.hpp" From 064cb6c10878658325dfa22da0bb6be3f573a124 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 19:51:09 +0200 Subject: [PATCH 12/13] Fix: add missing semicolon --- libraries/CAN/src/CANXMC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/CAN/src/CANXMC.cpp b/libraries/CAN/src/CANXMC.cpp index e0b96747..91ab1ff5 100644 --- a/libraries/CAN/src/CANXMC.cpp +++ b/libraries/CAN/src/CANXMC.cpp @@ -361,7 +361,7 @@ int CANXMC::loopback() { */ int CANXMC::sleep() { CAN_xmc->CLC |= CAN_CLC_EDIS_Msk; - return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0 ? 1 : 0 + return CAN_xmc->CLC & CAN_CLC_EDIS_Msk != 0 ? 1 : 0; }; /** From ba05ce7ecb4971c1f7e1f0ba485f5506aaf7e7b5 Mon Sep 17 00:00:00 2001 From: zhanglinjing Date: Tue, 22 Oct 2024 19:52:07 +0200 Subject: [PATCH 13/13] Test: CAN unit test pingpong v2, add more assertion --- .../test/unit/src/features/Test_CAN_connected_node2.hpp | 7 +++---- .../arduino/corelibs/Test_CAN_connected_node2.cpp | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp index 9acfde8e..955ccf20 100644 --- a/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp +++ b/libraries/CAN/test/unit/src/features/Test_CAN_connected_node2.hpp @@ -24,17 +24,16 @@ void processReceivedMessagesNode2() { if (newDataReceivedNode2) { // Process the received data - canDataLength = CAN.packetDlc(); for (uint8_t i = 0; i < canDataLength; ++i) { node2Data[i] = receivedData[i] + node2Increment; } // Send processed data back to Node1 - CAN.beginPacket(CAN_ID_2); + TEST_ASSERT_TRUE(CAN.beginPacket(CAN_ID_2)); for (uint8_t i = 0; i < canDataLength; ++i) { - CAN.write(node2Data[i]); + TEST_ASSERT_EQUAL(1, CAN.write(node2Data[i])); } - CAN.endPacket(); + TEST_ASSERT_EQUAL(1, CAN.endPacket()); // Clear flag newDataReceivedNode2 = false; diff --git a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp index 57dd733b..21b8f476 100644 --- a/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp +++ b/libraries/CAN/test/unit/src/framework/arduino/corelibs/Test_CAN_connected_node2.cpp @@ -31,10 +31,11 @@ extern CANXMC CAN; void receiveEventNode2(int packetSize) { uint8_t count = 0; - while (CAN.available() && count < 8) { + while (CAN.available()) { receivedData[count++] = CAN.read(); } newDataReceivedNode2 = true; + canDataLength = packetSize; } // Method invoked before a test suite is run.