Main Content

CERT C++: EXP37-C

Call functions with the correct number and type of arguments

Description

Rule Definition

Call functions with the correct number and type of arguments.1

Polyspace Implementation

The rule checker checks for these issues:

  • Bad file access mode or status.

  • Unreliable cast of function pointer.

  • Standard function call with incorrect arguments.

  • Function declaration mismatch

  • Incompatible Argument

Examples

expand all

Issue

Bad file access mode or status occurs when you use functions in the fopen or open group with invalid or incompatible file access modes, file creation flags, or file status flags as arguments. For instance, for the open function, examples of valid:

  • Access modes include O_RDONLY, O_WRONLY, and O_RDWR

  • File creation flags include O_CREAT, O_EXCL, O_NOCTTY, and O_TRUNC.

  • File status flags include O_APPEND, O_ASYNC, O_CLOEXEC, O_DIRECT, O_DIRECTORY, O_LARGEFILE, O_NOATIME, O_NOFOLLOW, O_NONBLOCK, O_NDELAY, O_SHLOCK, O_EXLOCK, O_FSYNC, O_SYNC and so on.

The defect can occur in the following situations.

SituationRiskFix

You pass an empty or invalid access mode to the fopen function.

According to the ANSI® C standard, the valid access modes for fopen are:

  • r,r+

  • w,w+

  • a,a+

  • rb, wb, ab

  • r+b, w+b, a+b

  • rb+, wb+, ab+

fopen has undefined behavior for invalid access modes.

Some implementations allow extension of the access mode such as:

  • GNU®: rb+cmxe,ccs=utf

  • Visual C++®: a+t, where t specifies a text mode.

However, your access mode string must begin with one of the valid sequences.

Pass a valid access mode to fopen.
You pass the status flag O_APPEND to the open function without combining it with either O_WRONLY or O_RDWR.

O_APPEND indicates that you intend to add new content at the end of a file. However, without O_WRONLY or O_RDWR, you cannot write to the file.

The open function does not return -1 for this logical error.

Pass either O_APPEND|O_WRONLY or O_APPEND|O_RDWR as access mode.
You pass the status flags O_APPEND and O_TRUNC together to the open function.

O_APPEND indicates that you intend to add new content at the end of a file. However, O_TRUNC indicates that you intend to truncate the file to zero. Therefore, the two modes cannot operate together.

The open function does not return -1 for this logical error.

Depending on what you intend to do, pass one of the two modes.
You pass the status flag O_ASYNC to the open function. On certain implementations, the mode O_ASYNC does not enable signal-driven I/O operations.Use the fcntl(pathname, F_SETFL, O_ASYNC); instead.

Fix

The fix depends on the root cause of the defect. Often the result details show a sequence of events that led to the defect. You can implement the fix on any event in the sequence. If the result details do not show the event history, you can trace back using right-click options in the source code and see previous related events. See also Interpret Bug Finder Results in Polyspace Desktop User Interface.

See examples of fixes below.

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Example - Invalid Access Mode with fopen
#include <stdio.h>

void func(void) {
    FILE *file = fopen("data.txt", "rw"); //Noncompliant
    if(file!=NULL) {
        fputs("new data",file);
        fclose(file);
    }
}

In this example, the access mode rw is invalid. Because r indicates that you open the file for reading and w indicates that you create a new file for writing, the two access modes are incompatible.

Correction — Use Either r or w as Access Mode

One possible correction is to use the access mode corresponding to what you intend to do.

#include <stdio.h>

void func(void) {
    FILE *file = fopen("data.txt", "w");
    if(file!=NULL) {
        fputs("new data",file);
        fclose(file);
    }
}
Issue

Unreliable cast of function pointer occurs when a function pointer is cast to another function pointer that has a different argument or return type.

Risk

If you cast a function pointer to another function pointer that has a different argument or return type, and then use the latter function pointer to call a function, the behavior is undefined.

Fix

Avoid a cast between two function pointers that have a mismatch in argument or return types.

See examples of fixes below.

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Example - Unreliable Cast of Function Pointer Error
int f(char c) {
	return c;
}

int g(int i) {
	return i;
}

typedef int (*fptr_t)(char);
typedef int (*gptr_t)(int);

void call() {
	gptr_t ptr = (gptr_t) f;//Noncompliant
	int i = ptr(511); // Undefined behavior
}

