You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 35 Next »

It is possible to devise syntax that can ambiguously be interpreted as either an expression statement or a declaration. Syntax of this sort is called a vexing parse because the compiler must use disambiguation rules to determine the semantic results. The C++ Standard, [stmt.ambig], paragraph 1 [ISO/IEC 14882-2014], states in part:

There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration. [Note: To disambiguate, the whole statement might have to be examined to determine if it is an expression-statement or a declaration. ...

A similarly vexing parse exists within the context of a declaration where syntax can be ambiguously interpreted as either a function declaration or a declaration with a function-style cast as the initializer. The C++ Standard, [dcl.ambig.res], paragraph 1, states in part:

The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8, the resolution is to consider any construct that could possibly be a declaration a declaration.

Do not write a syntactically ambiguous declaration, including vexing parses. With the advent of uniform initialization syntax using a braced-init-list, there is now syntax that unambiguously specifies a declaration instead of an expression statement. Declarations can also be disambiguated by using nonfunction-style casts, by initializating using =, or by removing extraneous parenthesis around the parameter name.

Noncompliant Code Example

In this noncompliant code example, an attempt is made to declare a local variable, w, of type Widget while executing the default constructor. However, this declaration is syntactically ambiguous where the code could be either a declaration of a function pointer accepting no arguments and returning a Widget or a declaration of a local variable of type Widget. The syntax used in this example defines the former instead of the latter.

#include <iostream>
 
struct Widget {
  Widget() { std::cout << "Constructed" << std::endl; }
};

void f() {
  Widget w();
}

As a result, this program compiles and prints no output because the default constructor is never actually invoked.

Compliant Solution

This compliant solution shows two equally compliant ways to write the declaration. The first way is to elide the parentheses after the variable declaration, which ensures the syntax is that of a variable declaration instead of a function declaration. The second way is to use a braced-init-list to direct-initialize the local variable.

#include <iostream>
 
struct Widget {
  Widget() { std::cout << "Constructed" << std::endl; }
};

void f() {
  Widget w1; // Elide the parentheses
  Widget w2{}; // Use direct initialization
}

Running this program produces the output Constructed.

Noncompliant Code Example

This noncompliant code example demonstrates a vexing parse. The declaration Gadget g(Widget(i)); is not parsed as declaring a Gadget object with a single argument. It is instead parsed as a function declaration with a redundant set of parentheses around a parameter. 

#include <iostream>

struct Widget {
  explicit Widget(int I) { std::cout << "Widget constructed" << std::endl; }
};

struct Gadget {
  explicit Gadget(Widget wid) { std::cout << "Gadget constructed" << std::endl; }
};

void f() {
  int i = 3;
  Gadget g(Widget(i));
  std::cout << i << std::endl;
}

Parentheses around parameter names are optional, so the following is a semantically identical spelling of the declaration:

Gadget g(Widget i);

As a result, this program is well-formed and prints only 3 as output because no Gadget or Widget objects are constructed.

Compliant Solution

This compliant solution demonstrates two equally compliant ways to write the declaration of g. The first declaration, g1, uses an extra set of parentheses around the argument to the constructor call, forcing the compiler to parse this as a local variable declaration of type Gadget instead of as a function declaration. The second declaration, g2, uses direct initialization to similar effect.

#include <iostream>

struct Widget {
  explicit Widget(int I) { std::cout << "Widget constructed" << std::endl; }
};

struct Gadget {
  explicit Gadget(Widget wid) { std::cout << "Gadget constructed" << std::endl; }
};

void f() {
  int i = 3;
  Gadget g1((Widget(i))); // Use extra parentheses
  Gadget g2{Widget(i)}; // Use direct initialization
  std::cout << i << std::endl;
}

Running this program produces the expected output:

Widget constructed
Gadget constructed
Widget constructed
Gadget constructed
3

Risk Assessment

Syntactically ambiguous declarations can lead to unexpected program execution. However, it is likely that rudimentary testing would uncover violations of this rule.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

DCL53-CPP

Low

Unlikely

Medium

P2

L3

Automated Detection

Tool

Version

Checker

Description

 PRQA QA-C++

4.4

2510

 
Clang3.9-Wvexing-parse 
SonarQube C/C++ Plugin

This page was automatically generated and should not be edited.

The information on this page was provided by outside contributors and has not been verified by SEI CERT.

The table below can be re-ordered, by clicking column headers.

Tool Version: 4.10

Checker

Guideline

978 DCL51-CPP. Do not declare or define a reserved identifier
FunctionEllipsis DCL50-CPP. Do not define a C-style variadic function
IncAndDecMixedWithOtherOperators EXP50-CPP. Do not depend on the order of evaluation for side effects
S859 EXP55-CPP. Do not access a cv-qualified object through a cv-unqualified type
S935 MSC52-CPP. Value-returning functions must return a value from all exit paths
S935 MSC53-CPP. Do not return from a function declared [[noreturn]]
S982 ERR52-CPP. Do not use setjmp() or longjmp()
S990 ERR50-CPP. Do not abruptly terminate the program
S1044 ERR61-CPP. Catch exceptions by lvalue reference
S1045 ERR54-CPP. Catch handlers should order their parameter types from most derived to least derived
S1232 MEM51-CPP. Properly deallocate dynamically allocated resources
S1235 OOP52-CPP. Do not delete a polymorphic object without a virtual destructor
S1265 DCL54-CPP. Overload allocation and deallocation functions as a pair in the same scope
S1699 OOP50-CPP. Do not invoke virtual functions from constructors or destructors
S3229 OOP53-CPP. Write constructor member initializers in the canonical order
S3468 DCL53-CPP. Do not write syntactically ambiguous declarations
S3470 DCL58-CPP. Do not modify the standard namespaces
S3519 STR50-CPP. Guarantee that storage for strings has sufficient space for character data and the null terminator
S3708 DCL52-CPP. Never qualify a reference type with const or volatile
UnnamedNamespaceInHeader DCL59-CPP. Do not define an unnamed namespace in a header file
S3468 

Related Vulnerabilities

Search for other vulnerabilities resulting from the violation of this rule on the CERT website.

Bibliography

[ISO/IEC 14882-2014]Subclause 6.8, "Ambiguity Resolution"
Subclause 8.2, "Ambiguity Resolution"
[Meyers 01]Item 6, "Be Alert for C++'s Most Vexing Parse"

 


  • No labels