diff --git a/cpp/common/src/codingstandards/cpp/Macro.qll b/cpp/common/src/codingstandards/cpp/Macro.qll index 6514e957fb..8d821ffaf2 100644 --- a/cpp/common/src/codingstandards/cpp/Macro.qll +++ b/cpp/common/src/codingstandards/cpp/Macro.qll @@ -19,6 +19,49 @@ class FunctionLikeMacro extends Macro { exists(this.getBody().regexpFind("\\#?\\b" + parameter + "\\b", _, result)) ) } + + /** + * Holds if the parameter is used in a way that may make it vulnerable to precedence issues. + * + * Typically, parameters are wrapped in parentheses to protect them from precedence issues, but + * that is not always possible. + */ + predicate parameterPrecedenceUnprotected(int index) { + // Check if the parameter is used in a way that requires parentheses + exists(string parameter | parameter = getParameter(index) | + // Finds any occurence of the parameter that is not preceded by, or followed by, either a + // parenthesis or the '#' token operator. + // + // Note the following cases: + // - "(x + 1)" is preceded by a parenthesis, but not followed by one, so SHOULD be matched. + // - "x # 1" is followed by "#" (though not preceded by #) and SHOULD be matched. + // - "(1 + x)" is followed by a parenthesis, but not preceded by one, so SHOULD be matched. + // - "1 # x" is preceded by "#" (though not followed by #) and SHOULD NOT be matched. + // + // So the regex is structured as follows: + // - paramMatch: Matches the parameter at a word boundary, with optional whitespace + // - notHashed: Finds parameters not used with a leading # operator. + // - The final regex finds cases of `notHashed` that are not preceded by a parenthesis, + // and cases of `notHashed` that are not followed by a parenthesis. + // + // Therefore, a parameter with parenthesis on both sides is not matched, a parameter with + // parenthesis missing on one or both sides is only matched if there is no leading or trailing + // ## operator. + exists(string noBeforeParen, string noAfterParen, string paramMatch, string notHashed | + // Not preceded by a parenthesis + noBeforeParen = "(? { occurrence = prevOccurrence + 1 ) else ( token = TNotParen() and - exists(inputStr.regexpFind("\\(|\\)", prevOccurrence + 1, endPos)) and + exists(inputStr.regexpFind("\\(|\\)|$", prevOccurrence + 1, endPos)) and occurrence = prevOccurrence ) ) diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preprocessor.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preprocessor.qll new file mode 100644 index 0000000000..569a20a226 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preprocessor.qll @@ -0,0 +1,78 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype PreprocessorQuery = + TUndefOfMacroNotDefinedInFileQuery() or + TInvalidTokenInDefinedOperatorQuery() or + TDefinedOperatorExpandedInIfDirectiveQuery() or + TNoValidIfdefGuardInHeaderQuery() + +predicate isPreprocessorQueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `undefOfMacroNotDefinedInFile` query + PreprocessorPackage::undefOfMacroNotDefinedInFileQuery() and + queryId = + // `@id` for the `undefOfMacroNotDefinedInFile` query + "cpp/misra/undef-of-macro-not-defined-in-file" and + ruleId = "RULE-19-0-4" and + category = "advisory" + or + query = + // `Query` instance for the `invalidTokenInDefinedOperator` query + PreprocessorPackage::invalidTokenInDefinedOperatorQuery() and + queryId = + // `@id` for the `invalidTokenInDefinedOperator` query + "cpp/misra/invalid-token-in-defined-operator" and + ruleId = "RULE-19-1-1" and + category = "required" + or + query = + // `Query` instance for the `definedOperatorExpandedInIfDirective` query + PreprocessorPackage::definedOperatorExpandedInIfDirectiveQuery() and + queryId = + // `@id` for the `definedOperatorExpandedInIfDirective` query + "cpp/misra/defined-operator-expanded-in-if-directive" and + ruleId = "RULE-19-1-1" and + category = "required" + or + query = + // `Query` instance for the `noValidIfdefGuardInHeader` query + PreprocessorPackage::noValidIfdefGuardInHeaderQuery() and + queryId = + // `@id` for the `noValidIfdefGuardInHeader` query + "cpp/misra/no-valid-ifdef-guard-in-header" and + ruleId = "RULE-19-2-1" and + category = "required" +} + +module PreprocessorPackage { + Query undefOfMacroNotDefinedInFileQuery() { + //autogenerate `Query` type + result = + // `Query` type for `undefOfMacroNotDefinedInFile` query + TQueryCPP(TPreprocessorPackageQuery(TUndefOfMacroNotDefinedInFileQuery())) + } + + Query invalidTokenInDefinedOperatorQuery() { + //autogenerate `Query` type + result = + // `Query` type for `invalidTokenInDefinedOperator` query + TQueryCPP(TPreprocessorPackageQuery(TInvalidTokenInDefinedOperatorQuery())) + } + + Query definedOperatorExpandedInIfDirectiveQuery() { + //autogenerate `Query` type + result = + // `Query` type for `definedOperatorExpandedInIfDirective` query + TQueryCPP(TPreprocessorPackageQuery(TDefinedOperatorExpandedInIfDirectiveQuery())) + } + + Query noValidIfdefGuardInHeaderQuery() { + //autogenerate `Query` type + result = + // `Query` type for `noValidIfdefGuardInHeader` query + TQueryCPP(TPreprocessorPackageQuery(TNoValidIfdefGuardInHeaderQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preprocessor2.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preprocessor2.qll new file mode 100644 index 0000000000..0993b099ff --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preprocessor2.qll @@ -0,0 +1,61 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Preprocessor2Query = + TInvalidIncludeDirectiveQuery() or + TUnparenthesizedMacroArgumentQuery() or + TDisallowedUseOfPragmaQuery() + +predicate isPreprocessor2QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `invalidIncludeDirective` query + Preprocessor2Package::invalidIncludeDirectiveQuery() and + queryId = + // `@id` for the `invalidIncludeDirective` query + "cpp/misra/invalid-include-directive" and + ruleId = "RULE-19-2-2" and + category = "required" + or + query = + // `Query` instance for the `unparenthesizedMacroArgument` query + Preprocessor2Package::unparenthesizedMacroArgumentQuery() and + queryId = + // `@id` for the `unparenthesizedMacroArgument` query + "cpp/misra/unparenthesized-macro-argument" and + ruleId = "RULE-19-3-4" and + category = "required" + or + query = + // `Query` instance for the `disallowedUseOfPragma` query + Preprocessor2Package::disallowedUseOfPragmaQuery() and + queryId = + // `@id` for the `disallowedUseOfPragma` query + "cpp/misra/disallowed-use-of-pragma" and + ruleId = "RULE-19-6-1" and + category = "advisory" +} + +module Preprocessor2Package { + Query invalidIncludeDirectiveQuery() { + //autogenerate `Query` type + result = + // `Query` type for `invalidIncludeDirective` query + TQueryCPP(TPreprocessor2PackageQuery(TInvalidIncludeDirectiveQuery())) + } + + Query unparenthesizedMacroArgumentQuery() { + //autogenerate `Query` type + result = + // `Query` type for `unparenthesizedMacroArgument` query + TQueryCPP(TPreprocessor2PackageQuery(TUnparenthesizedMacroArgumentQuery())) + } + + Query disallowedUseOfPragmaQuery() { + //autogenerate `Query` type + result = + // `Query` type for `disallowedUseOfPragma` query + TQueryCPP(TPreprocessor2PackageQuery(TDisallowedUseOfPragmaQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index 4a6cbe936b..341e6986c0 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -40,6 +40,8 @@ import Operators import OrderOfEvaluation import OutOfBounds import Pointers +import Preprocessor +import Preprocessor2 import Representation import Scope import SideEffects1 @@ -94,6 +96,8 @@ newtype TCPPQuery = TOrderOfEvaluationPackageQuery(OrderOfEvaluationQuery q) or TOutOfBoundsPackageQuery(OutOfBoundsQuery q) or TPointersPackageQuery(PointersQuery q) or + TPreprocessorPackageQuery(PreprocessorQuery q) or + TPreprocessor2PackageQuery(Preprocessor2Query q) or TRepresentationPackageQuery(RepresentationQuery q) or TScopePackageQuery(ScopeQuery q) or TSideEffects1PackageQuery(SideEffects1Query q) or @@ -148,6 +152,8 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isOrderOfEvaluationQueryMetadata(query, queryId, ruleId, category) or isOutOfBoundsQueryMetadata(query, queryId, ruleId, category) or isPointersQueryMetadata(query, queryId, ruleId, category) or + isPreprocessorQueryMetadata(query, queryId, ruleId, category) or + isPreprocessor2QueryMetadata(query, queryId, ruleId, category) or isRepresentationQueryMetadata(query, queryId, ruleId, category) or isScopeQueryMetadata(query, queryId, ruleId, category) or isSideEffects1QueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/util/CondensedList.qll b/cpp/common/src/codingstandards/cpp/util/CondensedList.qll new file mode 100644 index 0000000000..bda0177bff --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/util/CondensedList.qll @@ -0,0 +1,105 @@ +private import codeql.util.DenseRank + +/** + * Describes how to construct a condensed list from sparse but orderable data, and how that data + * should be connected, with one such list per specified division. + */ +signature module CondensedListSig { + /** + * The division specifies which items are connected into lists, with one list per division. + * + * For instance, if connecting variables defined in a file, the division will be the file. + */ + class Division; + + /** + * The class of the items to be condensed into lists. + * + * For instance, when connecting variables defined in a file, the items are the variables. + */ + class Item { + string toString(); + } + + /** + * The index specifies the order of the items in the condensed list, and may be sparse (have + * gaps). + * + * For instance, if connecting variables defined in a file, the index will be the line number of + * the variable in the file. + * + * The sparse index (which may have gaps) is used to determine the ordering of the items in the + * condensed list. Once the condensed list is created, the items in the list will automatically be + * assigned a dense index (which has no gaps). + * + * There must be no duplicate indices for the same division for correctness. + */ + int getSparseIndex(Division d, Item l); +} + +/** + * A module to take orderable data (which may not be continuous) and condense it into one or more + * dense lists, with one such list per specified division. + * + * To instantiate this module, you need to provide a `CondensedListSig` module that + * specifies the spare index and division of the items to be connected. + * + * For instance, to create a condensed list of variables defined in every file, you can + * create a `CondensedListSig` module that specifies the file as the division and + * the line number as the sparse index. + * + * ```ql + * module ConfigFileListConfig { + * class Division = File; + * class Item = Variable; + * int getSparseIndex(File file, Variable var) { + * file = var.getLocation().getFile() and + * var.getLocation().getStartLine() + * } + * } + * + * import Condense + * + * from Condense::ListEntry l + * select l, l.getItem(), l.getDenseIndex(), l.getNext(), l.getPrev(), + * ``` + */ +module Condense { + newtype TList = + THead(Config::Item l, Config::Division t) { denseRank(t, l) = 1 } or + TCons(ListEntry prev, Config::Item l) { + prev.getDenseIndex() = denseRank(prev.getDivision(), l) - 1 + } + + private module DenseRankConfig implements DenseRankInputSig2 { + class Ranked = Config::Item; + + class C = Config::Division; + + predicate getRank = Config::getSparseIndex/2; + } + + private import DenseRank2 + + class ListEntry extends TList { + Config::Division getDivision() { + this = THead(_, result) + or + exists(ListEntry prev | this = TCons(prev, _) and result = prev.getDivision()) + } + + string toString() { result = getItem().toString() + " [index " + getDenseIndex() + "]" } + + Config::Item getItem() { + this = THead(result, _) + or + this = TCons(_, result) + } + + int getDenseIndex() { result = denseRank(getDivision(), getItem()) } + + ListEntry getPrev() { this = TCons(result, _) } + + ListEntry getNext() { result.getPrev() = this } + } +} diff --git a/cpp/common/src/codingstandards/cpp/util/Pair.qll b/cpp/common/src/codingstandards/cpp/util/Pair.qll new file mode 100644 index 0000000000..a0c879ab93 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/util/Pair.qll @@ -0,0 +1,21 @@ +bindingset[this] +signature class ItemSig { + bindingset[this] + string toString(); +} + +module Pair { + signature predicate pred(A a, B b); + + module Where { + private newtype TAll = TSome(A a, B b) { ctor(a, b) } + + class Pair extends TAll { + A getFirst() { this = TSome(result, _) } + + B getSecond() { this = TSome(_, result) } + + string toString() { result = getFirst().toString() + ", " + getSecond().toString() } + } + } +} diff --git a/cpp/misra/src/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.ql b/cpp/misra/src/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.ql new file mode 100644 index 0000000000..a8121f2da5 --- /dev/null +++ b/cpp/misra/src/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.ql @@ -0,0 +1,66 @@ +/** + * @id cpp/misra/undef-of-macro-not-defined-in-file + * @name RULE-19-0-4: #undef should only be used for macros defined previously in the same file + * @description Using #undef to undefine a macro that is not defined in the same file can lead to + * confusion. + * @kind problem + * @precision very-high + * @problem.severity warning + * @tags external/misra/id/rule-19-0-4 + * scope/single-translation-unit + * readability + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/advisory + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.util.CondensedList +import codingstandards.cpp.util.Pair + +class DefOrUndef extends PreprocessorDirective { + string name; + + DefOrUndef() { + name = this.(PreprocessorUndef).getName() or + name = this.(Macro).getName() + } + + string getName() { result = name } +} + +predicate relevantNameAndFile(string name, File file) { + exists(DefOrUndef m | + m.getName() = name and + m.getFile() = file + ) +} + +class StringFilePair = Pair::Where::Pair; + +module DefUndefListConfig implements CondensedListSig { + class Division = StringFilePair; + + class Item = DefOrUndef; + + int getSparseIndex(StringFilePair division, DefOrUndef directive) { + directive.getName() = division.getFirst() and + directive.getFile() = division.getSecond() and + result = directive.getLocation().getStartLine() + } +} + +class ListEntry = Condense::ListEntry; + +from PreprocessorUndef undef, ListEntry defUndefListEntry +where + not isExcluded(undef, PreprocessorPackage::undefOfMacroNotDefinedInFileQuery()) and + // There exists a def or undef for a given name and file, and it is an #undef + undef = defUndefListEntry.getItem() and + // Exclude cases where the previous def or undef with the same name in the same file is a #define + not exists(ListEntry prev | + prev = defUndefListEntry.getPrev() and + prev.getItem() instanceof Macro + ) +select undef, "Undef of name '" + undef.getName() + "' not defined in the same file." diff --git a/cpp/misra/src/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.ql b/cpp/misra/src/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.ql new file mode 100644 index 0000000000..cb79a5bbf6 --- /dev/null +++ b/cpp/misra/src/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.ql @@ -0,0 +1,27 @@ +/** + * @id cpp/misra/defined-operator-expanded-in-if-directive + * @name RULE-19-1-1: The defined preprocessor operator shall be used appropriately + * @description Macro expansions that produce the token 'defined' inside of an if directive result + * in undefined behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-19-1-1 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from PreprocessorIf ifDirective, MacroInvocation mi +where + not isExcluded(ifDirective, PreprocessorPackage::definedOperatorExpandedInIfDirectiveQuery()) and + ifDirective.getLocation().subsumes(mi.getLocation()) and + mi.getMacro().getBody().regexpMatch(".*defined.*") +select ifDirective, + "If directive contains macro expansion including the token 'defined' from macro $@, which results in undefined behavior.", + mi.getMacro(), mi.getMacroName() diff --git a/cpp/misra/src/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.ql b/cpp/misra/src/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.ql new file mode 100644 index 0000000000..c405dcf542 --- /dev/null +++ b/cpp/misra/src/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.ql @@ -0,0 +1,42 @@ +/** + * @id cpp/misra/invalid-token-in-defined-operator + * @name RULE-19-1-1: The defined preprocessor operator shall be used appropriately + * @description Using the defined operator without an immediately following optionally parenthesized + * identifier results in undefined behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-19-1-1 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +string idRegex() { result = "[a-zA-Z_]([a-zA-Z_0-9]*)" } + +bindingset[body] +predicate hasInvalidDefinedOperator(string body) { + body.regexpMatch(".*\\bdefined" + + // Contains text "defined" at a word break + // Negative zero width lookahead: + "(?!(" + + // (group) optional whitespace followed by a valid identifier + "(\\s*" + idRegex() + ")" + + // or + "|" + + // (group) optional whitespace followed by parenthesis and valid identifier + "(\\s*\\(\\s*" + idRegex() + "\\s*\\))" + + // End negative zero width lookahead, match remaining text + ")).*") +} + +from PreprocessorIf ifDirective +where + not isExcluded(ifDirective, PreprocessorPackage::invalidTokenInDefinedOperatorQuery()) and + hasInvalidDefinedOperator(ifDirective.getHead()) +select ifDirective, "Invalid use of defined operator in if directive." diff --git a/cpp/misra/src/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.ql b/cpp/misra/src/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.ql new file mode 100644 index 0000000000..902a356f7a --- /dev/null +++ b/cpp/misra/src/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.ql @@ -0,0 +1,44 @@ +/** + * @id cpp/misra/no-valid-ifdef-guard-in-header + * @name RULE-19-2-1: Precautions shall be taken in order to prevent the contents of a header file being included more + * @description Precautions shall be taken in order to prevent the contents of a header file being + * included more than once. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-19-2-1 + * scope/single-translation-unit + * maintainability + * correctness + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import semmle.code.cpp.headers.MultipleInclusion + +predicate isOutside(CorrectIncludeGuard includeGuard, Location location) { + location.getFile() = includeGuard.getFile() and + ( + location.isBefore(includeGuard.getIfndef().getLocation()) + or + includeGuard.getEndif().getLocation().isBefore(location) + ) +} + +from File included +where + not isExcluded(included, PreprocessorPackage::noValidIfdefGuardInHeaderQuery()) and + included = any(Compilation c).getAFileCompiled().getAnIncludedFile+() and + not exists(CorrectIncludeGuard includeGuard | + includeGuard.getFile() = included and + // Stricter: define is before all other contents + not included + .getATopLevelDeclaration() + .getLocation() + .isBefore(includeGuard.getDefine().getLocation()) and + // Stricter: do not allow includes outside of the inclusion guard + not exists(Include include | isOutside(includeGuard, include.getLocation())) + ) +select included, "File does not have a well formatted include guard." diff --git a/cpp/misra/src/rules/RULE-19-2-2/InvalidIncludeDirective.ql b/cpp/misra/src/rules/RULE-19-2-2/InvalidIncludeDirective.ql new file mode 100644 index 0000000000..2e49b34d43 --- /dev/null +++ b/cpp/misra/src/rules/RULE-19-2-2/InvalidIncludeDirective.ql @@ -0,0 +1,24 @@ +/** + * @id cpp/misra/invalid-include-directive + * @name RULE-19-2-2: The #include directive shall be followed by either a or "filename" sequence + * @description Include directives shall only use the or "filename" forms. + * @kind problem + * @precision very-high + * @problem.severity warning + * @tags external/misra/id/rule-19-2-2 + * scope/single-translation-unit + * maintainability + * readability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from Include include +where + not isExcluded(include, Preprocessor2Package::invalidIncludeDirectiveQuery()) and + // Check for < followed by (not >)+ followed by >, or " followed by (not ")+ followed by ". + not include.getIncludeText().trim().regexpMatch("^(<[^>]+>|\"[^\"]+\")$") +select include, "Non-compliant #include directive text '" + include.getHead() + "'." diff --git a/cpp/misra/src/rules/RULE-19-3-4/UnparenthesizedMacroArgument.ql b/cpp/misra/src/rules/RULE-19-3-4/UnparenthesizedMacroArgument.ql new file mode 100644 index 0000000000..09cada7b08 --- /dev/null +++ b/cpp/misra/src/rules/RULE-19-3-4/UnparenthesizedMacroArgument.ql @@ -0,0 +1,187 @@ +/** + * @id cpp/misra/unparenthesized-macro-argument + * @name RULE-19-3-4: Parentheses shall be used to ensure macro arguments are expanded appropriately + * @description Expanded macro arguments shall be enclosed in parentheses to ensure the resulting + * expressions have the expected precedence and order of operations. + * @kind problem + * @precision high + * @problem.severity error + * @tags external/misra/id/rule-19-3-4 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.Macro +import codingstandards.cpp.MatchingParenthesis +import codeql.util.Boolean + +/** + * This regex is used to find macro arguments that appear to have critical operators in them, before + * we do the expensive process of parsing them to look for parenthesis. + */ +pragma[noinline] +string criticalOperatorRegex() { + result = + ".*(" + + concat(string op | + op in [ + "\\*=?", "/=?", "%=?", "\\+=?", "-=?", "<>?=?", "==?", "!=", "&&?=?", "\\^/?", + "\\|\\|?=?", "\\?" + ] + | + op, "|" + ) + ").*" +} + +/** + * Whether a string appears to contain a critical operator. + */ +bindingset[input] +predicate hasCriticalOperator(string input) { input.regexpMatch(criticalOperatorRegex()) } + +/** + * A critical operator is an operator with "level" between 13 and 2, according to the MISRA C++ + * standard. This includes from the "multiplicative" level (13) to the "conditional" level (2). + */ +class CriticalOperatorExpr extends Expr { + string operator; + + CriticalOperatorExpr() { + operator = this.(BinaryOperation).getOperator() + or + this instanceof ConditionalExpr and operator = "?" + or + operator = this.(Assignment).getOperator() + } + + string getOperator() { result = operator } +} + +/** + * An invocation of a macro that has a parameter that is not precedence-protected with parentheses, + * and that produces a critical operator expression. + * + * This class is used in two passes. Firstly, with `hasRiskyParameter`, to find the macro paramaters + * that should be parsed for parenthesis. Secondly, with `hasNonCompliantParameter`, to parse the + * risky parameters and attempt to match the produced AST to an unparenthesized occurence of that + * operator in the argument text. + * + * For a given macro invocation to be considered risky, it must + * - The macro must have a parameter that is not precedence-protected with parentheses. + * - The macro must produce a critical operator expression. + * - The macro must produce only expressions, statements, or variable declarations with initializers. + * + * For a risky macro to be non-compliant, it must hold for some values of the predicate + * `hasNonCompliantParameter`. + */ +class RiskyMacroInvocation extends MacroInvocation { + FunctionLikeMacro macro; + string riskyParamName; + int riskyParamIdx; + + RiskyMacroInvocation() { + macro = getMacro() and + // The parameter is not precedence-protected with parentheses in the macro body. + macro.parameterPrecedenceUnprotected(riskyParamIdx) and + riskyParamName = macro.getParameter(riskyParamIdx) and + // This macro invocation produces a critical operator expression. + getAGeneratedElement() instanceof CriticalOperatorExpr and + // It seems to generate an expression, statement, or variable declaration with initializer. + forex(Element e | e = getAGeneratedElement() | + e instanceof Expr + or + e instanceof Stmt + or + e.(Variable).getInitializer().getExpr() = getAGeneratedElement() + or + e.(VariableDeclarationEntry).getDeclaration().getInitializer().getExpr() = + getAGeneratedElement() + ) + } + + /** + * A stage 1 pass used to find macro parameters that are not precedence-protected, and have a + * critical operator in them, and therefore need to be parsed to check for parenthesis at the + * macro call-site, which is expensive. + */ + predicate hasRiskyParameter(string name, int index, string value) { + name = riskyParamName and + index = riskyParamIdx and + value = getExpandedArgument(riskyParamIdx) and + hasCriticalOperator(value) + } + + /** + * A stage 2 pass that occurs after risky parameters have been parsed, to check for parenthesis at the macro + * call-site. + * + * For a given macro argument to be flagged, it must: + * - be risky as determined by the characteristic predicate (produce a critical operator and only + * expressions, statements, etc). + * - be flagged by stage 1 as a risky parameter (i.e. it must have a critical operator in it and + * correspond to a macro parameter that is not precedence-protected with parentheses) + * - there must be a top-level text node that contains the operator in the argument string + * - the operator cannot be the first character in the string (i.e. it should not look like a + * unary - or +) + * - the operator cannot exist inside a generated string literal + * - the operator existence of the operator should not be as a substring of "->", "++", or "--" + * operators. + * + * The results of this predicate should be flagged by the query. + */ + predicate hasNonCompliantParameter(string name, int index, string value, string operator) { + hasRiskyParameter(name, index, value) and + exists( + ParsedRoot parsedRoot, ParsedText topLevelText, string text, CriticalOperatorExpr opExpr, + int opIndex + | + parsedRoot.getInputString() = value and + (topLevelText.getParent() = parsedRoot or topLevelText = parsedRoot) and + text = topLevelText.getText().trim() and + opExpr = getAGeneratedElement() and + operator = opExpr.getOperator() and + opIndex = text.indexOf(operator) and + // Ignore "->", "++", and "--" operators. + not [text.substring(opIndex - 1, opIndex + 1), text.substring(opIndex, opIndex + 2)] = + ["--", "++", "->"] and + // Ignore operators inside string literals. + not exists(Literal l | + l = getAGeneratedElement() and + exists(l.getValue().indexOf(operator)) + ) and + // A leading operator is probably unary and not a problem. + (opIndex > 0 or topLevelText.getChildIdx() > 0) + ) + } +} + +/** + * A string class that is used to determine what macro arguments will be parsed. + * + * This should be a reasonably small set of strings, as parsing is expensive. + */ +class RiskyMacroArgString extends string { + RiskyMacroArgString() { any(RiskyMacroInvocation mi).hasRiskyParameter(_, _, this) } +} + +// Import `ParsedRoot` etc for the parsed macro arguments. +import MatchingParenthesis + +from + RiskyMacroInvocation mi, FunctionLikeMacro m, string paramName, string criticalOperator, + int paramIndex, string argumentString +where + not isExcluded([m.(Element), mi.(Element)], + Preprocessor2Package::unparenthesizedMacroArgumentQuery()) and + mi.getMacro() = m and + mi.hasNonCompliantParameter(paramName, paramIndex, argumentString, criticalOperator) +select mi, + "Macro argument " + paramIndex + " (with expanded value '" + argumentString + "') contains a" + + " critical operator '" + criticalOperator + + "' that is not parenthesized, but macro $@ argument '" + paramName + + "' is not precedence-protected with parenthesis.", m, m.getName() diff --git a/cpp/misra/src/rules/RULE-19-6-1/DisallowedUseOfPragma.ql b/cpp/misra/src/rules/RULE-19-6-1/DisallowedUseOfPragma.ql new file mode 100644 index 0000000000..0e07ee02f4 --- /dev/null +++ b/cpp/misra/src/rules/RULE-19-6-1/DisallowedUseOfPragma.ql @@ -0,0 +1,32 @@ +/** + * @id cpp/misra/disallowed-use-of-pragma + * @name RULE-19-6-1: The #pragma directive and the _Pragma operator should not be used + * @description Preprocessor pragma directives are implementation-defined, and should not be used to + * maintain code portability. + * @kind problem + * @precision very-high + * @problem.severity warning + * @tags external/misra/id/rule-19-6-1 + * scope/single-translation-unit + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/advisory + */ + +import cpp +import codingstandards.cpp.misra + +from PreprocessorDirective pragma, string kind +where + not isExcluded(pragma, Preprocessor2Package::disallowedUseOfPragmaQuery()) and + ( + pragma instanceof PreprocessorPragma and + kind = "#pragma directive '" + pragma.getHead() + "'" + or + exists(string headOrBody, string pragmaOperand | + headOrBody = [pragma.getHead(), pragma.(Macro).getBody()] and + pragmaOperand = headOrBody.regexpCapture(".*\\b(_Pragma\\b\\s*\\([^\\)]+\\)).*", 1) and + kind = "_Pragma operator used: '" + pragmaOperand + "'" + ) + ) +select pragma, "Non-compliant " + kind + "." diff --git a/cpp/misra/test/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.expected b/cpp/misra/test/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.expected new file mode 100644 index 0000000000..af0c0993eb --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.expected @@ -0,0 +1,3 @@ +| test.cpp:3:1:3:9 | #undef M1 | Undef of name 'M1' not defined in the same file. | +| test.cpp:6:1:6:9 | #undef M1 | Undef of name 'M1' not defined in the same file. | +| test.cpp:7:1:7:9 | #undef M2 | Undef of name 'M2' not defined in the same file. | diff --git a/cpp/misra/test/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.qlref b/cpp/misra/test/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.qlref new file mode 100644 index 0000000000..094c73c7f2 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.qlref @@ -0,0 +1 @@ +rules/RULE-19-0-4/UndefOfMacroNotDefinedInFile.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-0-4/test.cpp b/cpp/misra/test/rules/RULE-19-0-4/test.cpp new file mode 100644 index 0000000000..665d5b970a --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-0-4/test.cpp @@ -0,0 +1,7 @@ +#define M1 +#undef M1 // COMPLIANT +#undef M1 // NON-COMPLIANT +#define M1 +#undef M1 // COMPLIANT +#undef M1 // NON-COMPLIANT +#undef M2 // NON-COMPLIANT \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.expected b/cpp/misra/test/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.expected new file mode 100644 index 0000000000..dbbbc5502f --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.expected @@ -0,0 +1,3 @@ +| test.cpp:39:1:39:6 | #if M1 | If directive contains macro expansion including the token 'defined' from macro $@, which results in undefined behavior. | test.cpp:34:1:34:18 | #define M1 defined | M1 | +| test.cpp:41:1:41:6 | #if M2 | If directive contains macro expansion including the token 'defined' from macro $@, which results in undefined behavior. | test.cpp:35:1:35:30 | #define M2 1 + 2 + defined + 3 | M2 | +| test.cpp:43:1:43:6 | #if M3 | If directive contains macro expansion including the token 'defined' from macro $@, which results in undefined behavior. | test.cpp:35:1:35:30 | #define M2 1 + 2 + defined + 3 | M2 | diff --git a/cpp/misra/test/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.qlref b/cpp/misra/test/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.qlref new file mode 100644 index 0000000000..733221320c --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.qlref @@ -0,0 +1 @@ +rules/RULE-19-1-1/DefinedOperatorExpandedInIfDirective.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.expected b/cpp/misra/test/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.expected new file mode 100644 index 0000000000..7ccfd6fe82 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.expected @@ -0,0 +1,3 @@ +| test.cpp:11:1:11:11 | #if defined | Invalid use of defined operator in if directive. | +| test.cpp:13:1:13:26 | #if defined(M1) && defined | Invalid use of defined operator in if directive. | +| test.cpp:15:1:15:26 | #if defined && defined(M1) | Invalid use of defined operator in if directive. | diff --git a/cpp/misra/test/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.qlref b/cpp/misra/test/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.qlref new file mode 100644 index 0000000000..4a0f690bfd --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-1-1/InvalidTokenInDefinedOperator.qlref @@ -0,0 +1 @@ +rules/RULE-19-1-1/InvalidTokenInDefinedOperator.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-1-1/test.cpp b/cpp/misra/test/rules/RULE-19-1-1/test.cpp new file mode 100644 index 0000000000..29a5843c12 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-1-1/test.cpp @@ -0,0 +1,48 @@ +#if defined M1 // COMPLIANT +#endif +#if defined(M1) // COMPLIANT +#endif +#if defined(M1) // COMPLIANT +#endif +#if defined M1 && defined M2 // COMPLIANT +#endif +#if defined(M1) && defined(M2) // COMPLIANT +#endif +#if defined // NON-COMPLIANT +#endif +#if defined(M1) && defined // NON-COMPLIANT +#endif +#if defined && defined(M1) // NON-COMPLIANT +#endif +// Compliant, there are no keywords in the context of the preprocessor, only +// identifiers. Therefore, 'new' is a valid identifier. +#if defined new // COMPLIANT +#endif +#if defined(new) // COMPLIANT +#endif + +// These cases don't compile in default tests, but may on other compilers +// #if defined 1 // NON-COMPLIANT +// #endif +// #if defined ( 1 ) // NON-COMPLIANT +// #endif +// #if defined + // NON-COMPLIANT +// #endif +// #if defined ( + ) // NON-COMPLIANT +// #endif + +#define M1 defined +#define M2 1 + 2 + defined + 3 +#define M3 M2 +#define M4 1 + 2 + 3 +#define M5 M4 +#if M1 // NON-COMPLIANT +#endif +#if M2 // NON-COMPLIANT +#endif +#if M3 // NON-COMPLIANT +#endif +#if M4 // COMPLIANT +#endif +#if M5 // COMPLIANT +#endif diff --git a/cpp/misra/test/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.expected b/cpp/misra/test/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.expected new file mode 100644 index 0000000000..73ce7d7a52 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.expected @@ -0,0 +1,9 @@ +| invalid1.h:0:0:0:0 | invalid1.h | File does not have a well formatted include guard. | +| invalid2.h:0:0:0:0 | invalid2.h | File does not have a well formatted include guard. | +| invalid3.h:0:0:0:0 | invalid3.h | File does not have a well formatted include guard. | +| invalid4.h:0:0:0:0 | invalid4.h | File does not have a well formatted include guard. | +| invalid5_file2.h:0:0:0:0 | invalid5_file2.h | File does not have a well formatted include guard. | +| invalid6_b.h:0:0:0:0 | invalid6_b.h | File does not have a well formatted include guard. | +| invalid7.h:0:0:0:0 | invalid7.h | File does not have a well formatted include guard. | +| invalid8.h:0:0:0:0 | invalid8.h | File does not have a well formatted include guard. | +| invalid9.h:0:0:0:0 | invalid9.h | File does not have a well formatted include guard. | diff --git a/cpp/misra/test/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.qlref b/cpp/misra/test/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.qlref new file mode 100644 index 0000000000..2857bfe31f --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/NoValidIfdefGuardInHeader.qlref @@ -0,0 +1 @@ +rules/RULE-19-2-1/NoValidIfdefGuardInHeader.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid1.h b/cpp/misra/test/rules/RULE-19-2-1/invalid1.h new file mode 100644 index 0000000000..ed9b55508c --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid1.h @@ -0,0 +1,6 @@ +#ifndef MISSPELLED +#define MISPELED + +void invalid1_f1(); + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid2.h b/cpp/misra/test/rules/RULE-19-2-1/invalid2.h new file mode 100644 index 0000000000..c2ebad359f --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid2.h @@ -0,0 +1,7 @@ +#ifdef INVALID2_H +#define INVALID2_H + +void invalid2_f1(); +// invalid: uses ifdef, not ifndef + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid3.h b/cpp/misra/test/rules/RULE-19-2-1/invalid3.h new file mode 100644 index 0000000000..cfac97ead4 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid3.h @@ -0,0 +1,6 @@ +#ifndef INVALID3_H +#define INVALID3_H + +#endif + +void invalid3_f1(); // NON-COMPLIANT -- outside of guard condition \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid4.h b/cpp/misra/test/rules/RULE-19-2-1/invalid4.h new file mode 100644 index 0000000000..002e4308bd --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid4.h @@ -0,0 +1,14 @@ +#ifndef INVALID4_H1 +#define INVALID4_H1 + +void invalid4_f1(); + +#endif + +#ifndef INVALID4_H2 +#define INVALID4_H2 + +// NON-COMPLIANT -- There should not be two inclusion guards in one file. +void invalid4_f1(); + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid5.h b/cpp/misra/test/rules/RULE-19-2-1/invalid5.h new file mode 100644 index 0000000000..79d7aaf838 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid5.h @@ -0,0 +1,6 @@ +#ifndef INVALID5_H +#define INVALID5_H + +#include "invalid5_file2.h" + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid5_file2.h b/cpp/misra/test/rules/RULE-19-2-1/invalid5_file2.h new file mode 100644 index 0000000000..ef548fb872 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid5_file2.h @@ -0,0 +1,2 @@ +void invalid5_f1(); // NON-COMPLIANT -- guarded from invalid5.h but not in + // current file \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid6_a.h b/cpp/misra/test/rules/RULE-19-2-1/invalid6_a.h new file mode 100644 index 0000000000..97aba7e3e6 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid6_a.h @@ -0,0 +1,7 @@ +// NON-COMPLIANT -- same inclusion guard identifier as invalid6_b.h +#ifndef INVALID6_H +#define INVALID6_H + +void invalid6_f1(); + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid6_b.h b/cpp/misra/test/rules/RULE-19-2-1/invalid6_b.h new file mode 100644 index 0000000000..43bf772174 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid6_b.h @@ -0,0 +1,7 @@ +// NON-COMPLIANT -- same inclusion guard identifier as invalid6_a.h +#ifndef INVALID6_H +#define INVALID6_H + +void invalid6_f2(); + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid7.h b/cpp/misra/test/rules/RULE-19-2-1/invalid7.h new file mode 100644 index 0000000000..e45f13886e --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid7.h @@ -0,0 +1,2 @@ +// Empty file +// NON-COMPLIANT -- no inclusion guard \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid8.h b/cpp/misra/test/rules/RULE-19-2-1/invalid8.h new file mode 100644 index 0000000000..2dc25f09cd --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid8.h @@ -0,0 +1,8 @@ +#include "valid1.h" // NON-COMPLIANT -- contents not in inclusion guard + +#ifndef INVALID8_H +#define INVALID8_H + +void invalid8_f1(); + +#endif diff --git a/cpp/misra/test/rules/RULE-19-2-1/invalid9.h b/cpp/misra/test/rules/RULE-19-2-1/invalid9.h new file mode 100644 index 0000000000..50d89c22b5 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/invalid9.h @@ -0,0 +1,7 @@ +#ifndef INVALID9_H + +void invalid9_f1(); + +// NON-COMPLIANT -- define is not at top of inclusion guard +#define INVALID9_H +#endif diff --git a/cpp/misra/test/rules/RULE-19-2-1/test.cpp b/cpp/misra/test/rules/RULE-19-2-1/test.cpp new file mode 100644 index 0000000000..d80bc89498 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/test.cpp @@ -0,0 +1,14 @@ +#include "invalid1.h" +#include "invalid2.h" +#include "invalid3.h" +#include "invalid4.h" +#include "invalid5.h" +#include "invalid6_a.h" +#include "invalid6_b.h" +#include "invalid7.h" +#include "invalid8.h" +#include "invalid9.h" +#include "valid1.h" +#include "valid2.h" + +void f() {} // COMPLIANT -- not in header, no inclusion guard necessary \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/valid1.h b/cpp/misra/test/rules/RULE-19-2-1/valid1.h new file mode 100644 index 0000000000..e924cac32f --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/valid1.h @@ -0,0 +1,8 @@ +// COMPLIANT + +#ifndef VALID1_H +#define VALID1_H + +void valid1_f1(); + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-1/valid2.h b/cpp/misra/test/rules/RULE-19-2-1/valid2.h new file mode 100644 index 0000000000..653ec0c249 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-1/valid2.h @@ -0,0 +1,8 @@ +// COMPLIANT + +#if !defined(VALID2_H) +#define VALID2_H + +void valid2_f1(); + +#endif \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-2/InvalidIncludeDirective.expected b/cpp/misra/test/rules/RULE-19-2-2/InvalidIncludeDirective.expected new file mode 100644 index 0000000000..4e85513b0e --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-2/InvalidIncludeDirective.expected @@ -0,0 +1,2 @@ +| test.cpp:6:1:6:20 | #include STRING_PATH | Non-compliant #include directive text 'STRING_PATH'. | +| test.cpp:10:1:10:16 | #include QSTRING | Non-compliant #include directive text 'QSTRING'. | diff --git a/cpp/misra/test/rules/RULE-19-2-2/InvalidIncludeDirective.qlref b/cpp/misra/test/rules/RULE-19-2-2/InvalidIncludeDirective.qlref new file mode 100644 index 0000000000..6268670c9b --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-2/InvalidIncludeDirective.qlref @@ -0,0 +1 @@ +rules/RULE-19-2-2/InvalidIncludeDirective.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-2-2/test.cpp b/cpp/misra/test/rules/RULE-19-2-2/test.cpp new file mode 100644 index 0000000000..b1788458ec --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-2-2/test.cpp @@ -0,0 +1,15 @@ +#define QSTRING "string" +#define DOTH .h +#define STRING_PATH "string.h" +#include "string.h" // COMPLIANT +#include // COMPLIANT +#include STRING_PATH // NON-COMPLIANT +// clang-format off +#include "string" ".h" // NON-COMPLIANT[False negative] +// clang-format on +#include QSTRING DOTH // NON-COMPLIANT + +// Invalid directives: +// #include string.h // NON-COMPLIANT +// #include // NON-COMPLIANT +// #include 1 + 1 // NON-COMPLIANT \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-3-4/UnparenthesizedMacroArgument.expected b/cpp/misra/test/rules/RULE-19-3-4/UnparenthesizedMacroArgument.expected new file mode 100644 index 0000000000..14e4307e5c --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-3-4/UnparenthesizedMacroArgument.expected @@ -0,0 +1,43 @@ +| test.cpp:36:3:36:11 | M1(X) | Macro argument 0 (with expanded value '1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:38:3:38:11 | M1(X) | Macro argument 0 (with expanded value '1 / 1') contains a critical operator '/' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:40:3:40:11 | M1(X) | Macro argument 0 (with expanded value '1 % 1') contains a critical operator '%' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:43:3:43:11 | M1(X) | Macro argument 0 (with expanded value '1 + 1') contains a critical operator '+' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:45:3:45:11 | M1(X) | Macro argument 0 (with expanded value '1 - 1') contains a critical operator '-' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:48:3:48:12 | M1(X) | Macro argument 0 (with expanded value '1 << 1') contains a critical operator '<<' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:50:3:50:12 | M1(X) | Macro argument 0 (with expanded value '1 >> 1') contains a critical operator '>>' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:53:3:53:11 | M1(X) | Macro argument 0 (with expanded value '1 < 1') contains a critical operator '<' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:55:3:55:11 | M1(X) | Macro argument 0 (with expanded value '1 > 1') contains a critical operator '>' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:57:3:57:12 | M1(X) | Macro argument 0 (with expanded value '1 <= 1') contains a critical operator '<=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:59:3:59:12 | M1(X) | Macro argument 0 (with expanded value '1 >= 1') contains a critical operator '>=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:62:3:62:12 | M1(X) | Macro argument 0 (with expanded value '1 == 1') contains a critical operator '==' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:64:3:64:12 | M1(X) | Macro argument 0 (with expanded value '1 != 1') contains a critical operator '!=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:67:3:67:11 | M1(X) | Macro argument 0 (with expanded value '1 & 1') contains a critical operator '&' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:70:3:70:11 | M1(X) | Macro argument 0 (with expanded value '1 ^ 1') contains a critical operator '^' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:73:3:73:11 | M1(X) | Macro argument 0 (with expanded value '1 \| 1') contains a critical operator '\|' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:76:3:76:12 | M1(X) | Macro argument 0 (with expanded value '1 && 1') contains a critical operator '&&' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:79:3:79:12 | M1(X) | Macro argument 0 (with expanded value '1 \|\| 1') contains a critical operator '\|\|' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:82:3:82:15 | M1(X) | Macro argument 0 (with expanded value '1 ? 1 : 1') contains a critical operator '?' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:85:3:85:12 | M1(X) | Macro argument 0 (with expanded value 'g3 = 1') contains a critical operator '=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:87:3:87:13 | M1(X) | Macro argument 0 (with expanded value 'g3 += 1') contains a critical operator '+=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:89:3:89:13 | M1(X) | Macro argument 0 (with expanded value 'g3 -= 1') contains a critical operator '-=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:91:3:91:13 | M1(X) | Macro argument 0 (with expanded value 'g3 *= 1') contains a critical operator '*=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:93:3:93:13 | M1(X) | Macro argument 0 (with expanded value 'g3 /= 1') contains a critical operator '/=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:95:3:95:13 | M1(X) | Macro argument 0 (with expanded value 'g3 %= 1') contains a critical operator '%=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:97:3:97:14 | M1(X) | Macro argument 0 (with expanded value 'g3 <<= 1') contains a critical operator '<<=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:99:3:99:14 | M1(X) | Macro argument 0 (with expanded value 'g3 >>= 1') contains a critical operator '>>=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:101:3:101:13 | M1(X) | Macro argument 0 (with expanded value 'g3 &= 1') contains a critical operator '&=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:103:3:103:13 | M1(X) | Macro argument 0 (with expanded value 'g3 ^= 1') contains a critical operator '^=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:105:3:105:13 | M1(X) | Macro argument 0 (with expanded value 'g3 \|= 1') contains a critical operator '\|=' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:138:3:138:14 | M4(PROTECTED,UNPROTECTED) | Macro argument 1 (with expanded value '1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'UNPROTECTED' is not precedence-protected with parenthesis. | test.cpp:136:1:136:59 | #define M4(PROTECTED,UNPROTECTED) (PROTECTED), UNPROTECTED | M4 | +| test.cpp:142:3:142:10 | M1(X) | Macro argument 0 (with expanded value '1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:152:3:152:13 | M1(X) | Macro argument 0 (with expanded value '1 * (1)') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:153:3:153:13 | M1(X) | Macro argument 0 (with expanded value '(1) * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:154:3:154:15 | M1(X) | Macro argument 0 (with expanded value '(1) * (1)') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:155:3:155:17 | M1(X) | Macro argument 0 (with expanded value '(1 * 1) * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:156:3:156:17 | M1(X) | Macro argument 0 (with expanded value '1 * (1 * 1)') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:11:1:11:15 | #define M1(X) X | M1 | +| test.cpp:170:3:170:15 | M6(NAME,INIT) | Macro argument 1 (with expanded value '1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'INIT' is not precedence-protected with parenthesis. | test.cpp:169:1:169:39 | #define M6(NAME,INIT) int NAME = INIT; | M6 | +| test.cpp:178:3:178:22 | M7(TYPE,NAME,INIT) | Macro argument 2 (with expanded value '= 1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'INIT' is not precedence-protected with parenthesis. | test.cpp:174:1:174:44 | #define M7(TYPE,NAME,INIT) TYPE NAME INIT; | M7 | +| test.cpp:229:3:229:19 | M10(X) | Macro argument 0 (with expanded value '"foo"[1 + 1]') contains a critical operator '+' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:197:1:201:32 | #define M10(X) (1 + 1, 1 - 1, 1 * 1, 1 / 1, 1 % 1, 1 << 1, 1 >> 1, 1 < 1, 1 > 1, 1 <= 1, 1 >= 1, 1 == 1, 1 != 1, 1 & 1, 1 ^ 1, 1 \| 1, g3 = 1, g3 += 1, g3 -= 1, g3 *= 1, g3 /= 1, g3 %= 1, g3 <<= 1, g3 >>= 1, g3 &= 1, g3 ^= 1, g3 \|= 1, 1 && 1, 1 \|\| 1, 1 ? 1 : 1, X) | M10 | +| test.cpp:235:3:235:15 | M11(X,Y) | Macro argument 0 (with expanded value '1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:234:1:234:26 | #define M11(X,Y) f3(X, Y) | M11 | +| test.cpp:239:3:240:8 | M13(X,Y) | Macro argument 0 (with expanded value '1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:238:1:238:27 | #define M13(X,Y) M12(X, Y) | M13 | +| test.cpp:253:3:254:8 | M17(X) | Macro argument 0 (with expanded value '1 * 1') contains a critical operator '*' that is not parenthesized, but macro $@ argument 'X' is not precedence-protected with parenthesis. | test.cpp:252:1:252:25 | #define M17(X) M15(X, #X) | M17 | diff --git a/cpp/misra/test/rules/RULE-19-3-4/UnparenthesizedMacroArgument.qlref b/cpp/misra/test/rules/RULE-19-3-4/UnparenthesizedMacroArgument.qlref new file mode 100644 index 0000000000..ca25e5a62a --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-3-4/UnparenthesizedMacroArgument.qlref @@ -0,0 +1 @@ +rules/RULE-19-3-4/UnparenthesizedMacroArgument.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-3-4/test.cpp b/cpp/misra/test/rules/RULE-19-3-4/test.cpp new file mode 100644 index 0000000000..027219961d --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-3-4/test.cpp @@ -0,0 +1,263 @@ +#include +void f1(); +void f2(int x); +void f3(int x, int y); +struct S1 { + int x; +} g1, *g2; +int g3; + +void f4() { +#define M1(X) X + // No critical operators: + M1(1); // COMPLIANT + M1((1)); // COMPLIANT + M1("foo"); // COMPLIANT + M1(("foo")); // COMPLIANT + M1(f1()); // COMPLIANT + M1((f1)); // COMPLIANT + M1(*"foo"); // COMPLIANT + M1(!1); // COMPLIANT + M1(+1); // COMPLIANT + M1(-1); // COMPLIANT + M1("foo"[1]); // COMPLIANT + M1(&"foo"); // COMPLIANT + M1(g1.x); // COMPLIANT + M1(g2->x); // COMPLIANT + M1(g1.x++); // COMPLIANT + M1(++g1.x); // COMPLIANT + M1(g1.x--); // COMPLIANT + M1(--g1.x); // COMPLIANT + // M1(1, 1); -- not interpreted as a comma operator + M1((1, 1)); // COMPLIANT + M1(throw 1); // COMPLIANT + + // Level 13 operators: + M1(1 * 1); // NON-COMPLIANT + M1((1 * 1)); // COMPLIANT + M1(1 / 1); // NON-COMPLIANT + M1((1 / 1)); // COMPLIANT + M1(1 % 1); // NON-COMPLIANT + M1((1 % 1)); // COMPLIANT + // Level 12 operators: + M1(1 + 1); // NON-COMPLIANT + M1((1 + 1)); // COMPLIANT + M1(1 - 1); // NON-COMPLIANT + M1((1 - 1)); // COMPLIANT + // Level 11 operators: + M1(1 << 1); // NON-COMPLIANT + M1((1 << 1)); // COMPLIANT + M1(1 >> 1); // NON-COMPLIANT + M1((1 >> 1)); // COMPLIANT + // Level 10 operators: + M1(1 < 1); // NON-COMPLIANT + M1((1 < 1)); // COMPLIANT + M1(1 > 1); // NON-COMPLIANT + M1((1 > 1)); // COMPLIANT + M1(1 <= 1); // NON-COMPLIANT + M1((1 <= 1)); // COMPLIANT + M1(1 >= 1); // NON-COMPLIANT + M1((1 >= 1)); // COMPLIANT + // Level 9 operators: + M1(1 == 1); // NON-COMPLIANT + M1((1 == 1)); // COMPLIANT + M1(1 != 1); // NON-COMPLIANT + M1((1 != 1)); // COMPLIANT + // Level 8 operators: + M1(1 & 1); // NON-COMPLIANT + M1((1 & 1)); // COMPLIANT + // Level 7 operators: + M1(1 ^ 1); // NON-COMPLIANT + M1((1 ^ 1)); // COMPLIANT + // Level 6 operators: + M1(1 | 1); // NON-COMPLIANT + M1((1 | 1)); // COMPLIANT + // Level 5 operators: + M1(1 && 1); // NON-COMPLIANT + M1((1 && 1)); // COMPLIANT + // Level 4 operators: + M1(1 || 1); // NON-COMPLIANT + M1((1 || 1)); // COMPLIANT + // Level 3 operators: + M1(1 ? 1 : 1); // NON-COMPLIANT + M1((1 ? 1 : 1)); // COMPLIANT + // Level 2 operators: + M1(g3 = 1); // NON-COMPLIANT + M1((g3 = 1)); // COMPLIANT + M1(g3 += 1); // NON-COMPLIANT + M1((g3 += 1)); // COMPLIANT + M1(g3 -= 1); // NON-COMPLIANT + M1((g3 -= 1)); // COMPLIANT + M1(g3 *= 1); // NON-COMPLIANT + M1((g3 *= 1)); // COMPLIANT + M1(g3 /= 1); // NON-COMPLIANT + M1((g3 /= 1)); // COMPLIANT + M1(g3 %= 1); // NON-COMPLIANT + M1((g3 %= 1)); // COMPLIANT + M1(g3 <<= 1); // NON-COMPLIANT + M1((g3 <<= 1)); // COMPLIANT + M1(g3 >>= 1); // NON-COMPLIANT + M1((g3 >>= 1)); // COMPLIANT + M1(g3 &= 1); // NON-COMPLIANT + M1((g3 &= 1)); // COMPLIANT + M1(g3 ^= 1); // NON-COMPLIANT + M1((g3 ^= 1)); // COMPLIANT + M1(g3 |= 1); // NON-COMPLIANT + M1((g3 |= 1)); // COMPLIANT + // Level 1 and below are not critical operators, tested above. + +// Precedence-protected macro: +#define M2(X) (X) + M2(1 * 1); // COMPLIANT + M2(1 + 1); // COMPLIANT + M2(1 - 1); // COMPLIANT + M2(1 << 1); // COMPLIANT + M2(1 >> 1); // COMPLIANT + M2(1 < 1); // COMPLIANT + M2(1 > 1); // COMPLIANT + M2(1 <= 1); // COMPLIANT + M2(1 >= 1); // COMPLIANT + M2(1 == 1); // COMPLIANT + M2(1 != 1); // COMPLIANT + M2(1 & 1); // COMPLIANT + M2(1 ^ 1); // COMPLIANT + M2(1 | 1); // COMPLIANT + M2(1 && 1); // COMPLIANT + M2(1 || 1); // COMPLIANT + M2(1 ? 1 : 1); // COMPLIANT + +// Macro that uses the # operator: +#define M3(X) #X + M3(1 * 1); // COMPLIANT + M3(1 == 1); // COMPLIANT + M3(1 ? 1 : 1); // COMPLIANT + +// Multi-argument macro: +#define M4(PROTECTED, UNPROTECTED) (PROTECTED), UNPROTECTED + M4(1 * 1, 1); // COMPLIANT + M4(1, 1 * 1); // NON-COMPLIANT + +// Macro passed into a macro: +#define M5() 1 * 1 + M1(M5()); // NON-COMPLIANT + M1((M5())); // COMPLIANT + + // Macro with function call: + M1(f2(1 * 1)); // COMPLIANT + + // Macro with a string literal: + M1("1 * 1"); // COMPLIANT + + // Macro with lots of silly parentheses: + M1(1 * (1)); // NON-COMPLIANT + M1((1) * 1); // NON-COMPLIANT + M1((1) * (1)); // NON-COMPLIANT + M1((1 * 1) * 1); // NON-COMPLIANT + M1(1 * (1 * 1)); // NON-COMPLIANT + M1(((1 * 1))); // COMPLIANT + M1(((1) * (1))); // COMPLIANT + +// Macros with unbalanced parenthesis: +#define OP_PAREN ( +#define CL_PAREN ) + M1(OP_PAREN 1 * 1) + CL_PAREN; // COMPLIANT -- by rule description, not top level operator. + OP_PAREN M1(1 CL_PAREN * 1); // NON-COMPLIANT[False negative] -- by rule + // description, top level operator. + +// Macro expanding to a variable declaration: +#define M6(NAME, INIT) int NAME = INIT; + M6(l1, 1 * 1); // NON-COMPLIANT + M6(l2, (1 * 1)); // COMPLIANT + +// Macro expanding to a type: +#define M7(TYPE, NAME, INIT) TYPE NAME INIT; + M7(int, l3, ); // COMPLIANT + M7(std::vector, l4, ); // COMPLIANT + M7(int, l5, = 0); // COMPLIANT + M7(int, l6, = 1 * 1); // NON-COMPLIANT + M7(std::vector, l7, = std::vector()); // COMPLIANT +} + +// Macro expanding to a function declaration -- We cannot confidently analyze +// this case. +#define M8(NAME, BODY) \ + void NAME() { BODY * 2; } +M8(f5, 2 + 2) // NON-COMPLIANT[False negative] + +// Macro expanding to a class declaration -- We cannot confidently analyze this +// case. +#define M9(NAME, BODY) \ + class NAME { \ + int x = BODY * 2; \ + }; +M9(C1, 2 + 2) // NON-COMPLIANT[False negative] + +// A macro containing every critical operator: +#define M10(X) \ + (1 + 1, 1 - 1, 1 * 1, 1 / 1, 1 % 1, 1 << 1, 1 >> 1, 1 < 1, 1 > 1, 1 <= 1, \ + 1 >= 1, 1 == 1, 1 != 1, 1 & 1, 1 ^ 1, 1 | 1, g3 = 1, g3 += 1, g3 -= 1, \ + g3 *= 1, g3 /= 1, g3 %= 1, g3 <<= 1, g3 >>= 1, g3 &= 1, g3 ^= 1, g3 |= 1, \ + 1 && 1, 1 || 1, 1 ? 1 : 1, X) + +void f6() { + // No critical operators in macro arguments, handled correctly: + M10(1); // COMPLIANT -- we can tell no operators are in the macro argument + M10(g2->x); // COMPLIANT -- we can tell this isn't a lt operator + M10(g3++); // COMPLIANT -- we can tell this isn't a binary + operator + M10(g3--); // COMPLIANT -- we can tell this isn't a binary - operator + M10("1 * 1"); // COMPLIANT -- we can tell this is in a string literal + M10("*"[0] * 1); // NON-COMPLIANT[False negative] --- falsely suppressed + + // Guess that operators are unary based on the first character: + M10(*"foo"); // COMPLIANT + M10(&"foo"); // COMPLIANT + M10(-1); // COMPLIANT + M10(+1); // COMPLIANT + + // No critical operators in macro arguments, handled incorrectly: + // These falsely look to us like the critical operators are in the macro + // arguments, but they are not. + + // Compliant, we know the operators are parenthesized and cannot be + // responsible for the critical operators generated by the macro. + M10((1 * 1)); // COMPLIANT + M10((1 - 1)); // COMPLIANT + M10((1 == 1)); // COMPLIANT + + // Remaining known false positive cases + M10("foo"[1 + 1]); // NON-COMPLIANT -- by rule description, this is a top + // level operator. We also don't handle parsing matching + // brackets like this currently. But in practice, this is + // not a top level operator. + +#define M11(X, Y) f3(X, Y) + M11(1 * 1, 1); // NON-COMPLIANT by rule, but not a dangerous case. + +#define M12(X, Y) (X) + (Y) +#define M13(X, Y) M12(X, Y) + M13(1 * 1, + 1); // NON-COMPLIANT -- the rule text suggests that this is non + // compliant, though an argument could be made the other way. + // Regardless, this case is diffucult for us to detect. +#define M14(X) M1(X *X) + M14(1); // NON-COMPLIANT[False negative] -- The definition of M13 is + // non-compliant, but we don't detect the generated elements. + + // Trickier case of # operator to handle. In this case, we do not produce a + // string literal, which ordinarily is cause to suppress an alert. +#define M15(X, Y) X +#define M16(X) M15((X), #X) + M16(1 * 1); // COMPLIANT -- all expansions of X are precedence protected. +#define M17(X) M15(X, #X) + M17(1 * + 1); // NON-COMPLIANT -- not all expansions of X are precedence protected. + + // Spaces should not fool our analysis: + /* clang-format off */ +#define M18(X) ( X ) + M18(1 * 1); // COMPLIANT +#define M19(X) M15(( X ), # X) + M16(1 * 1); // COMPLIANT -- all expansions of X are precedence protected. + /* clang-format on */ +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-6-1/DisallowedUseOfPragma.expected b/cpp/misra/test/rules/RULE-19-6-1/DisallowedUseOfPragma.expected new file mode 100644 index 0000000000..297622c0e0 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-6-1/DisallowedUseOfPragma.expected @@ -0,0 +1,2 @@ +| test.cpp:1:1:1:41 | #pragma GCC diagnostic warning "-Wformat" | Non-compliant #pragma directive 'GCC diagnostic warning "-Wformat"'. | +| test.cpp:2:1:2:36 | #define TODO(X) _Pragma("TODO: " #X) | Non-compliant _Pragma operator used: '_Pragma("TODO: " #X)'. | diff --git a/cpp/misra/test/rules/RULE-19-6-1/DisallowedUseOfPragma.qlref b/cpp/misra/test/rules/RULE-19-6-1/DisallowedUseOfPragma.qlref new file mode 100644 index 0000000000..107efc6520 --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-6-1/DisallowedUseOfPragma.qlref @@ -0,0 +1 @@ +rules/RULE-19-6-1/DisallowedUseOfPragma.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-19-6-1/test.cpp b/cpp/misra/test/rules/RULE-19-6-1/test.cpp new file mode 100644 index 0000000000..ef65e34e0f --- /dev/null +++ b/cpp/misra/test/rules/RULE-19-6-1/test.cpp @@ -0,0 +1,4 @@ +#pragma GCC diagnostic warning "-Wformat" // NON-COMPLIANT +#define TODO(X) _Pragma("TODO: " #X) // NON-COMPLIANT +void not_Pragma(); +#define NOT_PRAGMA not_Pragma() // COMPLIANT diff --git a/rule_packages/cpp/Preprocessor.json b/rule_packages/cpp/Preprocessor.json new file mode 100644 index 0000000000..b379af2ff7 --- /dev/null +++ b/rule_packages/cpp/Preprocessor.json @@ -0,0 +1,83 @@ +{ + "MISRA-C++-2023": { + "RULE-19-0-4": { + "properties": { + "enforcement": "decidable", + "obligation": "advisory" + }, + "queries": [ + { + "description": "Using #undef to undefine a macro that is not defined in the same file can lead to confusion.", + "kind": "problem", + "name": "#undef should only be used for macros defined previously in the same file", + "precision": "very-high", + "severity": "warning", + "short_name": "UndefOfMacroNotDefinedInFile", + "tags": [ + "scope/single-translation-unit", + "readability", + "maintainability" + ] + } + ], + "title": "#undef should only be used for macros defined previously in the same file" + }, + "RULE-19-1-1": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Using the defined operator without an immediately following optionally parenthesized identifier results in undefined behavior.", + "kind": "problem", + "name": "The defined preprocessor operator shall be used appropriately", + "precision": "very-high", + "severity": "error", + "short_name": "InvalidTokenInDefinedOperator", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ] + }, + { + "description": "Macro expansions that produce the token 'defined' inside of an if directive result in undefined behavior.", + "kind": "problem", + "name": "The defined preprocessor operator shall be used appropriately", + "precision": "very-high", + "severity": "error", + "short_name": "DefinedOperatorExpandedInIfDirective", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ] + } + ], + "title": "The defined preprocessor operator shall be used appropriately" + }, + "RULE-19-2-1": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Precautions shall be taken in order to prevent the contents of a header file being included more than once.", + "kind": "problem", + "name": "Precautions shall be taken in order to prevent the contents of a header file being included more", + "precision": "very-high", + "severity": "error", + "short_name": "NoValidIfdefGuardInHeader", + "tags": [ + "scope/single-translation-unit", + "maintainability", + "correctness" + ] + } + ], + "title": "Precautions shall be taken in order to prevent the contents of a header file being included more than once" + } + } +} \ No newline at end of file diff --git a/rule_packages/cpp/Preprocessor2.json b/rule_packages/cpp/Preprocessor2.json new file mode 100644 index 0000000000..ed3d327b88 --- /dev/null +++ b/rule_packages/cpp/Preprocessor2.json @@ -0,0 +1,72 @@ +{ + "MISRA-C++-2023": { + "RULE-19-2-2": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Include directives shall only use the or \"filename\" forms.", + "kind": "problem", + "name": "The #include directive shall be followed by either a or \"filename\" sequence", + "precision": "very-high", + "severity": "warning", + "short_name": "InvalidIncludeDirective", + "tags": [ + "scope/single-translation-unit", + "maintainability", + "readability" + ] + } + ], + "title": "The #include directive shall be followed by either a or \"filename\" sequence" + }, + "RULE-19-3-4": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Expanded macro arguments shall be enclosed in parentheses to ensure the resulting expressions have the expected precedence and order of operations.", + "kind": "problem", + "name": "Parentheses shall be used to ensure macro arguments are expanded appropriately", + "precision": "high", + "severity": "error", + "short_name": "UnparenthesizedMacroArgument", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ], + "implementation_scope": { + "description": "A mixture of textual and syntactic analysis is used to identify the problem, with positional cues being required to infer unary *, &, +, or - and semantic clues suggesting when <, >, * may refer to a type. The presence of string literals and non-critical operators ++, --, -> or unmatched parenthesis may abort the analysis. Safe cases not exempted in the rule text such as comma-separated arguments or brackets are not exempted by this query." + } + } + ], + "title": "Parentheses shall be used to ensure macro arguments are expanded appropriately" + }, + "RULE-19-6-1": { + "properties": { + "enforcement": "decidable", + "obligation": "advisory" + }, + "queries": [ + { + "description": "Preprocessor pragma directives are implementation-defined, and should not be used to maintain code portability.", + "kind": "problem", + "name": "The #pragma directive and the _Pragma operator should not be used", + "precision": "very-high", + "severity": "warning", + "short_name": "DisallowedUseOfPragma", + "tags": [ + "scope/single-translation-unit", + "maintainability" + ] + } + ], + "title": "The #pragma directive and the _Pragma operator should not be used" + } + } +} \ No newline at end of file diff --git a/rules.csv b/rules.csv index 3f7961b630..e87b99b810 100644 --- a/rules.csv +++ b/rules.csv @@ -963,14 +963,14 @@ cpp,MISRA-C++-2023,RULE-19-1-1,Yes,Required,Decidable,Single Translation Unit,Th cpp,MISRA-C++-2023,RULE-19-1-2,No,Required,Decidable,Single Translation Unit,"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",M16-1-2,,, cpp,MISRA-C++-2023,RULE-19-1-3,Yes,Required,Decidable,Single Translation Unit,All identifiers used in the controlling expression of #if or #elif preprocessing directives shall be defined prior to evaluation,M16-0-7,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-19-2-1,Yes,Required,Decidable,Single Translation Unit,Precautions shall be taken in order to prevent the contents of a header file being included more than once,M16-2-3,Preprocessor,Easy, -cpp,MISRA-C++-2023,RULE-19-2-2,Yes,Required,Decidable,Single Translation Unit,"The #include directive shall be followed by either a or ""filename"" sequence",,Preprocessor,Easy, +cpp,MISRA-C++-2023,RULE-19-2-2,Yes,Required,Decidable,Single Translation Unit,"The #include directive shall be followed by either a or ""filename"" sequence",,Preprocessor2,Easy, cpp,MISRA-C++-2023,RULE-19-2-3,Yes,Required,Decidable,Single Translation Unit,"The ' or "" or \ characters and the /* or // character sequences shall not occur in a header file name",A16-2-1,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-19-3-1,Yes,Advisory,Decidable,Single Translation Unit,The # and ## preprocessor operators should not be used,M16-3-2,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-19-3-2,Yes,Required,Decidable,Single Translation Unit,A macro parameter immediately following a # operator shall not be immediately followed by a ## operator,RULE-20-11,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-19-3-3,Yes,Required,Decidable,Single Translation Unit,The argument to a mixed-use macro parameter shall not be subject to further expansion,RULE-20-12,ImportMisra23,Import, -cpp,MISRA-C++-2023,RULE-19-3-4,Yes,Required,Decidable,Single Translation Unit,Parentheses shall be used to ensure macro arguments are expanded appropriately,M16-0-6,Preprocessor,Medium, +cpp,MISRA-C++-2023,RULE-19-3-4,Yes,Required,Decidable,Single Translation Unit,Parentheses shall be used to ensure macro arguments are expanded appropriately,M16-0-6,Preprocessor2,Medium, cpp,MISRA-C++-2023,RULE-19-3-5,Yes,Required,Decidable,Single Translation Unit,Tokens that look like a preprocessing directive shall not occur within a macro argument,RULE-20-6,ImportMisra23,Import, -cpp,MISRA-C++-2023,RULE-19-6-1,Yes,Advisory,Decidable,Single Translation Unit,The #pragma directive and the _Pragma operator should not be used,A16-7-1,Preprocessor,Easy, +cpp,MISRA-C++-2023,RULE-19-6-1,Yes,Advisory,Decidable,Single Translation Unit,The #pragma directive and the _Pragma operator should not be used,A16-7-1,Preprocessor2,Easy, cpp,MISRA-C++-2023,RULE-21-2-1,Yes,Required,Decidable,Single Translation Unit,"The library functions atof, atoi, atol and atoll from shall not be used",RULE-21-7,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-21-2-2,Yes,Required,Decidable,Single Translation Unit,"The string handling functions from , , and shall not be used",M18-0-5,BannedAPIs,Easy, cpp,MISRA-C++-2023,RULE-21-2-3,Yes,Required,Decidable,Single Translation Unit,The library function system from shall not be used,M18-0-3,BannedAPIs,Easy,