In this example, the pointer to function f is cast to gptr_t, which is the type of the function g. When the function pointer is used to call f by using integers, the code behavior is undefined. Polyspace® flags the unreliable cast of the function pointer.

Correction — Avoid Function Pointer Cast

To avoid undefined behavior, refactor your code so that the function f is not cast into a different argument type. For instance:

 int f(int c) { //Fix: declare f with int argument
	return c;
}

int g(int i) {
	return i;
}

typedef int (*fptr_t)(char);
typedef int (*gptr_t)(int);

void call() {
	gptr_t ptr = (gptr_t) f;//Compliant
	int i = ptr(511); 
}
Issue

Standard function call with incorrect arguments occurs when the arguments to certain standard functions do not meet the requirements for their use in the functions.

For instance, the arguments to these functions can be invalid in the following ways.

Function TypeSituationRiskFix
String manipulation functions such as strlen and strcpyThe pointer arguments do not point to a NULL-terminated string.The behavior of the function is undefined.Pass a NULL-terminated string to string manipulation functions.
File handling functions in stdio.h such as fputc and freadThe FILE* pointer argument can have the value NULL.The behavior of the function is undefined.Test the FILE* pointer for NULL before using it as function argument.
File handling functions in unistd.h such as lseek and read The file descriptor argument can be -1.

The behavior of the function is undefined.

Most implementations of the open function return a file descriptor value of -1. In addition, they set errno to indicate that an error has occurred when opening a file.

Test the return value of the open function for -1 before using it as argument for read or lseek.

If the return value is -1, check the value of errno to see which error has occurred.

The file descriptor argument represents a closed file descriptor.The behavior of the function is undefined.Close the file descriptor only after you have completely finished using it. Alternatively, reopen the file descriptor before using it as function argument.
Directory name generation functions such as mkdtemp and mkstempsThe last six characters of the string template are not XXXXXX.The function replaces the last six characters with a string that makes the file name unique. If the last six characters are not XXXXXX, the function cannot generate a unique enough directory name.Test if the last six characters of a string are XXXXXX before using the string as function argument.
Functions related to environment variables such as getenv and setenvThe string argument is "".The behavior is implementation-defined.Test the string argument for "" before using it as getenv or setenv argument.
The string argument terminates with an equal sign, =. For instance, "C=" instead of "C".The behavior is implementation-defined.Do not terminate the string argument with =.
String handling functions such as strtok and strstr

  • strtok: The delimiter argument is "".

  • strstr: The search string argument is "".

Some implementations do not handle these edge cases.Test the string for "" before using it as function argument.

Fix

The fix depends on the root cause of the defect. Often the result details show a sequence of events that led to the defect. You can implement the fix on any event in the sequence. If the result details do not show the event history, you can trace back using right-click options in the source code and see previous related events. See also Interpret Bug Finder Results in Polyspace Desktop User Interface.

See examples of fixes below.

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Example - NULL Pointer Passed as strnlen Argument
#include <string.h>
#include <stdlib.h>

enum {
    SIZE10 = 10,
    SIZE20 = 20
};

int func() {
    char* s = NULL;
    return strnlen(s, SIZE20); //Noncompliant
}

In this example, a NULL pointer is passed as strnlen argument instead of a NULL-terminated string.

Before running analysis on the code, specify a GNU compiler. See Compiler (-compiler).

Correction — Pass NULL-terminated String

Pass a NULL-terminated string as the first argument of strnlen.

#include <string.h>
#include <stdlib.h>

enum {
    SIZE10 = 10,
    SIZE20 = 20
};

int func() {
    char* s = "";
    return strnlen(s, SIZE20);
}
Issue

Note

In C++ code, this checker applies to functions that are specified as extern "C".

Function declaration mismatch occurs when the prototype of a extern "C" function does not match its definition. Type mismatch between the arguments of the function definition and the function prototype might depend on your environment. Polyspace considers two types as compatible if they have the same size and signedness in the environment that you use. For instance, if your specify -target as i386, Polyspace considers long and int as compatible types.

In C++, if a function is not specified as extern "C" and its prototype does not match any function definition, the compiler treats the prototype as that of an undefined overload of the function. Polyspace does not flag calls to such undefined functions.

The checker does not flag this issue in a default Polyspace as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).

Risk

