diff --git a/cpp/common/src/codingstandards/cpp/Extensions.qll b/cpp/common/src/codingstandards/cpp/Extensions.qll index 5ca6cea4f..da055e810 100644 --- a/cpp/common/src/codingstandards/cpp/Extensions.qll +++ b/cpp/common/src/codingstandards/cpp/Extensions.qll @@ -8,4 +8,216 @@ abstract class CompilerExtension extends Locatable { } /** * Common base class for modeling compiler extensions in CPP. */ -abstract class CPPCompilerExtension extends CompilerExtension { } +abstract class CPPCompilerExtension extends CompilerExtension { + abstract string getMessage(); +} + +/** + * An `Attribute` that may be a `GnuAttribute` or `Declspec`, or `MicrosoftAttribute`, etc. + * + * There are language extensions such as GNU `__attribute__`, Microsoft `__declspec` or + * `[attribute]` syntax. + */ +class CPPAttributeExtension extends CPPCompilerExtension, Attribute { + CPPAttributeExtension() { not this instanceof StdAttribute and not this instanceof AlignAs } + + override string getMessage() { + result = + "Use of attribute '" + getName() + + "' is a compiler extension and is not portable to other compilers." + } +} + +/** + * An `Attribute` within a compiler specific namespace such as `[[gnu::weak]]`. + */ +class CppNamespacedStdAttributeExtension extends CPPCompilerExtension, StdAttribute { + CppNamespacedStdAttributeExtension() { exists(this.getNamespace()) and not getNamespace() = "" } + + override string getMessage() { + result = + "Use of attribute '" + getName() + "' in namespace '" + getNamespace() + + "' is a compiler extension and is not portable to other compilers." + } +} + +class CppUnrecognizedAttributeExtension extends CPPCompilerExtension, StdAttribute { + CppUnrecognizedAttributeExtension() { + not this instanceof CppNamespacedStdAttributeExtension and + not getName() in [ + "maybe_unused", "nodiscard", "noreturn", "deprecated", "carries_dependency", "fallthrough" + ] + } + + override string getMessage() { + result = "Use of unrecognized or non-C++17 attribute '" + getName() + "'." + } +} + +/** + * Compiler-specific builtin functions. + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html + */ +class CPPBuiltinFunctionExtension extends CPPCompilerExtension, FunctionCall { + CPPBuiltinFunctionExtension() { + getTarget().getName().indexOf("__builtin_") = 0 or + getTarget().getName().indexOf("__sync_") = 0 or + getTarget().getName().indexOf("__atomic_") = 0 + } + + override string getMessage() { + result = + "Call to builtin function '" + getTarget().getName() + + "' is a compiler extension and is not portable to other compilers." + } +} + +/** + * Statement expressions: ({ ... }) syntax. + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html + */ +class CPPStmtExprExtension extends CPPCompilerExtension, StmtExpr { + override string getMessage() { + result = + "Statement expressions are a compiler extension and are not portable to other compilers." + } +} + +/** + * Ternary expressions with omitted middle operand: x ?: y + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Conditionals.html + */ +class CPPTerseTernaryExtension extends CPPCompilerExtension, ConditionalExpr { + CPPTerseTernaryExtension() { getCondition() = getElse() or getCondition() = getThen() } + + override string getMessage() { + result = + "Ternaries with omitted middle operands are a compiler extension and are not portable to other compilers." + } +} + +/** + * Extended integer types: __int128, etc. + * Reference: https://gcc.gnu.org/onlinedocs/gcc/__int128.html + */ +class CPPExtendedIntegerTypeExtension extends CPPCompilerExtension, DeclarationEntry { + CPPExtendedIntegerTypeExtension() { getType() instanceof Int128Type } + + override string getMessage() { + result = "128-bit integers are a compiler extension and are not portable to other compilers." + } +} + +/** + * Extended floating-point types: __float128, _Decimal32, etc. + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Decimal-Float.html + */ +class CPPExtendedFloatTypeExtension extends CPPCompilerExtension, DeclarationEntry { + CPPExtendedFloatTypeExtension() { + getType() instanceof Decimal128Type or + getType() instanceof Decimal32Type or + getType() instanceof Decimal64Type or + getType() instanceof Float128Type + } + + override string getMessage() { + result = + "Extended floating-point types are a compiler extension and are not portable to other compilers." + } +} + +/** + * Zero-length arrays (flexible array members must be last). + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html + */ +class CPPZeroLengthArraysExtension extends CPPCompilerExtension, DeclarationEntry { + CPPZeroLengthArraysExtension() { getType().(ArrayType).getArraySize() = 0 } + + override string getMessage() { + result = "Zero length arrays are a compiler extension and are not portable to other compilers." + } +} + +/** + * Variable-length arrays in struct members (not C++17 compliant). + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html + */ +class CPPVariableLengthArraysExtension extends CPPCompilerExtension, Field { + CPPVariableLengthArraysExtension() { + getType() instanceof ArrayType and + not getType().(ArrayType).hasArraySize() and + // Not the final member of the struct, which is allowed in some contexts + not exists(int lastIndex, Class declaringStruct | + declaringStruct = getDeclaringType() and + lastIndex = count(declaringStruct.getACanonicalMember()) - 1 and + this = declaringStruct.getCanonicalMember(lastIndex) + ) + } + + override string getMessage() { + result = + "Variable length arrays are a compiler extension and are not portable to other compilers." + } +} + +/** + * __alignof__ operator (use alignof from C++11 instead). + */ +class CPPAlignofExtension extends CPPCompilerExtension, AlignofExprOperator { + CPPAlignofExtension() { exists(getValueText().indexOf("__alignof__")) } + + override string getMessage() { + result = "'__alignof__' is a compiler extension and is not portable to other compilers." + } +} + +/** + * Preprocessor extensions for feature detection. + * Reference: https://clang.llvm.org/docs/LanguageExtensions.html + */ +class CPPConditionalDefineExtension extends CPPCompilerExtension, PreprocessorIfdef { + string feature; + + CPPConditionalDefineExtension() { + feature = + [ + "__has_builtin", "__has_constexpr_builtin", "__has_feature", "__has_extension", + "__has_attribute", "__has_declspec_attribute", "__is_identifier", "__has_include", + "__has_include_next", "__has_warning", "__has_cpp_attribute" + ] and + exists(toString().indexOf(feature)) + } + + override string getMessage() { + result = + "Call to builtin preprocessor feature '" + feature + + "' is a compiler extension and is not portable to other compilers." + } +} + +class CPPPreprocessorDirectiveExtension extends CPPCompilerExtension, PreprocessorDirective { + string kind; + + CPPPreprocessorDirectiveExtension() { + this instanceof PreprocessorPragma and kind = "#pragma " + getHead() + or + this instanceof PreprocessorError and kind = "#error" + or + this instanceof PreprocessorWarning and kind = "#warning" + } + + override string getMessage() { + result = "Use of non-standard preprocessor directive '" + kind + "' is a compiler extension." + } +} + +/** + * Built-in type traits and operations such as `__is_abstract`, `__is_same`, etc. + * + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Type-Traits.html + */ +class CPPBuiltinOperationExtension extends CPPCompilerExtension, BuiltInOperation { + override string getMessage() { + result = "Use of built-in operation '" + toString() + "' is a compiler extension." + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index 204db5b4d..d45b43854 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -92,6 +92,7 @@ import Statements import Strings import Templates import Toolchain +import Toolchain2 import Toolchain3 import Trigraph import TrustBoundaries @@ -191,6 +192,7 @@ newtype TCPPQuery = TStringsPackageQuery(StringsQuery q) or TTemplatesPackageQuery(TemplatesQuery q) or TToolchainPackageQuery(ToolchainQuery q) or + TToolchain2PackageQuery(Toolchain2Query q) or TToolchain3PackageQuery(Toolchain3Query q) or TTrigraphPackageQuery(TrigraphQuery q) or TTrustBoundariesPackageQuery(TrustBoundariesQuery q) or @@ -290,6 +292,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isStringsQueryMetadata(query, queryId, ruleId, category) or isTemplatesQueryMetadata(query, queryId, ruleId, category) or isToolchainQueryMetadata(query, queryId, ruleId, category) or + isToolchain2QueryMetadata(query, queryId, ruleId, category) or isToolchain3QueryMetadata(query, queryId, ruleId, category) or isTrigraphQueryMetadata(query, queryId, ruleId, category) or isTrustBoundariesQueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Toolchain2.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Toolchain2.qll new file mode 100644 index 000000000..aa698424f --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Toolchain2.qll @@ -0,0 +1,26 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Toolchain2Query = TCompilerLanguageExtensionsUsedQuery() + +predicate isToolchain2QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `compilerLanguageExtensionsUsed` query + Toolchain2Package::compilerLanguageExtensionsUsedQuery() and + queryId = + // `@id` for the `compilerLanguageExtensionsUsed` query + "cpp/misra/compiler-language-extensions-used" and + ruleId = "RULE-4-1-1" and + category = "required" +} + +module Toolchain2Package { + Query compilerLanguageExtensionsUsedQuery() { + //autogenerate `Query` type + result = + // `Query` type for `compilerLanguageExtensionsUsed` query + TQueryCPP(TToolchain2PackageQuery(TCompilerLanguageExtensionsUsedQuery())) + } +} diff --git a/cpp/common/test/includes/standard-library/type_traits.h b/cpp/common/test/includes/standard-library/type_traits.h index f164019b8..e14917b71 100644 --- a/cpp/common/test/includes/standard-library/type_traits.h +++ b/cpp/common/test/includes/standard-library/type_traits.h @@ -62,6 +62,20 @@ template struct remove_extent { typedef T type; }; +template struct is_abstract { + const static bool value = false; + constexpr operator bool() { return value; } +}; + +template bool is_abstract_v = is_abstract::value; + +template struct is_same { + const static bool value = false; + constexpr operator bool() { return value; } +}; + +template bool is_same_v = is_same::value; + template struct is_trivially_copy_constructible { const static bool value = true; constexpr operator bool() { return value; } diff --git a/cpp/misra/src/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.ql b/cpp/misra/src/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.ql new file mode 100644 index 000000000..05141f663 --- /dev/null +++ b/cpp/misra/src/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.ql @@ -0,0 +1,27 @@ +/** + * @id cpp/misra/compiler-language-extensions-used + * @name RULE-4-1-1: A program shall conform to ISO/IEC 14882:2017 (C++17) + * @description Language extensions are compiler-specific features that are not part of the C++17 + * standard. Using these extensions reduces portability and may lead to unpredictable + * behavior when code is compiled with different compilers or compiler versions. + * @kind problem + * @precision high + * @problem.severity error + * @tags external/misra/id/rule-4-1-1 + * scope/system + * maintainability + * portability + * external/misra/enforcement/undecidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.Extensions +import codingstandards.cpp.AlertReporting + +from Element unwrapped, CPPCompilerExtension e +where + not isExcluded([e, unwrapped], Toolchain2Package::compilerLanguageExtensionsUsedQuery()) and + unwrapped = MacroUnwrapper::unwrapElement(e) +select unwrapped, e.getMessage() diff --git a/cpp/misra/test/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.expected b/cpp/misra/test/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.expected new file mode 100644 index 000000000..05f2019d2 --- /dev/null +++ b/cpp/misra/test/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.expected @@ -0,0 +1,24 @@ +| test.cpp:11:3:11:19 | maybe_unused | Use of attribute 'maybe_unused' in namespace 'gnu' is a compiler extension and is not portable to other compilers. | +| test.cpp:12:16:12:22 | aligned | Use of attribute 'aligned' is a compiler extension and is not portable to other compilers. | +| test.cpp:13:16:13:21 | unused | Use of attribute 'unused' is a compiler extension and is not portable to other compilers. | +| test.cpp:14:21:14:28 | noreturn | Use of attribute 'noreturn' is a compiler extension and is not portable to other compilers. | +| test.cpp:15:3:15:17 | deprecated | Use of attribute 'deprecated' in namespace 'gnu' is a compiler extension and is not portable to other compilers. | +| test.cpp:16:3:16:23 | nonstandard_attribute | Use of unrecognized or non-C++17 attribute 'nonstandard_attribute'. | +| test.cpp:19:3:19:20 | call to __builtin_popcount | Call to builtin function '__builtin_popcount' is a compiler extension and is not portable to other compilers. | +| test.cpp:20:3:20:18 | call to __builtin_expect | Call to builtin function '__builtin_expect' is a compiler extension and is not portable to other compilers. | +| test.cpp:21:3:21:22 | call to __sync_fetch_and_add_4 | Call to builtin function '__sync_fetch_and_add_4' is a compiler extension and is not portable to other compilers. | +| test.cpp:23:3:23:20 | __is_abstract | Use of built-in operation '__is_abstract' is a compiler extension. | +| test.cpp:24:3:24:22 | __is_same | Use of built-in operation '__is_same' is a compiler extension. | +| test.cpp:30:3:33:4 | (statement expression) | Statement expressions are a compiler extension and are not portable to other compilers. | +| test.cpp:34:3:34:12 | ... ? ... : ... | Ternaries with omitted middle operands are a compiler extension and are not portable to other compilers. | +| test.cpp:36:12:36:13 | definition of l0 | 128-bit integers are a compiler extension and are not portable to other compilers. | +| test.cpp:45:20:45:30 | fallthrough | Use of attribute 'fallthrough' is a compiler extension and is not portable to other compilers. | +| test.cpp:47:7:47:22 | fallthrough | Use of attribute 'fallthrough' in namespace 'gnu' is a compiler extension and is not portable to other compilers. | +| test.cpp:55:3:55:29 | __builtin_va_start | Use of built-in operation '__builtin_va_start' is a compiler extension. | +| test.cpp:60:7:60:8 | definition of m1 | Zero length arrays are a compiler extension and are not portable to other compilers. | +| test.cpp:64:31:64:41 | vector_size | Use of attribute 'vector_size' is a compiler extension and is not portable to other compilers. | +| test.cpp:66:1:66:20 | #ifdef __has_builtin | Call to builtin preprocessor feature '__has_builtin' is a compiler extension and is not portable to other compilers. | +| test.cpp:72:1:72:12 | #pragma once | Use of non-standard preprocessor directive '#pragma once' is a compiler extension. | +| test.cpp:73:1:73:27 | #pragma GCC diagnostic push | Use of non-standard preprocessor directive '#pragma GCC diagnostic push' is a compiler extension. | +| test.cpp:74:1:74:28 | #warning "This is a warning" | Use of non-standard preprocessor directive '#warning' is a compiler extension. | +| test.cpp:76:1:76:41 | #warning "preceeding spaces is common" | Use of non-standard preprocessor directive '#warning' is a compiler extension. | diff --git a/cpp/misra/test/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.qlref b/cpp/misra/test/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.qlref new file mode 100644 index 000000000..6435d0f39 --- /dev/null +++ b/cpp/misra/test/rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.qlref @@ -0,0 +1 @@ +rules/RULE-4-1-1/CompilerLanguageExtensionsUsed.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-4-1-1/test.cpp b/cpp/misra/test/rules/RULE-4-1-1/test.cpp new file mode 100644 index 000000000..f6fa99b1b --- /dev/null +++ b/cpp/misra/test/rules/RULE-4-1-1/test.cpp @@ -0,0 +1,77 @@ +#include +#include + +alignas(8) int g0; // COMPLIANT +[[maybe_unused]] int g1; // COMPLIANT +[[noreturn]] void f1(); // COMPLIANT +[[maybe_unused]] void f2(); // COMPLIANT +[[deprecated]] void f3(); // COMPLIANT +[[nodiscard]] void f4(); // COMPLIANT +[[carries_dependency]] void f5(); // COMPLIANT +[[gnu::maybe_unused]] int g2; // NON_COMPLIANT +__attribute__((aligned(8))) int g3; // NON_COMPLIANT +__attribute__((unused)) int g4; // NON_COMPLIANT +void __attribute__((noreturn)) f6(); // NON_COMPLIANT +[[gnu::deprecated]] void f7(); // NON_COMPLIANT +[[nonstandard_attribute]] void f8(); // NON_COMPLIANT + +void f9() { + __builtin_popcount(15); // NON_COMPLIANT + __builtin_expect(1, 1); // NON_COMPLIANT + __sync_fetch_and_add(&g1, 1); // NON_COMPLIANT + + __is_abstract(int); // NON_COMPLIANT -- gnu extension + __is_same(int, long); // NON_COMPLIANT -- gnu extension + std::is_abstract::value; // COMPLIANT + std::is_abstract_v; // COMPLIANT + std::is_same::value; // COMPLIANT + std::is_same_v; // COMPLIANT + + ({ // NON_COMPLIANT + int y = 5; + y; + }); + true ?: 42; // NON_COMPLIANT + + __int128 l0 = 0; // NON_COMPLIANT + + __alignof__(int); // NON_COMPLIANT[False negative] + alignof(int); // COMPLIANT + + switch (1) { + case 1: + [[fallthrough]]; // COMPLIANT + case 2: + __attribute__((fallthrough)); // NON_COMPLIANT + case 3: + [[gnu::fallthrough]]; // NON_COMPLIANT + default: + } +} + +void f10(int x, ...) { + va_list args; // COMPLIANT + va_start(args, x); // COMPLIANT + __builtin_va_start(args, x); // NON_COMPLIANT +} + +struct ZeroLengthArray { + int m0; + int m1[0]; // NON_COMPLIANT -- gnu extension +}; + +typedef int t1; // COMPLIANT +typedef int t2 __attribute__((vector_size(16))); // NON_COMPLIANT + +#ifdef __has_builtin // NON_COMPLIANT -- clang extension +#endif + +#ifdef __cplusplus // COMPLIANT +#endif + +#pragma once +#pragma GCC diagnostic push // NON_COMPLIANT +#warning "This is a warning" // NON_COMPLIANT +// clang-format off +# warning "preceeding spaces is common" // NON_COMPLIANT +// clang-format on \ No newline at end of file diff --git a/rule_packages/cpp/Toolchain2.json b/rule_packages/cpp/Toolchain2.json new file mode 100644 index 000000000..0d474f871 --- /dev/null +++ b/rule_packages/cpp/Toolchain2.json @@ -0,0 +1,29 @@ +{ + "MISRA-C++-2023": { + "RULE-4-1-1": { + "properties": { + "enforcement": "undecidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Language extensions are compiler-specific features that are not part of the C++17 standard. Using these extensions reduces portability and may lead to unpredictable behavior when code is compiled with different compilers or compiler versions.", + "kind": "problem", + "name": "A program shall conform to ISO/IEC 14882:2017 (C++17)", + "precision": "high", + "severity": "error", + "short_name": "CompilerLanguageExtensionsUsed", + "tags": [ + "scope/system", + "maintainability", + "portability" + ], + "implementation_scope": { + "description": "This query detects pragmas, non standard attributes, some compiler-specific built-ins and nonstandard behaviors. It cannot detect all possible compiler extensions or deviations from standard C++17." + } + } + ], + "title": "A program shall conform to ISO/IEC 14882:2017 (C++17)" + } + } +} \ No newline at end of file