Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@
"Preprocessor2",
"Preprocessor3",
"Preprocessor4",
"Preprocessor5",
"IntegerConversion",
"Expressions",
"DeadCode",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# MSC38-C: Do not treat a predefined identifier as an object if it might only be implemented as a macro
Comment thread
knewbury01 marked this conversation as resolved.

This query implements the CERT-C rule MSC38-C:

> Do not treat a predefined identifier as an object if it might only be implemented as a macro

## CERT

** REPLACE THIS BY RUNNING THE SCRIPT `scripts/help/cert-help-extraction.py` **

## Implementation notes

None

## References

* CERT-C: [MSC38-C: Do not treat a predefined identifier as an object if it might only be implemented as a macro](https://wiki.sei.cmu.edu/confluence/display/c)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @id c/cert/do-not-treat-a-predefined-identifier-as-object
* @name MSC38-C: Do not treat a predefined identifier as an object if it might only be implemented as a macro
* @description Accessing an object or function that expands to one of a few specific standard
* library macros is undefined behaviour.
* @kind problem
* @precision very-high
* @problem.severity warning
* @tags external/cert/id/msc38-c
* correctness
* readability
* external/cert/obligation/rule
*/

import cpp
import codingstandards.c.cert

predicate hasRestrictedMacroName(string s) {
s = "assert"
or
s = "errno"
or
s = "math_errhandling"
or
s = "setjmp"
or
s = "va_arg"
or
s = "va_copy"
or
s = "va_end"
or
s = "va_start"
}

from Element m, string name
where
not isExcluded(m, Preprocessor5Package::doNotTreatAPredefinedIdentifierAsObjectQuery()) and
(
m.(Access).getTarget().hasName(name)
or
m.(Declaration).hasGlobalName(name)
) and
hasRestrictedMacroName(name)
select m, "Supression of standard library macro " + name + "."
17 changes: 17 additions & 0 deletions c/cert/src/rules/PRE32-C/MacroOrFunctionArgsContainHashToken.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# PRE32-C: Do not use preprocessor directives in invocations of function-like macros
Comment thread
knewbury01 marked this conversation as resolved.

This query implements the CERT-C rule PRE32-C:

> Do not use preprocessor directives in invocations of function-like macros

## CERT

** REPLACE THIS BY RUNNING THE SCRIPT `scripts/help/cert-help-extraction.py` **

## Implementation notes

None

## References

* CERT-C: [PRE32-C: Do not use preprocessor directives in invocations of function-like macros](https://wiki.sei.cmu.edu/confluence/display/c)
58 changes: 58 additions & 0 deletions c/cert/src/rules/PRE32-C/MacroOrFunctionArgsContainHashToken.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @id c/cert/macro-or-function-args-contain-hash-token
* @name PRE32-C: Do not use preprocessor directives in invocations of function-like macros
* @description Arguments to a function-like macros shall not contain tokens that look like
* pre-processing directives or else behaviour after macro expansion is unpredictable.
* This rule applies to functions as well in case they are implemented using macros.
* @kind problem
* @precision high
* @problem.severity warning
* @tags external/cert/id/pre32-c
* correctness
* readability
* external/cert/obligation/rule
*/

import cpp
import codingstandards.c.cert
import codingstandards.cpp.PreprocessorDirective

pragma[noinline]
predicate isFunctionInvocationLocation(FunctionCall call, File f, int startline, int endline) {
call.getLocation().hasLocationInfo(f.getAbsolutePath(), startline, _, _, _) and
//for all function calls the closest location heurisitc we have for end line is the next node in cfg
call.getASuccessor().getLocation().hasLocationInfo(f.getAbsolutePath(), endline, _, _, _)
}

PreprocessorDirective isLocatedInAFunctionInvocation(FunctionCall c) {
exists(PreprocessorDirective p, File f, int startCall, int endCall |
isFunctionInvocationLocation(c, f, startCall, endCall) and
exists(int startLine, int endLine | isPreprocDirectiveLine(p, f, startLine, endLine) |
startCall < startLine and
startCall < endLine and
endLine <= endCall and
endLine <= endCall
) and
result = p
)
}

from PreprocessorDirective p, string msg
where
not isExcluded(p, Preprocessor5Package::macroOrFunctionArgsContainHashTokenQuery()) and
exists(FunctionCall c |
p = isLocatedInAFunctionInvocation(c) and
//if this is actually a function in a macro ignore it because it will still be seen in the macro condition
not c.isInMacroExpansion() and
msg =
"Invocation of function " + c.getTarget().getName() + " includes a token \"" + p +
"\" that could be confused for an argument preprocessor directive."
)
or
exists(MacroInvocation m |
p = isLocatedInAMacroInvocation(m) and
msg =
"Invocation of macro " + m.getMacroName() + " includes a token \"" + p +
"\" that could be confused for an argument preprocessor directive."
)
select p, msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
| test.c:3:12:3:16 | errno | Supression of standard library macro errno. |
| test.c:10:21:10:26 | assert | Supression of standard library macro assert. |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rules/MSC38-C/DoNotTreatAPredefinedIdentifierAsObject.ql
17 changes: 17 additions & 0 deletions c/cert/test/rules/MSC38-C/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <assert.h>

extern int errno; // NON_COMPLIANT

typedef void (*handler_type)(int);

void execute_handler(handler_type handler, int value) { handler(value); }

void f(int e) {
execute_handler(&(assert), e < 0); // NON_COMPLIANT
}

static void assert_handler(int value) { assert(value); }

void f2(int e) {
execute_handler(&assert_handler, e < 0); // COMPLIANT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
| test.c:9:1:9:19 | #if NOTDEFINEDMACRO | Invocation of macro MACROFUNCTION includes a token "#if NOTDEFINEDMACRO" that could be confused for an argument preprocessor directive. |
| test.c:11:1:11:5 | #else | Invocation of macro MACROFUNCTION includes a token "#else" that could be confused for an argument preprocessor directive. |
| test.c:13:1:13:6 | #endif | Invocation of macro MACROFUNCTION includes a token "#endif" that could be confused for an argument preprocessor directive. |
| test.c:20:1:20:16 | #ifdef SOMEMACRO | Invocation of function memcpy includes a token "#ifdef SOMEMACRO" that could be confused for an argument preprocessor directive. |
| test.c:22:1:22:5 | #else | Invocation of function memcpy includes a token "#else" that could be confused for an argument preprocessor directive. |
| test.c:24:1:24:6 | #endif | Invocation of function memcpy includes a token "#endif" that could be confused for an argument preprocessor directive. |
| test.c:27:1:27:8 | #if TEST | Invocation of function memcpy includes a token "#if TEST" that could be confused for an argument preprocessor directive. |
| test.c:28:1:28:6 | #endif | Invocation of function memcpy includes a token "#endif" that could be confused for an argument preprocessor directive. |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rules/PRE32-C/MacroOrFunctionArgsContainHashToken.ql
29 changes: 29 additions & 0 deletions c/cert/test/rules/PRE32-C/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

#include <string.h>
#define MACROFUNCTION(X) strlen(X)

void func(const char *src) {
char *dest;

MACROFUNCTION(
#if NOTDEFINEDMACRO // NON_COMPLIANT
"longstringtest!test!"
#else // NON_COMPLIANT
"shortstring"
#endif // NON_COMPLIANT
);

MACROFUNCTION("alright"); // COMPLIANT
memcpy(dest, src, 12); // COMPLIANT

memcpy(dest, src,
#ifdef SOMEMACRO // NON_COMPLIANT
12
#else // NON_COMPLIANT
24
#endif // NON_COMPLIANT
);

#if TEST // COMPLIANT[FALSE_POSITIVE]
#endif // COMPLIANT[FALSE_POSITIVE]
}
1 change: 1 addition & 0 deletions c/common/test/includes/standard-library/assert.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <features.h>

#undef assert
void assert(int i);
Comment thread
knewbury01 marked this conversation as resolved.
Outdated

#ifdef NDEBUG
#define assert(x) (void)0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| test.c:1:1:1:27 | #define MACRO(X,Y) (X * Y) | Accesses of parameter 'X' are not always enclosed in parentheses in the macro MACRO. |
| test.c:1:1:1:27 | #define MACRO(X,Y) (X * Y) | Accesses of parameter 'Y' are not always enclosed in parentheses in the macro MACRO. |
| test.c:4:1:4:29 | #define MACROFOUR(X,Y) (X).Y | Accesses of parameter 'Y' are not always enclosed in parentheses in the macro MACROFOUR. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// GENERATED FILE - DO NOT MODIFY
import codingstandards.cpp.rules.macroparameternotenclosedinparentheses.MacroParameterNotEnclosedInParentheses
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#define MACRO(X, Y) (X * Y) // NON_COMPLIANT
#define MACROTWO(X, Y) ((X) * (Y)) // COMPLIANT
#define MACROTHREE(X) a##X *(X) // COMPLIANT
#define MACROFOUR(X, Y) (X).Y // COMPLIANT[FALSE_POSITIVE]

#define MACROFIVE(Y) MACROSIX(Y) // COMPLIANT
#define MACROSIX(X) ((X) + 1) // COMPLIANT
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @id c/misra/macro-parameter-not-enclosed-in-parentheses-c-query
* @name RULE-20-7: Expressions resulting from the expansion of macro parameters shall be enclosed in parentheses
* @description In the definition of a function-like macro, each instance of a parameter shall be
* enclosed in parentheses, otherwise the result of preprocessor macro substitition may
* not be as expected.
* @kind problem
* @precision high
* @problem.severity warning
* @tags external/misra/id/rule-20-7
* correctness
* readability
* external/misra/obligation/required
*/

import cpp
import codingstandards.c.misra
import codingstandards.cpp.rules.macroparameternotenclosedinparentheses.MacroParameterNotEnclosedInParentheses

class MacroParameterNotEnclosedInParenthesesCQueryQuery extends MacroParameterNotEnclosedInParenthesesSharedQuery {
MacroParameterNotEnclosedInParenthesesCQueryQuery() {
this = Preprocessor5Package::macroParameterNotEnclosedInParenthesesCQueryQuery()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
c/common/test/rules/macroparameternotenclosedinparentheses/MacroParameterNotEnclosedInParentheses.ql
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,10 @@

import cpp
import codingstandards.cpp.autosar
import codingstandards.cpp.FunctionLikeMacro
import codingstandards.cpp.rules.macroparameternotenclosedinparentheses.MacroParameterNotEnclosedInParentheses

from FunctionLikeMacro m, string param, string squishedBody
where
not isExcluded(m, MacrosPackage::functionLikeMacroParameterNotEnclosedInParenthesesQuery()) and
param = m.getAParameter() and
//chop out identifiers that contain a substring matching our parameter identifier (i.e., wrapped in other valid identifier characters)
squishedBody =
m.getBody()
.regexpReplaceAll("([\\w]*" + param + "[\\w]+)|([\\w]+" + param + "[\\w]*)", "")
.replaceAll(" ", "") and
(
squishedBody.regexpMatch(".*[^\\(]" + param + ".*") or
squishedBody.regexpMatch(".*" + param + "[^\\)].*") or
squishedBody.regexpMatch("^" + param + "$")
) and
not (
//case where param is right hand side operand to either # or ## operator
squishedBody.regexpMatch(".*\\#{1,2}?" + param + ".*")
or
//case where param is left hand side operand to either # or ## operator
squishedBody.regexpMatch(".*" + param + "\\#{1,2}?.*")
)
select m,
"Accesses of parameter '" + param + "' are not always enclosed in parentheses in the macro " +
m.getName() + "."
class MacroParameterNotEnclosedInParenthesesCQueryQuery extends MacroParameterNotEnclosedInParenthesesSharedQuery {
MacroParameterNotEnclosedInParenthesesCQueryQuery() {
this = MacrosPackage::functionLikeMacroParameterNotEnclosedInParenthesesQuery()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cpp/common/test/rules/macroparameternotenclosedinparentheses/MacroParameterNotEnclosedInParentheses.ql
5 changes: 5 additions & 0 deletions cpp/common/src/codingstandards/cpp/Macro.qll
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,8 @@ class StringizingOperator extends TStringizingOperator {

string toString() { result = getMacro().toString() }
}

pragma[noinline]
predicate isMacroInvocationLocation(MacroInvocation mi, File f, int startChar, int endChar) {
mi.getActualLocation().charLoc(f, startChar, endChar)
}
32 changes: 32 additions & 0 deletions cpp/common/src/codingstandards/cpp/PreprocessorDirective.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import cpp
import codingstandards.cpp.Macro

pragma[noinline]
predicate isPreprocDirectiveLocation(PreprocessorDirective pd, File f, int startChar) {
pd.getLocation().charLoc(f, startChar, _)
}

pragma[noinline]
predicate isPreprocDirectiveLine(PreprocessorDirective pd, File f, int startline, int endline) {
pd.getLocation().hasLocationInfo(f.getAbsolutePath(), startline, _, endline, _)
}

PreprocessorDirective isLocatedInAMacroInvocation(MacroInvocation m) {
exists(PreprocessorDirective p |
// There is not sufficient information in the database for nested macro invocations, because
// the location of nested macros and preprocessor directives are all set to the location of the
// outermost macro invocation
not exists(m.getParentInvocation()) and
exists(File f, int startChar, int endChar |
isMacroInvocationLocation(m, f, startChar, endChar) and
exists(int lStart | isPreprocDirectiveLocation(p, f, lStart) |
// If the start location of the preprocessor directive is after the start of the macro
// invocation, and before the end, it must be within the macro invocation
// Note: it's critical to use startChar < lStart, not startChar <= lStart, because the
// latter will include preprocessor directives which occur in nested macro invocations
startChar < lStart and lStart < endChar
)
) and
result = p
)
}
Loading