Function declaration mismatch might result in undefined behavior. When function declarations are specified with extern "C", mismatches between definition and declaration of a function might produce only warnings during compilation, resulting in code that compiles but behaves in an unexpected way.

Fix

  • Before you call a function, provide its complete prototype, even if you define the function later in the same file.

  • Avoid any mismatch between the number arguments in the function prototype declaration and the function definition.

  • Avoid any mismatch between the argument types of the function prototype declaration and the function definition.

Example — Noncompliant Function Calls
// file1.c
extern "C" void foo(int iVar){
	//...
}
extern "C" void bar(int iVar){
	//...
}
extern "C" void fubar(int A, ...){
	//...
}


//prototype.h
extern "C" void foo(void);
extern "C" void fubar(int A, ...);
extern "C" void bar(long iVar);
//file2.c
//file2.c
#include"prototype.h"
void call_funcs(){
	int iTemp;
	float fTemp;
	long lTemp;
	foo(); //Noncompliant
	bar(lTemp);//Noncompliant in x86_64
	fubar(iTemp,fTemp);//Compliant
	
}

In this example, the functions foo, bar, and fubar are defined in the file file1.c. Their prototypes are declared in prototype.h. These functions are then called in the file file2.c.

  • The function foo is defined with an int argument but its prototype is declared without any argument. Because of this mismatch, Polyspace flags the function call.

  • The function bar is defined with an int argument but its prototype is declared with a long argument. These two types are not compatible in x86_64 environment. When you specify -target as x86_64, Polyspace flags the function call.

  • The call to the variadic function fubar is compliant because its call signature, prototype, and definition matches.

Correction — Compliant Function Calls

The fix for this defect is to declare complete and accurate prototypes for the called functions. In this case, fix the raised issues by resolving the mismatches between the function definition and prototype declaration. Update the function calls to match the updated prototypes.

// file1.c
extern "C" void foo(int iVar){
	//...
}
extern "C" void bar(int iVar){
	//...
}
extern "C" void fubar(int A, ...){
	//...
}


//prototype.h
extern "C" void foo(int);
extern "C" void fubar(int A, ...);
extern "C" void bar(int iVar);
//file2.c
//file2.c
#include"prototype.h"
void call_funcs(){
	int iTemp;
	float fTemp;
	long lTemp;
	foo(iTemp); //Compliant
	bar(iTemp);//Compliant in x86_64
	fubar(iTemp,fTemp);//Compliant
	
}
Issue

Incompatible Argument occurs when an external function is called by using an argument that is not compatible with the prototype. The compatibility of types might depend on the set of hardware and software that you use. For instance, consider this code:

extern long foo(int);

long bar(long i) {
    return foo(i); //Noncompliant: calls foo(int) with a long              
}
The external function foo is called with a long when an int is expected. In environments where the size of an int is smaller than the size of a long, this function call is incompatible with the prototype, resulting in a defect.

In C++, this defect might cause a compilation error.

Risk

Calling external functions with arguments that are incompatible with the parameter is undefined behavior. Depending on your environment, the code might compile but behave in an unexpected way.

Fix

When calling external functions, use argument types that are smaller or equal in size compared to the parameter type defined in the prototype. Check the sizes of various integer types in your environment to determine compatibility of argument and parameter types.

Example — Call External Functions with Incompatible argument
extern long foo1(int);
extern long foo2(long);
void bar(){
	int varI;
	long varL;
	foo1(varL);//Noncompliant
	foo2(varI);//Compliant
}

In this example, the external function foo1 is called with a long argument, while the prototype specifies the parameter as an int. In x86 architecture, the size of long is larger than the size of int. The call foo1(varL) might result in undefined behavior. Polyspace flags the call. The call foo2(varI) uses an int argument while the parameter is specified as a long. This type of mismatch is compliant with this rule because the size of int is not larger than the size of long.

To run this example in Polyspace, use these options:

  • -target x86_64

See Target processor type (-target).

Correction — Cast Variables Explicitly to Match Argument to Parameter

To fix this issue, cast the argument of foo1 explicitly so that argument type and parameter type matches.

extern long foo1(int);
extern long foo2(long);
void bar(){
	int varI;
	long varL;
	foo1((int)varL);//Compliant
	foo2(varI);//Compliant
}

Check Information

Group: 02. Expressions (EXP)

Version History

Introduced in R2019a

expand all


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.