diff options
author | Marek Polacek <polacek@redhat.com> | 2020-10-02 09:46:30 -0400 |
---|---|---|
committer | Marek Polacek <polacek@redhat.com> | 2020-11-05 15:55:14 -0500 |
commit | 5b2003105b35f8fe8e074c055a718c8f484d9d32 (patch) | |
tree | 864228a0058125059a0930ea0abc69aef281e8df /gcc/cp/parser.c | |
parent | 22984f3f090921b5ac80ec0057f6754ec458e97e (diff) | |
download | gcc-5b2003105b35f8fe8e074c055a718c8f484d9d32.zip gcc-5b2003105b35f8fe8e074c055a718c8f484d9d32.tar.gz gcc-5b2003105b35f8fe8e074c055a718c8f484d9d32.tar.bz2 |
c++: Implement -Wvexing-parse [PR25814]
This patch implements the -Wvexing-parse warning to warn about the
sneaky most vexing parse rule in C++: the cases when a declaration
looks like a variable definition, but the C++ language requires it
to be interpreted as a function declaration. This warning is on by
default (like clang++). From the docs:
void f(double a) {
int i(); // extern int i (void);
int n(int(a)); // extern int n (int);
}
Another example:
struct S { S(int); };
void f(double a) {
S x(int(a)); // extern struct S x (int);
S y(int()); // extern struct S y (int (*) (void));
S z(); // extern struct S z (void);
}
You can find more on this in [dcl.ambig.res].
I spent a fair amount of time on fix-it hints so that GCC can recommend
various ways to resolve such an ambiguity. Sometimes that's tricky.
E.g., suggesting default-initialization when the class doesn't have
a default constructor would not be optimal. Suggesting {}-init is also
not trivial because it can use an initializer-list constructor if no
default constructor is available (which ()-init wouldn't do). And of
course, pre-C++11, we shouldn't be recommending {}-init at all.
I also uncovered a bug in cp_parser_declarator, where we were setting
*parenthesized_p to true despite the comment saying the exact opposite.
gcc/c-family/ChangeLog:
PR c++/25814
* c.opt (Wvexing-parse): New option.
gcc/cp/ChangeLog:
PR c++/25814
* cp-tree.h (enum cp_tree_index): Add CPTI_EXPLICIT_VOID_LIST.
(explicit_void_list_node): Define.
(PARENTHESIZED_LIST_P): New macro.
(struct cp_declarator): Add function::parens_loc.
* decl.c (cxx_init_decl_processing): Initialize explicit_void_list_node.
(grokparms): Also break when explicit_void_list_node.
* parser.c (make_call_declarator): New location_t parameter. Use it
to set declarator->u.function.parens_loc.
(cp_parser_lambda_declarator_opt): Pass UNKNOWN_LOCATION to
make_call_declarator.
(warn_about_ambiguous_parse): New function.
(cp_parser_init_declarator): Call warn_about_ambiguous_parse.
(cp_parser_declarator): Set *parenthesized_p to false rather than to
true.
(cp_parser_direct_declarator): Create a location for the function's
parentheses and pass it to make_call_declarator.
(cp_parser_parameter_declaration_clause): Return explicit_void_list_node
for (void).
(cp_parser_parameter_declaration_list): Set PARENTHESIZED_LIST_P
in the parameters tree.
gcc/ChangeLog:
PR c++/25814
* doc/invoke.texi: Document -Wvexing-parse.
gcc/testsuite/ChangeLog:
PR c++/25814
* g++.dg/cpp2a/fn-template16.C: Add a dg-warning.
* g++.dg/cpp2a/fn-template7.C: Likewise.
* g++.dg/lookup/pr80891-5.C: Likewise.
* g++.dg/lto/pr79050_0.C: Add extern.
* g++.dg/lto/pr84805_0.C: Likewise.
* g++.dg/parse/pr58898.C: Add a dg-warning.
* g++.dg/template/scope5.C: Likewise.
* g++.old-deja/g++.brendan/recurse.C: Likewise.
* g++.old-deja/g++.jason/template4.C: Likewise.
* g++.old-deja/g++.law/arm4.C: Likewise.
* g++.old-deja/g++.mike/for2.C: Likewise.
* g++.old-deja/g++.other/local4.C: Likewise.
* g++.old-deja/g++.pt/crash3.C: Likewise.
* g++.dg/warn/Wvexing-parse.C: New test.
* g++.dg/warn/Wvexing-parse2.C: New test.
* g++.dg/warn/Wvexing-parse3.C: New test.
* g++.dg/warn/Wvexing-parse4.C: New test.
* g++.dg/warn/Wvexing-parse5.C: New test.
* g++.dg/warn/Wvexing-parse6.C: New test.
* g++.dg/warn/Wvexing-parse7.C: New test.
libstdc++-v3/ChangeLog:
PR c++/25814
* testsuite/20_util/reference_wrapper/lwg2993.cc: Add a dg-warning.
* testsuite/25_algorithms/generate_n/87982_neg.cc: Likewise.
Diffstat (limited to 'gcc/cp/parser.c')
-rw-r--r-- | gcc/cp/parser.c | 155 |
1 files changed, 149 insertions, 6 deletions
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index b0d5c69..e7bfbf6 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -1438,7 +1438,8 @@ clear_decl_specs (cp_decl_specifier_seq *decl_specs) VAR_DECLs or FUNCTION_DECLs) should do that directly. */ static cp_declarator *make_call_declarator - (cp_declarator *, tree, cp_cv_quals, cp_virt_specifiers, cp_ref_qualifier, tree, tree, tree, tree); + (cp_declarator *, tree, cp_cv_quals, cp_virt_specifiers, cp_ref_qualifier, + tree, tree, tree, tree, location_t); static cp_declarator *make_array_declarator (cp_declarator *, tree); static cp_declarator *make_pointer_declarator @@ -1621,7 +1622,8 @@ make_call_declarator (cp_declarator *target, tree tx_qualifier, tree exception_specification, tree late_return_type, - tree requires_clause) + tree requires_clause, + location_t parens_loc) { cp_declarator *declarator; @@ -1635,6 +1637,7 @@ make_call_declarator (cp_declarator *target, declarator->u.function.exception_specification = exception_specification; declarator->u.function.late_return_type = late_return_type; declarator->u.function.requires_clause = requires_clause; + declarator->u.function.parens_loc = parens_loc; if (target) { declarator->id_loc = target->id_loc; @@ -11246,7 +11249,8 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) tx_qual, exception_spec, return_type, - trailing_requires_clause); + trailing_requires_clause, + UNKNOWN_LOCATION); declarator->std_attributes = std_attrs; fco = grokmethod (&return_type_specs, @@ -20613,6 +20617,129 @@ strip_declarator_types (tree type, cp_declarator *declarator) return type; } +/* Warn about the most vexing parse syntactic ambiguity, i.e., warn when + a construct looks like a variable definition but is actually a function + declaration. DECL_SPECIFIERS is the decl-specifier-seq and DECLARATOR + is the declarator for this function declaration. */ + +static void +warn_about_ambiguous_parse (const cp_decl_specifier_seq *decl_specifiers, + const cp_declarator *declarator) +{ + /* Only warn if we are declaring a function at block scope. */ + if (!at_function_scope_p ()) + return; + + /* And only if there is no storage class specified. */ + if (decl_specifiers->storage_class != sc_none + || decl_spec_seq_has_spec_p (decl_specifiers, ds_typedef)) + return; + + if (declarator->kind != cdk_function + || !declarator->declarator + || declarator->declarator->kind != cdk_id + || !identifier_p (get_unqualified_id + (const_cast<cp_declarator *>(declarator)))) + return; + + /* Don't warn when the whole declarator (not just the declarator-id!) + was parenthesized. That is, don't warn for int(n()) but do warn + for int(f)(). */ + if (declarator->parenthesized != UNKNOWN_LOCATION) + return; + + tree type = decl_specifiers->type; + if (TREE_CODE (type) == TYPE_DECL) + type = TREE_TYPE (type); + + /* If the return type is void there is no ambiguity. */ + if (same_type_p (type, void_type_node)) + return; + + auto_diagnostic_group d; + location_t loc = declarator->u.function.parens_loc; + tree params = declarator->u.function.parameters; + const bool has_list_ctor_p = CLASS_TYPE_P (type) && TYPE_HAS_LIST_CTOR (type); + + /* The T t() case. */ + if (params == void_list_node) + { + if (warning_at (loc, OPT_Wvexing_parse, + "empty parentheses were disambiguated as a function " + "declaration")) + { + /* () means value-initialization (C++03 and up); {} (C++11 and up) + means value-initialization or aggregate-initialization, nothing + means default-initialization. We can only suggest removing the + parentheses/adding {} if T has a default constructor. */ + if (!CLASS_TYPE_P (type) || TYPE_HAS_DEFAULT_CONSTRUCTOR (type)) + { + gcc_rich_location iloc (loc); + iloc.add_fixit_remove (); + inform (&iloc, "remove parentheses to default-initialize " + "a variable"); + if (cxx_dialect >= cxx11 && !has_list_ctor_p) + { + if (CP_AGGREGATE_TYPE_P (type)) + inform (loc, "or replace parentheses with braces to " + "aggregate-initialize a variable"); + else + inform (loc, "or replace parentheses with braces to " + "value-initialize a variable"); + } + } + } + return; + } + + /* If we had (...) or the parameter-list wasn't parenthesized, + we're done. */ + if (params == NULL_TREE || !PARENTHESIZED_LIST_P (params)) + return; + + /* The T t(X()) case. */ + if (list_length (params) == 2) + { + if (warning_at (loc, OPT_Wvexing_parse, + "parentheses were disambiguated as a function " + "declaration")) + { + gcc_rich_location iloc (loc); + /* {}-initialization means that we can use an initializer-list + constructor if no default constructor is available, so don't + suggest using {} for classes that have an initializer_list + constructor. */ + if (cxx_dialect >= cxx11 && !has_list_ctor_p) + { + iloc.add_fixit_replace (get_start (loc), "{"); + iloc.add_fixit_replace (get_finish (loc), "}"); + inform (&iloc, "replace parentheses with braces to declare a " + "variable"); + } + else + { + iloc.add_fixit_insert_after (get_start (loc), "("); + iloc.add_fixit_insert_before (get_finish (loc), ")"); + inform (&iloc, "add parentheses to declare a variable"); + } + } + } + /* The T t(X(), X()) case. */ + else if (warning_at (loc, OPT_Wvexing_parse, + "parentheses were disambiguated as a function " + "declaration")) + { + gcc_rich_location iloc (loc); + if (cxx_dialect >= cxx11 && !has_list_ctor_p) + { + iloc.add_fixit_replace (get_start (loc), "{"); + iloc.add_fixit_replace (get_finish (loc), "}"); + inform (&iloc, "replace parentheses with braces to declare a " + "variable"); + } + } +} + /* Declarators [gram.dcl.decl] */ /* Parse an init-declarator. @@ -20808,6 +20935,9 @@ cp_parser_init_declarator (cp_parser* parser, } } + if (!member_p && !cp_parser_error_occurred (parser)) + warn_about_ambiguous_parse (decl_specifiers, declarator); + /* Check to see if the token indicates the start of a function-definition. */ if (cp_parser_token_starts_function_definition_p (token)) @@ -21202,7 +21332,7 @@ cp_parser_declarator (cp_parser* parser, /* If a ptr-operator was found, then this declarator was not parenthesized. */ if (parenthesized_p) - *parenthesized_p = true; + *parenthesized_p = false; /* The dependent declarator is optional if we are parsing an abstract-declarator. */ if (dcl_kind != CP_PARSER_DECLARATOR_NAMED) @@ -21349,6 +21479,7 @@ cp_parser_direct_declarator (cp_parser* parser, cp_parser_parse_tentatively (parser); /* Consume the `('. */ + const location_t parens_start = token->location; matching_parens parens; parens.consume_open (parser); if (first) @@ -21368,6 +21499,8 @@ cp_parser_direct_declarator (cp_parser* parser, /* Parse the parameter-declaration-clause. */ params = cp_parser_parameter_declaration_clause (parser, flags); + const location_t parens_end + = cp_lexer_peek_token (parser->lexer)->location; /* Consume the `)'. */ parens.require_close (parser); @@ -21432,6 +21565,9 @@ cp_parser_direct_declarator (cp_parser* parser, /* Parse the virt-specifier-seq. */ virt_specifiers = cp_parser_virt_specifier_seq_opt (parser); + location_t parens_loc = make_location (parens_start, + parens_start, + parens_end); /* Create the function-declarator. */ declarator = make_call_declarator (declarator, params, @@ -21441,7 +21577,8 @@ cp_parser_direct_declarator (cp_parser* parser, tx_qual, exception_specification, late_return, - requires_clause); + requires_clause, + parens_loc); declarator->std_attributes = attrs; declarator->attributes = gnu_attrs; /* Any subsequent parameter lists are to do with @@ -22708,7 +22845,7 @@ cp_parser_parameter_declaration_clause (cp_parser* parser, /* Consume the `void' token. */ cp_lexer_consume_token (parser->lexer); /* There are no parameters. */ - return void_list_node; + return explicit_void_list_node; } /* Parse the parameter-declaration-list. */ @@ -22832,6 +22969,12 @@ cp_parser_parameter_declaration_list (cp_parser* parser, cp_parser_flags flags) *tail = build_tree_list (parameter->default_argument, decl); tail = &TREE_CHAIN (*tail); + /* If the parameters were parenthesized, it's the case of + T foo(X(x)) which looks like a variable definition but + is a function declaration. */ + if (index == 1 || PARENTHESIZED_LIST_P (parameters)) + PARENTHESIZED_LIST_P (parameters) = parenthesized_p; + /* Peek at the next token. */ if (cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_PAREN) || cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS) |