parts/6.32.PassingParametersAndReturnValues-CSJ.md

6.32 Passing Parameters and Return Values [CSJ]

6.32.1 Applicability to language

The vulnerability as described in ISO/IEC TR 24772-1:2019 clause 6.32 exists in C++. However, the language also provides appropriate mitigation.

C++ provides both call by copy (aka call by value) and call by reference parameter passing. The argument is evaluated to initialize the formal parameter (in the first case) or bound to the formal parameter (in the second case) of the function that is being called. A formal parameter behaves like a local variable, however, changes to a non-const reference parameter affect the bound object.

The rich type system of C++ allows types that when passed by value still have call by reference semantics, for example, pointer types, std::reference_wrapper, or class types with pointer or reference member variables.

C++ assumes that pointer or reference parameters of different types never alias, even if the underlying object representations are identical, i.e., for a function declared as void f(int *pi, long *pl) the compiler will assume that pi and pl always refer to different objects, even if sizeof(int) == sizeof(long). Two parameters may refer to the same object if they have pointer or reference type, and the target types are the same or related. This means, aliasing between reference parameters or with a reference result needs to be taken into account in user code. For example, in an assignment expression the left and right hand side can refer to the same object. This implies that user-defined assignment operators must take precautions against self-assignment or document that it is forbidden.

Modern C++ ensures that in many cases the need for and overhead of copying value arguments or results is elided by the compiler, especially from temporary objects.

The use of const lvalue-reference parameters combines the efficiency of call by reference with the guarantee that the underlying input parameter is not changed (marking it as an in parameter). A non-const reference parameter must be considered an inout parameter. Rvalue-reference parameters are inout parameters that allow transfer-of-ownership semantics. At their call site it is best to assume that the argument object is in an indeterminate state and has to be reassigned before subsequent use. There is no language mechanism for marking out parameters, one would use the return mechanism. Instead of multiple out parameters a struct, std::pair, or std::tuple can be used as a return type and eventually decomposed at the call site to its constituents via a structured binding.

Member functions take the *this object as an implicit reference parameter. The kind of reference can be specified through qualification of the member function. However, in addition to lvalue-reference, const-lvalue-reference, and rvalue-reference qualification, there exists an oddity with respect to normal reference parameters:

This means, unqualified member functions are callable on temporaries (rvalues) and thus can have side effects, but also can return an lvalue-reference to said temporary by returning *this (or members of *this), which can lead to dangling if such a reference is used beyond the expression of the function call returning it. For example, the compiler-provided assignment operators of a class are unqualified member functions that return an lvalue-reference to *this.

Rvalue-reference parameters in a context where their actual type is deduced from the call site, are called forwarding references. A forwarding references will either be deduced to an lvalue-reference or an rvalue-reference depending on the argument at the call site.

Aliasing is expected and allowed in some cases, such as:

-   Assignment and compound assignment operators: the right parameter may alias the left parameter. The function result always refers to the left parameter unless overloaded differently. In the case of self-assignment the stored value should not change.

-   Functions that `swap`{.cpp} their parameters: The two parameters to be swapped may refer to the same object.

-   Shift operators used for input and output: the result always refers to the left parameter.

-   Prefix increment and decrement operators: the result always refers to the parameter.
    

The C++ preprocessor macros use a call by name parameter passing; a call to the macro replaces the macro by the body of the macro. This is called macro expansion. Macro expansion is applied to the program source text and amounts to the substitution of the formal parameters with the actual parameter expressions. Formal parameters are often parenthesized to avoid syntax issues after the expansion. Call by name parameter passing reevaluates the actual parameter expression each time the formal parameter is read.

6.32.2 Avoidance mechanisms for language users

To avoid the vulnerability or mitigate its ill effects, C++ software developers can: