|
|
Created:
14 years, 1 month ago by Le-Chun Wu Modified:
14 years ago CC:
gcc-patches_gcc.gnu.org Visibility:
Public. |
Patch Set 1 #Patch Set 2 : Retry uploading the patch set. #
Total comments: 10
Patch Set 3 : Incorporated Diego's review comments. #MessagesTotal messages: 4
Port annotalysis (lock annotations and analysis) from GCC-4.4.3 source tree to (GCC-4.6 based) google/main branch (essentially merging from branches/annotalysis at r171369.) This patch is bootstrapped and tested on x86_64-unknown-linux-gnu. OK for google/main? Thanks, Le-chun 2011-03-24 Le-Chun Wu <lcwu@google.com> * Makefile.in: Add new source file and headers in dependencies. * attribs.c (decl_attributes): Handle lock attributes. (is_lock_attribute_with_args): New function. (is_lock_attribute_p): Likewise. (extract_lock_attributes): Likewise. (merge_lock_attr_args): Likewise. * c-decl.c (undeclared_variable): Suppress errors for lock attributes. * c-parser.c (c_parser_declaration_or_fndef): Allow lock attributes on function definitions. Add support for suppressing errors for lock attributes. (c_parser_attributes): Replace the original code that handles the argument list with a call to c_parser_attr_arg_list. (c_parser_attr_arg_list): New function. * common.opt: Add new flags for lock annotations and analysis. * doc/invoke.texi: Add documentation for new flags for lock annotations and analysis. * gimplify.c (lookup_tmp_var): Copy thread safety attributes to tmp variable and save the original variable name. * langhooks-def.h: Define new language hooks. * langhooks.c (lhd_do_nothing_t_return_int): New function. (lhd_do_nothing_t_return_bool): Likewise. (lhd_do_nothing_t_t_return_null_tree): Likewise. * langhooks.h: Add new hook functions in the lang_hooks struct. * passes.c (init_optimization_passes): Add a new pass. * pointer-set.c (pointer_set_copy): New function. (pointer_set_delete): Likewise. (pointer_set_intersection_complement): Likewise. (pointer_set_union_inplace): Likewise. (pointer_set_cardinality): Likewise. * pointer-set.h: Add declarations of new functions. * timevar.def: Add a new time var for thread safety analysis pass. * toplev.c (compile_file): Clean up the global data structures used by the thread safety analysis. * tree-pass.h: Add a new pass declaration. * tree-threadsafe-analyze.c: New file. * tree-threadsafe-analyze.h: New file. * tree.h: Declaration for new functions. c-family/ * c-common.c (c_common_attribute_table): Add new functions to process lock attributes. (attribute_takes_identifier_p): Handle lock attributes. (handle_lockable_attribute): New Handler. (handle_guarded_by_attribute): Likewise. (handle_point_to_guarded_by_attribute): Likewise. (handle_guarded_attribute): Likewise. (handle_point_to_guarded_attribute): Likewise. (handle_acquired_order_attribute): Likewise. (handle_lock_attribute): Likewise. (handle_unlock_attribute): Likewise. (handle_locks_required_excluded_attribute): Likewise. (handle_lock_returned_attribute): Likewise. (handle_no_thread_safety_analysis_attribute): Likewise. (supported_lock_expression): New helper function. (get_lock_decl): Likewise. (populate_acquired_after_map): Likewise. (is_lock_formal_parameter): Likewise. (check_lock_unlock_attr_args): Likewise. * c-cppbuiltin.c (c_cpp_builtins): Define annotalysis-related macros. * c-pretty-print.c (pp_c_expression): Handle SSA_NAME. cp/ * Make-lang.in: Add new includes. * call.c (build_new_op): Support for non-const non-modifying methods. (find_const_memfunc_with_identical_prototype): New function. (build_new_method_call): Suppress errors for calls in lock attributes. Support for non-const non-modifying methods. * class.c (cp_get_virtual_function_decl): New function. (cp_fold_obj_type_ref): Refactored to call cp_get_virtual_function_decl. (cp_decl_is_base_field): New function. (cp_decl_is_constructor): Likewise. (cp_decl_is_destructor): Likewise. (cp_decl_is_const_member_func): Likewise. * cp-lang.c: New language hooks. * cp-tree.h: New function declarations. * decl2.c (is_late_template_attribute): Handle delay parsing of lock attribute arguments. * error.c (dump_expr): Handle SSA_NAME. * lex.c (unqualified_name_lookup_error): Suppress errors for lock attributes. * name-lookup.c (lookup_name_in_func_params): New function. * name-lookup.h: New function declaration. * parser.c (cp_parser): New fields. (cp_parser_name_lookup_error): Suppress errors for lock attributes. (cp_parser_new): Initialize unparsed_attribute_args_queue. (cp_parser_postfix_expression): Add function parameter lookup support. (cp_parser_parenthesized_expression_list): Fix a problem in parsing identifier arguments and skip folding for decl arguments. (cp_parser_lambda_declarator_opt): Add a new argument to cp_parser_attributes_opt. (cp_parser_label_for_labeled_statement): Likewise. (cp_parser_condition): Likewise. (cp_parser_decl_specifier_seq): Likewise. (cp_parser_conversion_type_id): Likewise. (cp_parser_elaborated_type_specifier): Likewise. (cp_parser_enum_specifier): Likewise. (cp_parser_namespace_definition): Likewise. (cp_parser_using_directive): Likewise. (cp_parser_init_declarator): Allow lock attributes on function definitions. Support function parameter lookup. Also add a new argument to cp_parser_attributes_opt. (cp_parser_declarator): Add a new argument to calls to cp_parser_attributes_opt. (cp_parser_type_specifier_seq): Likewise. (cp_parser_parameter_declaration): Likewise. (cp_parser_class_specifier): Late-parse the lock attribute arguments. Also add a new argument to cp_parser_attributes_opt. (cp_parser_class_head): Add a new argument to cp_parser_attributes_opt. (cp_parser_member_declaration): Likewise. (cp_parser_asm_label_list): Likewise. (cp_parser_attributes_opt): Add a new parameter 'member_p' and call cp_parser_attribute_list with it. (cp_parser_save_attribute_arg_list): New function. (cp_parser_attribute_list): Add a new parameter 'member_p'. Also delay parsing of lock attribute arguments by saving the tokens. (cp_parser_late_parsing_attribute_arg_lists): New function. (cp_parser_function_definition_from_specifiers_and_declarator): Parse unbound lock attribute arguments. (cp_parser_objc_method_keyword_params): Add a new argument to cp_parser_attributes_opt. (cp_parser_objc_method_tail_params_opt): Likewise. (cp_parser_objc_method_maybe_bad_prefix_attributes): Likewise. (cp_parser_objc_class_ivars): Likewise. (cp_parser_objc_struct_declaration): Likewise. (cp_parser_omp_for_loop): Likewise. * pt.c (find_parameter_packs_r): Skip walking the subtrees if the tree list node is created for delay parsing. (apply_late_template_attributes): Defer instantiation of lock attributes. (pa_reverse): New function. (instantiate_class_template): Instantiate the deferred lock attributes and apply them to the corresponding declarations. (tsubst_copy): Suppress errors for lock attributes. (tsubst_copy_and_build): Support function parameter lookup. * semantics.c (finish_non_static_data_member): Suppress errors for lock attributes. * typeck.c (finish_class_member_access_expr): Suppress errors for lock attributes. * typeck2.c (cxx_incomplete_type_diagnostic): Suppress errors for lock attributes. testsuite/ * g++.dg/README: Add an entry for the new thread-ann sub-directory. * g++.dg/thread-ann: New test sub-directory. * g++.dg/thread-ann/thread_annot_common.h: New test header file. * g++.dg/thread-ann/thread_annot_lock-1.C: New test. * g++.dg/thread-ann/thread_annot_lock-2.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-3.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-4.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-5.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-6.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-7.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-8.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-9.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-10.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-11.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-12.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-13.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-14.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-15.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-16.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-17.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-18.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-19.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-20.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-21.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-22.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-23.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-24.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-25.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-26.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-27.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-28.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-29.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-30.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-31.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-32.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-33.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-34.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-35.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-36.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-37.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-38.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-39.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-40.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-41.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-42.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-43.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-44.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-45.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-46.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-47.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-48.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-49.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-50.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-51.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-52.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-53.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-54.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-55.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-56.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-57.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-58.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-59.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-60.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-61.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-62.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-63.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-64.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-65.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-66.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-67.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-68.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-69.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-70.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-71.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-72.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-73.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-74.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-75.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-76.C: Likewise. * g++.dg/thread-ann/thread_annot_lock-77.C: Likewise. * gcc.dg/thread_annot_common_c.h: New test header file for C. * gcc.dg/thread_annot_lock-23.c: New test. * gcc.dg/thread_annot_lock-23.c: New test. * gcc.dg/thread_annot_lock-24.c: Likewise. * gcc.dg/thread_annot_lock-25.c: Likewise. * gcc.dg/thread_annot_lock-26.c: Likewise. * gcc.dg/thread_annot_lock-27.c: Likewise. * gcc.dg/thread_annot_lock-42.c: Likewise. diff --git a/gcc/Makefile.in b/gcc/Makefile.in --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1431,6 +1431,7 @@ OBJS-common = \ tree-ssanames.o \ tree-stdarg.o \ tree-tailcall.o \ + tree-threadsafe-analyze.o \ tree-vect-generic.o \ tree-vect-patterns.o \ tree-vect-data-refs.o \ @@ -2025,7 +2026,8 @@ c-decl.o : c-decl.c c-lang.h $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) \ $(HASHTAB_H) $(LIBFUNCS_H) $(EXCEPT_H) $(LANGHOOKS_DEF_H) \ $(TREE_DUMP_H) $(C_COMMON_H) $(CPPLIB_H) $(DIAGNOSTIC_CORE_H) \ $(INPUT_H) langhooks.h tree-mudflap.h pointer-set.h tree-iterator.h \ - $(PLUGIN_H) c-family/c-ada-spec.h c-family/c-objc.h + $(PLUGIN_H) c-family/c-ada-spec.h c-family/c-objc.h \ + tree-threadsafe-analyze.h c-errors.o: c-errors.c $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) $(TREE_H) \ $(C_TREE_H) $(FLAGS_H) $(DIAGNOSTIC_H) $(TM_P_H) @@ -2047,7 +2049,7 @@ c-parser.o : c-parser.c $(CONFIG_H) $(SYSTEM_H) coretypes.h \ $(GGC_H) $(TIMEVAR_H) $(INPUT_H) $(FLAGS_H) output.h \ gt-c-parser.h langhooks.h \ $(VEC_H) $(TARGET_H) $(CGRAPH_H) $(PLUGIN_H) \ - c-family/c-objc.h + c-family/c-objc.h tree-threadsafe-analyze.h c-typeck.o : c-typeck.c c-lang.h $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) \ $(TREE_H) $(C_TREE_H) $(TARGET_H) $(FLAGS_H) intl.h output.h $(EXPR_H) \ @@ -2100,7 +2102,7 @@ c-family/c-common.o : c-family/c-common.c $(CONFIG_H) $(SYSTEM_H) coretypes.h \ $(TARGET_H) tree-iterator.h langhooks.h tree-mudflap.h \ intl.h $(OPTS_H) $(CPPLIB_H) $(TREE_INLINE_H) $(HASHTAB_H) \ $(BUILTINS_DEF) $(CGRAPH_H) $(BASIC_BLOCK_H) $(TARGET_DEF_H) \ - $(LIBFUNCS_H) \ + $(LIBFUNCS_H) pointer-set.h tree-threadsafe-analyze.h \ gt-c-family-c-common.h c-family/c-cppbuiltin.o : c-family/c-cppbuiltin.c $(CONFIG_H) $(SYSTEM_H) \ @@ -2514,6 +2516,11 @@ tree-tailcall.o : tree-tailcall.c $(TREE_FLOW_H) $(CONFIG_H) $(SYSTEM_H) \ $(TREE_H) $(TM_P_H) $(FUNCTION_H) $(TM_H) coretypes.h \ $(TREE_DUMP_H) $(DIAGNOSTIC_H) $(EXCEPT_H) $(TREE_PASS_H) $(FLAGS_H) langhooks.h \ $(BASIC_BLOCK_H) $(DBGCNT_H) gimple-pretty-print.h $(TARGET_H) +tree-threadsafe-analyze.o : tree-threadsafe-analyze.c \ + tree-threadsafe-analyze.h $(TREE_FLOW_H) $(CONFIG_H) $(SYSTEM_H) $(TREE_H) \ + $(TM_H) coretypes.h $(C_COMMON_H) toplev.h $(INPUT_H) $(DIAGNOSTIC_H) \ + intl.h $(BASIC_BLOCK_H) $(TIMEVAR_H) $(TREE_PASS_H) $(TREE_DUMP_H) \ + langhooks.h pointer-set.h tree-pretty-print.h tree-ssa-propagate.h tree-ssa-sink.o : tree-ssa-sink.c $(TREE_FLOW_H) $(CONFIG_H) \ $(SYSTEM_H) $(TREE_H) $(DIAGNOSTIC_H) $(TIMEVAR_H) \ $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(FLAGS_H) alloc-pool.h \ @@ -2819,7 +2826,7 @@ toplev.o : toplev.c $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) $(TREE_H) \ $(CGRAPH_H) $(COVERAGE_H) alloc-pool.h $(GGC_H) $(INTEGRATE_H) \ $(OPTS_H) params.def tree-mudflap.h $(TREE_PASS_H) $(GIMPLE_H) \ tree-ssa-alias.h $(PLUGIN_H) realmpfr.h tree-diagnostic.h \ - tree-pretty-print.h opts-diagnostic.h + tree-pretty-print.h opts-diagnostic.h tree-threadsafe-analyze.h $(COMPILER) $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) \ -DTARGET_NAME=\"$(target_noncanonical)\" \ -c $(srcdir)/toplev.c $(OUTPUT_OPTION) diff --git a/gcc/attribs.c b/gcc/attribs.c --- a/gcc/attribs.c +++ b/gcc/attribs.c @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see #include "plugin.h" static void init_attributes (void); +static void merge_lock_attr_args (tree, tree); /* Table of the tables of attributes (common, language, format, machine) searched. */ @@ -317,6 +318,20 @@ decl_attributes (tree *node, tree attributes, int flags) } gcc_assert (is_attribute_p (spec->name, name)); + /* If this is a lock attribute and the purpose field of the args is + an error_mark_node, the attribute arguments have not been parsed yet + (as we delay the parsing of the attribute arguments until after the + whole class has been parsed). So don't handle this attribute now + but simply replace the error_mark_node with the current decl node + (which we will need when we call this routine again later). */ + if (args + && TREE_PURPOSE (args) == error_mark_node + && is_lock_attribute_with_args (name)) + { + TREE_PURPOSE (args) = *node; + continue; + } + if (spec->decl_required && !DECL_P (*anode)) { if (flags & ((int) ATTR_FLAG_DECL_NEXT @@ -396,9 +411,30 @@ decl_attributes (tree *node, tree attributes, int flags) } if (spec->handler != NULL) - returned_attrs = chainon ((*spec->handler) (anode, name, args, - flags, &no_add_attrs), - returned_attrs); + { + tree ret_attr = (*spec->handler) (anode, name, args, + flags, &no_add_attrs); + if (ret_attr) + { + /* For the lock attributes whose arguments (i.e. locks) are not + supported or the names are not in scope, we would demote the + attributes. For example, if 'foo' is not in scope in the + attribute "guarded_by(foo->lock), the attribute would be + downgraded to a "guarded" attribute. And in this case, the + handler would return the new, demoted attribute which is + appended to the current one so that it is handled in the next + iteration. */ + if (is_lock_attribute_with_args (name)) + { + gcc_assert (no_add_attrs); + TREE_CHAIN (ret_attr) = TREE_CHAIN (a); + TREE_CHAIN (a) = ret_attr; + continue; + } + else + returned_attrs = chainon (ret_attr, returned_attrs); + } + } /* Layout the decl in case anything changed. */ if (spec->type_required && DECL_P (*node) @@ -423,6 +459,13 @@ decl_attributes (tree *node, tree attributes, int flags) { if (simple_cst_equal (TREE_VALUE (a), args) == 1) break; + /* If a lock attribute of the same kind is already on the decl, + don't add this one again. Instead, merge the arguments. */ + if (is_lock_attribute_with_args (name)) + { + merge_lock_attr_args (a, args); + break; + } } if (a == NULL_TREE) @@ -477,3 +520,115 @@ decl_attributes (tree *node, tree attributes, int flags) return returned_attrs; } + +/* Return true if IDENTIFIER is the name of a lock attribute that takes + arguments, as listed in the if-statement below. */ + +bool +is_lock_attribute_with_args (const_tree identifier) +{ + gcc_assert (TREE_CODE (identifier) == IDENTIFIER_NODE); + + if (is_attribute_p ("guarded_by", identifier) + || is_attribute_p ("point_to_guarded_by", identifier) + || is_attribute_p ("acquired_after", identifier) + || is_attribute_p ("acquired_before", identifier) + || is_attribute_p ("exclusive_lock", identifier) + || is_attribute_p ("shared_lock", identifier) + || is_attribute_p ("exclusive_trylock", identifier) + || is_attribute_p ("shared_trylock", identifier) + || is_attribute_p ("unlock", identifier) + || is_attribute_p ("exclusive_locks_required", identifier) + || is_attribute_p ("shared_locks_required", identifier) + || is_attribute_p ("locks_excluded", identifier) + || is_attribute_p ("lock_returned", identifier)) + return true; + else + return false; +} + +/* Return true if IDENTIFIER is the name of a lock attribute. */ + +static bool +is_lock_attribute_p (const_tree identifier) +{ + gcc_assert (TREE_CODE (identifier) == IDENTIFIER_NODE); + + if (is_lock_attribute_with_args (identifier) + || is_attribute_p ("no_thread_safety_analysis", identifier) + || is_attribute_p ("ignore_reads_begin", identifier) + || is_attribute_p ("ignore_reads_end", identifier) + || is_attribute_p ("ignore_writes_begin", identifier) + || is_attribute_p ("ignore_writes_end", identifier) + || is_attribute_p ("unprotected_read", identifier) + || is_attribute_p ("guarded", identifier) + || is_attribute_p ("point_to_guarded", identifier) + || is_attribute_p ("lockable", identifier) + || is_attribute_p ("scoped_lockable", identifier)) + return true; + else + return false; +} + +/* Extract and return all lock attributes from the given ATTRS list. + Note that the ATTRS list could be damaged if there is any lock attribute + in the list so you should not call this function if you expect ATTRS to + be intact. For example, here is the given ATTRS list: + + attr("locks_excluded") -> attr("pure") -> attr("shared_locks_required") + + This function will return the following attribute list + + attr("shared_locks_required") -> attr("locks_excluded") */ + +tree +extract_lock_attributes (tree attrs) +{ + tree lock_attrs = NULL_TREE; + tree next; + + for ( ; attrs; attrs = next) + { + next = TREE_CHAIN (attrs); + if (is_lock_attribute_p (TREE_PURPOSE (attrs))) + { + TREE_CHAIN (attrs) = lock_attrs; + lock_attrs = attrs; + } + } + + return lock_attrs; +} + +/* This helper function is called when we see multiple lock attributes of + the same kind on a decl. ATTR is the first attribute of this kind we've + encountered and ADDITIONAL_ARGS is the args list of another attribute + of this kind. This function appends ADDITIONAL_ARGS to the args list + of ATTR. Note that we don't allow some of the lock attributes to appear + multiple times on a decl (such as 'guarded_by') and would emit a warning + if that happens. */ + +static void +merge_lock_attr_args (tree attr, tree additional_args) +{ + tree identifier = TREE_PURPOSE (attr); + + if (is_attribute_p ("acquired_after", identifier) + || is_attribute_p ("acquired_before", identifier) + || is_attribute_p ("exclusive_lock", identifier) + || is_attribute_p ("shared_lock", identifier) + || is_attribute_p ("exclusive_trylock", identifier) + || is_attribute_p ("shared_trylock", identifier) + || is_attribute_p ("unlock", identifier) + || is_attribute_p ("exclusive_locks_required", identifier) + || is_attribute_p ("shared_locks_required", identifier) + || is_attribute_p ("locks_excluded", identifier)) + TREE_VALUE (attr) = chainon (TREE_VALUE (attr), additional_args); + /* We don't allow the following lock attributes to appear multiple times + on a decl. */ + else if (is_attribute_p ("guarded_by", identifier) + || is_attribute_p ("point_to_guarded_by", identifier) + || is_attribute_p ("lock_returned", identifier)) + warning (OPT_Wattributes, "Additional %qs attribute ignored", + IDENTIFIER_POINTER (identifier)); +} diff --git a/gcc/c-decl.c b/gcc/c-decl.c --- a/gcc/c-decl.c +++ b/gcc/c-decl.c @@ -60,6 +60,7 @@ along with GCC; see the file COPYING3. If not see #include "pointer-set.h" #include "plugin.h" #include "c-family/c-ada-spec.h" +#include "tree-threadsafe-analyze.h" /* In grokdeclarator, distinguish syntactic contexts of declarators. */ enum decl_context @@ -2975,19 +2976,27 @@ undeclared_variable (location_t loc, tree id) if (current_function_decl == 0) { - error_at (loc, "%qE undeclared here (not in a function)", id); + /* Suppress the error message and return an error_mark_node if we are + parsing a lock attribute. We would like the lock attributes to + reference (and tolerate) names not in scope so that they provide + better code documentation capability. */ + if (!parsing_lock_attribute) + error_at (loc, "%qE undeclared here (not in a function)", id); scope = current_scope; } else { - if (!objc_diagnose_private_ivar (id)) - error_at (loc, "%qE undeclared (first use in this function)", id); - if (!already) - { - inform (loc, "each undeclared identifier is reported only" - " once for each function it appears in"); - already = true; - } + if (!parsing_lock_attribute) + { + if (!objc_diagnose_private_ivar (id)) + error_at (loc, "%qE undeclared (first use in this function)", id); + if (!already) + { + inform (loc, "each undeclared identifier is reported only" + " once for each function it appears in"); + already = true; + } + } /* If we are parsing old-style parameter decls, current_function_decl will be nonnull but current_function_scope will be null. */ diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c --- a/gcc/c-family/c-common.c +++ b/gcc/c-family/c-common.c @@ -46,6 +46,8 @@ along with GCC; see the file COPYING3. If not see #include "cgraph.h" #include "target-def.h" #include "libfuncs.h" +#include "pointer-set.h" +#include "tree-threadsafe-analyze.h" cpp_reader *parse_in; /* Declared in c-pragma.h. */ @@ -370,6 +372,21 @@ static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *); static tree handle_alloc_size_attribute (tree *, tree, tree, int, bool *); static tree handle_target_attribute (tree *, tree, tree, int, bool *); static tree handle_optimize_attribute (tree *, tree, tree, int, bool *); +static tree handle_lockable_attribute (tree *, tree, tree, int, bool *); +static tree handle_guarded_by_attribute (tree *, tree, tree, int, bool *); +static tree handle_point_to_guarded_by_attribute (tree *, tree, tree, int, + bool *); +static tree handle_guarded_attribute (tree *, tree, tree, int, bool *); +static tree handle_point_to_guarded_attribute (tree *, tree, tree, int, + bool *); +static tree handle_acquired_order_attribute (tree *, tree, tree, int, bool *); +static tree handle_lock_attribute (tree *, tree, tree, int, bool *); +static tree handle_unlock_attribute (tree *, tree, tree, int, bool *); +static tree handle_locks_required_excluded_attribute (tree *, tree, tree, int, + bool *); +static tree handle_lock_returned_attribute (tree *, tree, tree, int, bool *); +static tree handle_no_thread_safety_analysis_attribute (tree *, tree, tree, + int, bool *); static tree handle_no_split_stack_attribute (tree *, tree, tree, int, bool *); static tree handle_fnspec_attribute (tree *, tree, tree, int, bool *); @@ -695,6 +712,52 @@ const struct attribute_spec c_common_attribute_table[] = handle_target_attribute }, { "optimize", 1, -1, true, false, false, handle_optimize_attribute }, + { "lockable", 0, 0, false, false, false, + handle_lockable_attribute }, + { "scoped_lockable", 0, 0, false, false, false, + handle_lockable_attribute }, + { "guarded_by", 1, 1, true, false, false, + handle_guarded_by_attribute }, + { "point_to_guarded_by", 1, 1, true, false, false, + handle_point_to_guarded_by_attribute }, + { "guarded", 0, 0, true, false, false, + handle_guarded_attribute }, + { "point_to_guarded", 0, 0, true, false, false, + handle_point_to_guarded_attribute }, + { "acquired_after", 1, -1, true, false, false, + handle_acquired_order_attribute }, + { "acquired_before", 1, -1, true, false, false, + handle_acquired_order_attribute }, + { "exclusive_lock", 0, -1, true, false, false, + handle_lock_attribute }, + { "shared_lock", 0, -1, true, false, false, + handle_lock_attribute }, + { "exclusive_trylock", 0, -1, true, false, false, + handle_lock_attribute }, + { "shared_trylock", 0, -1, true, false, false, + handle_lock_attribute }, + { "unlock", 0, -1, true, false, false, + handle_unlock_attribute }, + { "exclusive_locks_required", 1, -1, true, false, false, + handle_locks_required_excluded_attribute }, + { "shared_locks_required", 1, -1, true, false, false, + handle_locks_required_excluded_attribute }, + { "locks_excluded", 1, -1, true, false, false, + handle_locks_required_excluded_attribute }, + { "lock_returned", 1, 1, true, false, false, + handle_lock_returned_attribute }, + { "no_thread_safety_analysis", 0, 0, true, false, false, + handle_no_thread_safety_analysis_attribute }, + { "ignore_reads_begin", 0, 0, true, false, false, + handle_no_thread_safety_analysis_attribute }, + { "ignore_reads_end", 0, 0, true, false, false, + handle_no_thread_safety_analysis_attribute }, + { "ignore_writes_begin", 0, 0, true, false, false, + handle_no_thread_safety_analysis_attribute }, + { "ignore_writes_end", 0, 0, true, false, false, + handle_no_thread_safety_analysis_attribute }, + { "unprotected_read", 0, 0, true, false, false, + handle_no_thread_safety_analysis_attribute }, { "no_split_stack", 0, 0, true, false, false, handle_no_split_stack_attribute }, /* For internal use (marking of builtins and runtime functions) only. @@ -5674,6 +5737,8 @@ attribute_takes_identifier_p (const_tree attr_id) || !strcmp ("format", spec->name) || !strcmp ("cleanup", spec->name)) return true; + else if (is_lock_attribute_with_args (attr_id)) + return true; else return targetm.attribute_takes_identifier_p (attr_id); } @@ -7916,6 +7981,738 @@ handle_no_split_stack_attribute (tree *node, tree name, return NULL_TREE; } + +/* Handle a "lockable" or a "scoped_lockable" attribute. */ + +static tree +handle_lockable_attribute (tree *node, tree name, tree ARG_UNUSED (args), + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + tree type = *node; + + if (TREE_CODE (type) != RECORD_TYPE && TREE_CODE (type) != UNION_TYPE) + { + if (TREE_CODE (type) == TYPE_DECL) + warning (OPT_Wattributes, + "%qE attribute should be applied to a type, not a" + " type declaration (i.e. typedef)", + name); + else + warning (OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + } + + return NULL_TREE; +} + +/* Return true if the LOCK tree is supported. Here are the supported lock + trees: + - var/param/field decl + - field access, direct or indirect, e.g. foo.mu, bar->lock + - array access with constant index, e.g. mutex[3], foo.mu[2] + - address-taken, e.g. &mu + + If the LOCK is an error_mark_node, which means we encountered an error + earlier when parsing the lock name/expression, then simply return false. */ + +static bool +supported_lock_expression (tree lock) +{ + if (lock == error_mark_node) + return false; + + switch (TREE_CODE (lock)) + { + case VAR_DECL: + case PARM_DECL: + case FIELD_DECL: + return true; + case ADDR_EXPR: + case INDIRECT_REF: + return supported_lock_expression (TREE_OPERAND (lock, 0)); + case COMPONENT_REF: + if (supported_lock_expression (TREE_OPERAND (lock, 0)) + && supported_lock_expression (TREE_OPERAND (lock, 1))) + return true; + else + return false; + case ARRAY_REF: + if (supported_lock_expression (TREE_OPERAND (lock, 0)) + && TREE_CODE (TREE_OPERAND (lock, 1)) == INTEGER_CST) + return true; + else + return false; + default: + return false; + } +} + +/* A helper function that returns + - a lock decl tree if LOCK is an identifier and can be bound + to a decl tree, + - LOCK if LOCK is an identifier which cannot be bound at this time, + (ADD_UNBOUND_LOCK_TO_MAP controls whether to insert the LOCK to the + unbound_lock_map), + - LOCK if LOCK is a supported expression tree, + - NULL_TREE otherwise. */ + +static tree +get_lock_decl (tree lock, bool add_unbound_lock_to_map) +{ + tree lockable_decl; + + if (TREE_CODE (lock) == IDENTIFIER_NODE) + { + lockable_decl = lookup_name (lock); + if (lockable_decl == NULL) + { + if (add_unbound_lock_to_map) + { + /* If the identifier is not in the current scope, add it to the + unbound lock map so that we will try to bind it later in our + analysis. */ + void **entry; + if (unbound_lock_map == NULL) + unbound_lock_map = pointer_map_create(); + entry = pointer_map_contains (unbound_lock_map, lock); + if (!entry) + { + entry = pointer_map_insert (unbound_lock_map, lock); + *entry = NULL; + } + } + lockable_decl = lock; + } + } + else if (supported_lock_expression (lock)) + lockable_decl = get_canonical_lock_expr (lock, NULL_TREE, false, NULL_TREE); + else + lockable_decl = NULL_TREE; + + return lockable_decl; +} + +/* Handle a "guarded_by" attribute. */ + +static tree +handle_guarded_by_attribute (tree *node, tree name, tree args, + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + tree decl = *node; + + if (TREE_CODE (decl) != VAR_DECL + && TREE_CODE (decl) != PARM_DECL + && TREE_CODE (decl) != FIELD_DECL) + { + warning (OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + return NULL_TREE; + } + + if (warn_thread_safety) + { + /* get_lock_decl will check if ARGS is a supported lock expression + and return a decl tree for an identifier node if possible. + If it returns NULL, which means the lock expression is not supported, + we will downgrade the "guarded_by" and "pt_guarded_by" to "guarded" + and "pt_guarded". */ + tree lock = get_lock_decl (TREE_VALUE (args), true); + if (!lock) + { + const char *new_name; + if (is_attribute_p ("guarded_by", name)) + new_name = "guarded"; + else + new_name = "point_to_guarded"; + + if (warn_unsupported_lock_name) + warning (OPT_Wattributes, "%qE attribute downgraded to '%s'" + " due to the unsupported lock argument", name, new_name); + *no_add_attrs = true; + return build_tree_list (get_identifier (new_name), NULL_TREE); + } + else + TREE_VALUE (args) = lock; + } + + return NULL_TREE; +} + +/* Handle a "point_to_guarded_by" attribute. */ + +static tree +handle_point_to_guarded_by_attribute (tree *node, tree name, tree args, + int flags, bool *no_add_attrs) +{ + /* We used to check if the point_to_guarded_by attribute is applied to + a pointer, but no longer do that as we now allow the attribute to + be applied to smart/scoped pointer objects. */ + + /* The rest of the handler is identical to the handler for + the guarded_by attr. */ + return handle_guarded_by_attribute (node, name, args, flags, no_add_attrs); +} + +/* Handle a "guarded" attribute. */ + +static tree +handle_guarded_attribute (tree *node, tree name, tree ARG_UNUSED (args), + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + tree decl = *node; + + if (TREE_CODE (decl) != VAR_DECL + && TREE_CODE (decl) != PARM_DECL + && TREE_CODE (decl) != FIELD_DECL) + { + warning (OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + return NULL_TREE; + } + + return NULL_TREE; +} + +/* Handle a "point_to_guarded" attribute. */ + +static tree +handle_point_to_guarded_attribute (tree *node, tree name, tree args, + int flags, bool *no_add_attrs) +{ + /* We used to check if the point_to_guarded attribute is applied to + a pointer, but no longer do that as we now allow the attribute to + be applied to smart/scoped pointer objects. */ + + /* The rest of the handler is identical to the handler for guarded attr. */ + return handle_guarded_attribute (node, name, args, flags, no_add_attrs); +} + +/* Add entries to the acquired_after_map with the declared lock DECL and + the locks specified in either the "acquired_after" or "acquired_before" + attributes arguments ARGS. The entries are mapping from the declared + lock to the set of locks that should be acquired earlier if they could + be held simultaneously by a thread. */ + +static void +populate_acquired_after_map (tree decl, tree args, bool is_acquired_after_attr) +{ + void **entry; + struct pointer_set_t *acquired_after_set; + + if (lock_acquired_after_map == NULL) + lock_acquired_after_map = pointer_map_create(); + + /* Now populate the acquired_after_map based on whether the attribute is + "acquired_after" or "acquired_before". For example, assuming the + acquired_after_map is empty, with the following declaration, + + Mutex mu1 __attribute__ ((acquired_after(mu2, mu3))); + + we will add an entry, mu1 -> {mu2, mu3}, to the map. + + On the other hand, with the following declaration + + Mutex mu4 __attribute__ ((acquired_before(mu5, mu6))); + + we will add the following two entries to the map: + + mu5 -> { mu4 } + mu6 -> { mu4 } + */ + if (is_acquired_after_attr) + { + entry = pointer_map_contains (lock_acquired_after_map, decl); + if (!entry) + { + entry = pointer_map_insert (lock_acquired_after_map, decl); + *entry = pointer_set_create(); + } + acquired_after_set = (struct pointer_set_t *)*entry; + + /* We don't have to check the case where args is NULL because we have + specified in the c_common_attribute_table that acquired_after attr + needs at least one argument. */ + do + { + /* Grab the decl tree of the argument if it is an identifier. */ + tree lock_decl = get_lock_decl (TREE_VALUE (args), false); + + if (!lock_decl) + { + if (warn_unsupported_lock_name) + warning (OPT_Wattributes, "Unsupported argument of" + " 'acquired_after' attribute ignored"); + } + /* If the lock argument is not declared (so the lock_decl is + still an identifier, don't add it to the acquired-after set + and skip it. */ + else if (TREE_CODE (lock_decl) != IDENTIFIER_NODE) + pointer_set_insert (acquired_after_set, lock_decl); + + args = TREE_CHAIN (args); + } + while (args != NULL_TREE); + } + else + { + /* If the control reaches here, the attribute is "acquired_before". + Again, we don't have to check the case where args is NULL because + we have specified in the c_common_attribute_table that + "acquired_before" attr needs at least one argument. */ + do + { + /* Grab the decl tree of the argument if it is an identifier. */ + tree lock_decl = get_lock_decl (TREE_VALUE (args), false); + + if (!lock_decl) + { + if (warn_unsupported_lock_name) + warning (OPT_Wattributes, "Unsupported argument of" + " 'acquired_before' attribute ignored"); + args = TREE_CHAIN (args); + continue; + } + + /* If the lock argument is not declared, skip it. */ + if (TREE_CODE (lock_decl) == IDENTIFIER_NODE) + { + args = TREE_CHAIN (args); + continue; + } + + entry = pointer_map_contains (lock_acquired_after_map, lock_decl); + if (!entry) + { + entry = pointer_map_insert (lock_acquired_after_map, lock_decl); + *entry = pointer_set_create(); + } + acquired_after_set = (struct pointer_set_t *)*entry; + + pointer_set_insert (acquired_after_set, decl); + + args = TREE_CHAIN (args); + } + while (args != NULL_TREE); + } +} + +/* Handle either an "acquired_after" or an "acquired_before" attribute. */ + +static tree +handle_acquired_order_attribute (tree *node, tree name, tree args, + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + tree decl = *node; + tree lockable_type; + tree old_decl; + bool is_acquired_after_attr; + + if (TREE_CODE (decl) != VAR_DECL + && TREE_CODE (decl) != PARM_DECL + && TREE_CODE (decl) != FIELD_DECL) + { + warning (OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + return NULL_TREE; + } + + if (POINTER_TYPE_P (TREE_TYPE (decl)) + || TREE_CODE (TREE_TYPE (decl)) == ARRAY_TYPE) + lockable_type = TREE_TYPE (TREE_TYPE (decl)); + else + lockable_type = TREE_TYPE (decl); + + /* Make sure this attribute is used only on a lock variable. */ + if (!lookup_attribute ("lockable", TYPE_ATTRIBUTES(lockable_type))) + { + warning (OPT_Wattributes, + "%qE attribute ignored for a non-lockable", name); + *no_add_attrs = true; + return NULL_TREE; + } + + /* If thread safety warning is not enabled, don't bother to populate + the acquired_after map. */ + if (!warn_thread_safety) + return NULL_TREE; + + if (is_attribute_p ("acquired_after", name)) + is_acquired_after_attr = true; + else + { + gcc_assert (is_attribute_p ("acquired_before", name)); + is_acquired_after_attr = false; + } + + /* The decl could be a duplicate. If so, we need to call lookup_name to + find the final one and use it in the lock_acquired_after_map. */ + gcc_assert (DECL_NAME (decl)); + old_decl = lookup_name (DECL_NAME (decl)); + if (old_decl) + decl = old_decl; + + /* Add entries to the acquired_after_map using the attribute. */ + populate_acquired_after_map (decl, args, is_acquired_after_attr); + + return NULL_TREE; +} + +/* This is a helper function that checks if LOCK_ID is a formal parameter + of FDECL, and if so, sets *POS with the position (1-based) of the + parameter corresponding to LOCK_ID. */ + +static bool +is_lock_formal_parameter (tree fdecl, tree lock_id, int *pos) +{ + tree parm; + int parm_pos; + + if (TREE_CODE (lock_id) != IDENTIFIER_NODE) + return false; + + for (parm = DECL_ARGUMENTS (fdecl), parm_pos = 1; + parm; + parm = TREE_CHAIN (parm), ++parm_pos) + { + if (DECL_NAME (parm) == lock_id) + { + *pos = parm_pos; + return true; + } + } + return false; +} + +/* This helper routine is called to check the validity of the arguments of + a lock or an unlock attribute when we expect the attribute should take + at least one argument. */ + +static tree +check_lock_unlock_attr_args (tree *node, tree name, tree args, + bool *no_add_attrs, bool is_scoped_lock, + bool is_trylock) +{ + int lock_pos; + int num_parms = 0; + tree curr_arg; + tree prev_arg = NULL_TREE; + bool error_mark_added = false; + + if (args == NULL_TREE) + { + error ("%qE attribute needs at least a lock argument", name); + *no_add_attrs = true; + return NULL_TREE; + } + + if (is_scoped_lock) + { + if (TREE_CHAIN (args) != NULL_TREE) + { + error ("%qE attribute takes a single argument for a scoped" + " lockable type", name); + *no_add_attrs = true; + return NULL_TREE; + } + } + else if (is_trylock) + { + if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST) + { + error ("The first argument of the %qE attribute must be either a" + " boolean or an integer value", name); + *no_add_attrs = true; + return NULL_TREE; + } + args = TREE_CHAIN (args); + if (args == NULL_TREE) + return NULL_TREE; + } + + curr_arg = args; + + /* Iterate through the attribute's argument list. */ + do + { + if (TREE_CODE (TREE_VALUE (curr_arg)) == INTEGER_CST) + { + /* Check whether the lock argument position is out of bound. */ + lock_pos = TREE_INT_CST_LOW (TREE_VALUE (curr_arg)); + + /* We lazily compute the number of fdecl arguments when we see + an integer argument of the attribute for the first time. */ + if (num_parms == 0) + num_parms = type_num_arguments (TREE_TYPE (*node)); + if (lock_pos > num_parms || lock_pos < 1) + { + error ("Parameter position (%i) specified in %qE attribute is" + " not valid", lock_pos, name); + *no_add_attrs = true; + return NULL_TREE; + } + } + else if (is_lock_formal_parameter(*node, TREE_VALUE (curr_arg), + &lock_pos)) + { + /* While the public documentation for lock/unlock attributes allow + users to use formal parameters to specify locks, internally we + convert the identifier nodes (for the formal parameters) to + integers that represent the parameter positions. */ + if (warn_thread_safety) + TREE_VALUE (curr_arg) = build_int_cst(NULL_TREE, lock_pos); + } + else + { + /* If we reach here, the attribute argument could be a global + variable, a class member, or even an expression. If the leftmost + operand of the argument is not a parameter of the annotated + function, it is an error for a scoped lock's constructor (which + is usually annotated as a locking primitive) as the lock should + be passed in to the constructor. */ + if (is_scoped_lock + && (TREE_CODE (get_leftmost_base_var (TREE_VALUE (curr_arg))) + != PARM_DECL)) + { + error ("%qE attribute needs to specify a function parameter" + " for a scoped lockable type", name); + *no_add_attrs = true; + return NULL_TREE; + } + + if (warn_thread_safety) + { + /* get_lock_decl will check if curr_arg is a supported lock + expression and return a decl tree for an identifier node + if possible. If it returns NULL, which means the lock + expression is not supported, the lock is ignored. But we + will add an error_mark_node in the argument list, which + serves as either a universal lock or an any lock (depending + on the attributes). */ + tree lock = get_lock_decl (TREE_VALUE (curr_arg), true); + if (!lock) + { + if (warn_unsupported_lock_name) + warning (OPT_Wattributes, "Unsupported argument of" + " %qE attribute ignored", name); + if (prev_arg && error_mark_added) + { + /* If an error_mark_node is already in the argument + list, simply ignore this unsupported lock. */ + TREE_CHAIN (prev_arg) = TREE_CHAIN (curr_arg); + curr_arg = TREE_CHAIN (curr_arg); + continue; + } + else + { + /* The first time we see an unsupported lock, add + an error_mark_node to the argument list. */ + TREE_VALUE (curr_arg) = error_mark_node; + error_mark_added = true; + } + } + else + TREE_VALUE (curr_arg) = lock; + } + } + + prev_arg = curr_arg; + curr_arg = TREE_CHAIN (curr_arg); + } + while (curr_arg != NULL_TREE); + + /* If the locks_excluded attribute contains only the error_mark_node in + its argument list, don't bother to apply the attribute to the decl. */ + if (TREE_VALUE (args) == error_mark_node + && TREE_CHAIN (args) == NULL_TREE + && is_attribute_p ("locks_excluded", name)) + *no_add_attrs = true; + + return NULL_TREE; +} + +/* Handle any of the following attribute used for annotating locking + primitives: "exclusive_lock", "shared_lock", "exclusive_trylock", and + "shared_trylock". */ + +static tree +handle_lock_attribute (tree *node, tree name, tree args, + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + tree lockable_type; + bool is_trylock; + + if (TREE_CODE (*node) != FUNCTION_DECL) + { + warning (OPT_Wattributes, + "%qE attribute ignored for a non-function declaration", name); + *no_add_attrs = true; + return NULL_TREE; + } + + lockable_type = DECL_CONTEXT (*node); + if (lockable_type && !TYPE_P (lockable_type)) + lockable_type = NULL_TREE; + + if (is_attribute_p ("exclusive_trylock", name) + || is_attribute_p ("shared_trylock", name)) + is_trylock = true; + else + is_trylock = false; + + if (!is_trylock + && lockable_type + && lookup_attribute ("lockable", TYPE_ATTRIBUTES (lockable_type))) + { + /* If the annotated locking primitive is a member function of + a lockable type, the attribute should not take any argument + (to specify the lock to be acquired), unless the primitive is + a trylock which requires at least an argument to specify the + return value on successful lock acquisition. */ + if (args != NULL_TREE) + { + warning (OPT_Wattributes, "Argument of %qE attribute ignored for" + " a locking primitive of a lockable type", name); + *no_add_attrs = true; + } + return NULL_TREE; + } + else + { + /* If the attribute does require arguments, check their validity. */ + bool is_scoped_lock = (lockable_type + && lookup_attribute ("scoped_lockable", + TYPE_ATTRIBUTES ( + lockable_type))); + return check_lock_unlock_attr_args (node, name, args, no_add_attrs, + is_scoped_lock, is_trylock); + } +} + +/* Handle an "unlock" attribute. */ + +static tree +handle_unlock_attribute (tree *node, tree name, tree ARG_UNUSED (args), + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + tree lockable_type; + + if (TREE_CODE (*node) != FUNCTION_DECL) + { + warning (OPT_Wattributes, + "%qE attribute ignored for a non-function declaration", name); + *no_add_attrs = true; + return NULL_TREE; + } + + lockable_type = DECL_CONTEXT (*node); + if (lockable_type && !TYPE_P (lockable_type)) + lockable_type = NULL_TREE; + + if (lockable_type + && (lookup_attribute ("lockable", TYPE_ATTRIBUTES (lockable_type)) + || lookup_attribute ("scoped_lockable", + TYPE_ATTRIBUTES (lockable_type)))) + { + /* If the annotated unlocking primitive is a member function of + a lockable type or a destructor of a scoped lock, the attribute + should not take any argument (to specify the lock to be released). */ + if (args != NULL_TREE) + { + warning (OPT_Wattributes, "Argument of %qE attribute ignored for" + " an unlock method of a lockable type", name); + *no_add_attrs = true; + } + return NULL_TREE; + } + else + /* If the attribute does require arguments, check their validity. */ + return check_lock_unlock_attr_args (node, name, args, no_add_attrs, + false /* is_scoped_lock */, + false /* is_trylock */); +} + +/* Handle the following function attributes that specify function's lock + requirements: "exclusive_locks_required", "shared_locks_required", and + "locks_excluded". */ + +static tree +handle_locks_required_excluded_attribute (tree *node, tree name, tree args, + int ARG_UNUSED (flags), + bool *no_add_attrs) +{ + if (TREE_CODE (*node) != FUNCTION_DECL) + { + warning (OPT_Wattributes, + "%qE attribute ignored for a non-function declaration", name); + *no_add_attrs = true; + return NULL_TREE; + } + + /* If thread safety warning is not enabled, don't bother to convert + lock identifiers to decls. */ + if (!warn_thread_safety) + return NULL_TREE; + + /* The rest of the handler is the same as that of the lock/unlock primitive + attributes. */ + return check_lock_unlock_attr_args (node, name, args, no_add_attrs, + false /* is_scoped_lock */, + false /* is_trylock */); +} + +/* Handle a "lock_returned" attribute. */ + +static tree +handle_lock_returned_attribute (tree *node, tree name, tree args, + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + if (TREE_CODE (*node) != FUNCTION_DECL) + { + warning (OPT_Wattributes, + "%qE attribute ignored for a non-function declaration", name); + *no_add_attrs = true; + return NULL_TREE; + } + + if (warn_thread_safety) + { + /* get_lock_decl will check if ARGS is a supported lock expression + and return a decl tree for an identifier node if possible. + If it returns NULL, which means the lock expression is not supported, + the attribute is ignored. */ + tree lock = get_lock_decl (TREE_VALUE (args), true); + if (!lock) + { + if (warn_unsupported_lock_name) + warning (OPT_Wattributes, "%qE attribute ignored due to the" + " unsupported argument", name); + *no_add_attrs = true; + } + else + TREE_VALUE (args) = lock; + } + + return NULL_TREE; +} + +/* Handle a "no_thread_safety_analysis" attribute. */ + +static tree +handle_no_thread_safety_analysis_attribute (tree *node, tree name, + tree ARG_UNUSED (args), + int ARG_UNUSED (flags), + bool *no_add_attrs) +{ + if (TREE_CODE (*node) != FUNCTION_DECL) + { + warning (OPT_Wattributes, + "%qE attribute ignored for a non-function declaration", name); + *no_add_attrs = true; + } + + return NULL_TREE; +} + /* Check for valid arguments being passed to a function. ATTRS is a list of attributes. There are NARGS arguments in the array ARGARRAY. TYPELIST is the list of argument types for the function. diff --git a/gcc/c-family/c-cppbuiltin.c b/gcc/c-family/c-cppbuiltin.c --- a/gcc/c-family/c-cppbuiltin.c +++ b/gcc/c-family/c-cppbuiltin.c @@ -749,6 +749,14 @@ c_cpp_builtins (cpp_reader *pfile) if (c_dialect_cxx () && TYPE_UNSIGNED (wchar_type_node)) cpp_define (pfile, "__WCHAR_UNSIGNED__"); + /* Define a macro indicating whether the thread safety attributes/analysis + is supported. */ + if (warn_thread_safety) + { + cpp_define (pfile, "__SUPPORT_TS_ANNOTATION__"); + cpp_define (pfile, "__SUPPORT_DYN_ANNOTATION__"); + } + /* Tell source code if the compiler makes sync_compare_and_swap builtins available. */ #ifdef HAVE_sync_compare_and_swapqi diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c --- a/gcc/c-family/c-pretty-print.c +++ b/gcc/c-family/c-pretty-print.c @@ -2202,6 +2202,10 @@ pp_c_expression (c_pretty_printer *pp, tree e) pp_c_expression (pp, C_MAYBE_CONST_EXPR_EXPR (e)); break; + case SSA_NAME: + pp_primary_expression (pp, SSA_NAME_VAR (e)); + break; + default: pp_unsupported_tree (pp, e); break; diff --git a/gcc/c-parser.c b/gcc/c-parser.c --- a/gcc/c-parser.c +++ b/gcc/c-parser.c @@ -57,6 +57,7 @@ along with GCC; see the file COPYING3. If not see #include "target.h" #include "cgraph.h" #include "plugin.h" +#include "tree-threadsafe-analyze.h" /* Initialization routine for this file. */ @@ -1151,6 +1152,7 @@ static struct c_expr c_parser_expression (c_parser *); static struct c_expr c_parser_expression_conv (c_parser *); static VEC(tree,gc) *c_parser_expr_list (c_parser *, bool, bool, VEC(tree,gc) **); +static tree c_parser_attr_arg_list (c_parser *); static void c_parser_omp_construct (c_parser *); static void c_parser_omp_threadprivate (c_parser *); static void c_parser_omp_barrier (c_parser *); @@ -1416,6 +1418,7 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, tree all_prefix_attrs; bool diagnosed_no_specs = false; location_t here = c_parser_peek_token (parser)->location; + bool orig_fndef_ok = fndef_ok; if (static_assert_ok && c_parser_next_token_is_keyword (parser, RID_STATIC_ASSERT)) @@ -1668,9 +1671,18 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, } else { - c_parser_error (parser, "expected %<,%> or %<;%>"); - c_parser_skip_to_end_of_block_or_statement (parser); - return; + /* Allow the lock attributes to be applied to function + definitions. */ + if (c_parser_next_token_is_not (parser, CPP_OPEN_BRACE) + || !orig_fndef_ok + || !postfix_attrs + || ((postfix_attrs = extract_lock_attributes (postfix_attrs)) + == NULL_TREE)) + { + c_parser_error (parser, "expected %<,%> or %<;%>"); + c_parser_skip_to_end_of_block_or_statement (parser); + return; + } } } else if (!fndef_ok) @@ -3367,7 +3379,6 @@ c_parser_attributes (c_parser *parser) || c_parser_next_token_is (parser, CPP_KEYWORD)) { tree attr, attr_name, attr_args; - VEC(tree,gc) *expr_list; if (c_parser_next_token_is (parser, CPP_COMMA)) { c_parser_consume_token (parser); @@ -3431,44 +3442,17 @@ c_parser_attributes (c_parser *parser) continue; } c_parser_consume_token (parser); - /* Parse the attribute contents. If they start with an - identifier which is followed by a comma or close - parenthesis, then the arguments start with that - identifier; otherwise they are an expression list. - In objective-c the identifier may be a classname. */ - if (c_parser_next_token_is (parser, CPP_NAME) - && (c_parser_peek_token (parser)->id_kind == C_ID_ID - || (c_dialect_objc () - && c_parser_peek_token (parser)->id_kind == C_ID_CLASSNAME)) - && ((c_parser_peek_2nd_token (parser)->type == CPP_COMMA) - || (c_parser_peek_2nd_token (parser)->type - == CPP_CLOSE_PAREN))) - { - tree arg1 = c_parser_peek_token (parser)->value; - c_parser_consume_token (parser); - if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN)) - attr_args = build_tree_list (NULL_TREE, arg1); - else - { - tree tree_list; - c_parser_consume_token (parser); - expr_list = c_parser_expr_list (parser, false, true, NULL); - tree_list = build_tree_list_vec (expr_list); - attr_args = tree_cons (NULL_TREE, arg1, tree_list); - release_tree_vector (expr_list); - } - } - else - { - if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN)) - attr_args = NULL_TREE; - else - { - expr_list = c_parser_expr_list (parser, false, true, NULL); - attr_args = build_tree_list_vec (expr_list); - release_tree_vector (expr_list); - } - } + /* If this is a lock annotation attribute that takes arguments, + set a flag so that we can make the parser tolerant of lock names + not in scope or unsupported. */ + if (is_lock_attribute_with_args (attr_name)) + parsing_lock_attribute = true; + /* Parse the attribute contents. */ + if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN)) + attr_args = NULL_TREE; + else + attr_args = c_parser_attr_arg_list (parser); + parsing_lock_attribute = false; attr = build_tree_list (attr_name, attr_args); if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN)) c_parser_consume_token (parser); @@ -8156,6 +8140,69 @@ c_parser_objc_at_dynamic_declaration (c_parser *parser) objc_add_dynamic_declaration (loc, list); } +/* Parse the attribute contents (argument list). This routine is similar + to c_parser_expr_list except that when an argument is an identifier, + instead of calling c_parser_expr_no_commas to get its corresponding + DECL tree, we leave the identifier node as-is in the argument list. + This allows an attribute to take as an argument an identifier not + currently in scope when the attribute is being parsed. If the DECL tree + of an identifier argument is needed later in the compilation, it is the + attribute handlers' responsibility (see c-common.c) to replace the + IDENTIFIER node with the DECL node. See also cp/parser.c + (cp_parser_parenthesized_expression_list) for an example. */ + +static tree +c_parser_attr_arg_list (c_parser *parser) +{ + struct c_expr expr; + tree ret, cur, identifier; + + /* Process the first argument and initialize ret and cur. */ + if (c_parser_next_token_is (parser, CPP_NAME) + && (c_parser_peek_token (parser)->id_kind == C_ID_ID + || (c_dialect_objc () + && c_parser_peek_token (parser)->id_kind == C_ID_CLASSNAME)) + && ((c_parser_peek_2nd_token (parser)->type == CPP_COMMA) + || (c_parser_peek_2nd_token (parser)->type == CPP_CLOSE_PAREN))) + { + /* If the token is an identifier which is followed by a comma or close + parenthesis, then the argument is an identifier. */ + identifier = c_parser_peek_token (parser)->value; + c_parser_consume_token (parser); + ret = cur = build_tree_list (NULL_TREE, identifier); + } + else + { + expr = c_parser_expr_no_commas (parser, NULL); + expr.value = c_fully_fold (expr.value, false, NULL); + ret = cur = build_tree_list (NULL_TREE, expr.value); + } + + /* Process the remaining arguments of the list if there is any. */ + while (c_parser_next_token_is (parser, CPP_COMMA)) + { + c_parser_consume_token (parser); + if (c_parser_next_token_is (parser, CPP_NAME) + && (c_parser_peek_token (parser)->id_kind == C_ID_ID + || (c_dialect_objc () + && c_parser_peek_token (parser)->id_kind == C_ID_CLASSNAME)) + && ((c_parser_peek_2nd_token (parser)->type == CPP_COMMA) + || (c_parser_peek_2nd_token (parser)->type == CPP_CLOSE_PAREN))) + { + identifier = c_parser_peek_token (parser)->value; + c_parser_consume_token (parser); + cur = TREE_CHAIN (cur) = build_tree_list (NULL_TREE, identifier); + } + else + { + expr = c_parser_expr_no_commas (parser, NULL); + expr.value = c_fully_fold (expr.value, false, NULL); + cur = TREE_CHAIN (cur) = build_tree_list (NULL_TREE, expr.value); + } + } + return ret; +} + /* Handle pragmas. Some OpenMP pragmas are associated with, and therefore should be considered, statements. ALLOW_STMT is true if we're within diff --git a/gcc/common.opt b/gcc/common.opt --- a/gcc/common.opt +++ b/gcc/common.opt @@ -649,6 +649,38 @@ Wthread-unsupported-lock-name Common Var(warn_unsupported_lock_name) Init(0) Warning Warn about uses of unsupported lock names in attributes +Wthread-safety +Common Var(warn_thread_safety) Warning +Warn about potential thread safety issues when the code is annotated with thread safety attributes + +Wthread-unguarded-var +Common Var(warn_thread_unguarded_var) Init(1) Warning +Warn about shared variables not properly protected by locks specified in the attributes + +Wthread-unguarded-func +Common Var(warn_thread_unguarded_func) Init(1) Warning +Warn about function calls not properly protected by locks specified in the attributes + +Wthread-mismatched-lock-order +Common Var(warn_thread_mismatched_lock_order) Init(1) Warning +Warn about lock acquisition order inconsistent with what specified in the attributes + +Wthread-mismatched-lock-acq-rel +Common Var(warn_thread_mismatched_lock_acq_rel) Init(1) Warning +Warn about mismatched lock acquisition and release + +Wthread-reentrant-lock +Common Var(warn_thread_reentrant_lock) Init(1) Warning +Warn about a lock being acquired recursively + +Wthread-unsupported-lock-name +Common Var(warn_unsupported_lock_name) Init(0) Warning +Warn about uses of unsupported lock names in attributes + +Wthread-attr-bind-param +Common Var(warn_thread_attr_bind_param) Init(1) Warning +Make the thread safety analysis try to bind the function parameters used in the attributes + Wtype-limits Common Var(warn_type_limits) Init(-1) Warning Warn if a comparison is always true or always false due to the limited range of the data type diff --git a/gcc/cp/Make-lang.in b/gcc/cp/Make-lang.in --- a/gcc/cp/Make-lang.in +++ b/gcc/cp/Make-lang.in @@ -248,7 +248,7 @@ CXX_PRETTY_PRINT_H = cp/cxx-pretty-print.h $(C_PRETTY_PRINT_H) cp/lex.o: cp/lex.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) \ $(C_PRAGMA_H) output.h input.h cp/operators.def $(TM_P_H) \ - c-family/c-objc.h + c-family/c-objc.h tree-threadsafe-analyze.h cp/cp-lang.o: cp/cp-lang.c $(CXX_TREE_H) $(TM_H) debug.h langhooks.h \ $(LANGHOOKS_DEF_H) $(C_COMMON_H) gtype-cp.h gt-cp-cp-lang.h \ cp/cp-objcp-common.h $(EXPR_H) $(TARGET_H) @@ -268,16 +268,17 @@ cp/cp-objcp-common.o : cp/cp-objcp-common.c $(CONFIG_H) $(SYSTEM_H) \ langhooks.h $(LANGHOOKS_DEF_H) $(DIAGNOSTIC_H) debug.h \ $(CXX_PRETTY_PRINT_H) cp/cp-objcp-common.h gt-cp-cp-objcp-common.h cp/typeck2.o: cp/typeck2.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) output.h \ - $(TM_P_H) $(DIAGNOSTIC_CORE_H) gt-cp-typeck2.h $(REAL_H) intl.h + $(TM_P_H) $(DIAGNOSTIC_CORE_H) gt-cp-typeck2.h $(REAL_H) intl.h \ + tree-threadsafe-analyze.h cp/typeck.o: cp/typeck.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) \ toplev.h $(DIAGNOSTIC_H) convert.h $(C_COMMON_H) $(TARGET_H) \ - output.h c-family/c-objc.h + output.h c-family/c-objc.h tree-threadsafe-analyze.h cp/class.o: cp/class.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) toplev.h \ $(TARGET_H) convert.h $(CGRAPH_H) $(TREE_DUMP_H) gt-cp-class.h \ $(SPLAY_TREE_H) cp/call.o: cp/call.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) toplev.h \ $(DIAGNOSTIC_CORE_H) intl.h gt-cp-call.h convert.h $(TARGET_H) langhooks.h \ - c-family/c-objc.h + c-family/c-objc.h tree-threadsafe-analyze.h cp/friend.o: cp/friend.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) cp/init.o: cp/init.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) \ $(EXCEPT_H) $(TARGET_H) @@ -298,7 +299,7 @@ cp/except.o: cp/except.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) \ cp/expr.o: cp/expr.c $(CXX_TREE_H) $(TM_H) $(FLAGS_H) $(TM_P_H) cp/pt.o: cp/pt.c $(CXX_TREE_H) $(TM_H) cp/decl.h cp/cp-objcp-common.h \ toplev.h $(TREE_INLINE_H) pointer-set.h gt-cp-pt.h vecprim.h intl.h \ - c-family/c-objc.h + c-family/c-objc.h tree-threadsafe-analyze.h cp/error.o: cp/error.c $(CXX_TREE_H) $(TM_H) $(DIAGNOSTIC_H) \ $(FLAGS_H) $(REAL_H) $(LANGHOOKS_DEF_H) $(CXX_PRETTY_PRINT_H) \ tree-diagnostic.h tree-pretty-print.h c-family/c-objc.h @@ -307,7 +308,7 @@ cp/repo.o: cp/repo.c $(CXX_TREE_H) $(TM_H) toplev.h $(DIAGNOSTIC_CORE_H) \ cp/semantics.o: cp/semantics.c $(CXX_TREE_H) $(TM_H) toplev.h \ $(FLAGS_H) output.h $(RTL_H) $(TIMEVAR_H) \ $(TREE_INLINE_H) $(CGRAPH_H) $(TARGET_H) $(C_COMMON_H) $(GIMPLE_H) \ - bitmap.h gt-cp-semantics.h c-family/c-objc.h + bitmap.h gt-cp-semantics.h c-family/c-objc.h tree-threadsafe-analyze.h cp/dump.o: cp/dump.c $(CXX_TREE_H) $(TM_H) $(TREE_DUMP_H) cp/optimize.o: cp/optimize.c $(CXX_TREE_H) $(TM_H) \ input.h $(PARAMS_H) debug.h $(TREE_INLINE_H) $(GIMPLE_H) \ @@ -316,7 +317,7 @@ cp/mangle.o: cp/mangle.c $(CXX_TREE_H) $(TM_H) $(REAL_H) \ gt-cp-mangle.h $(TARGET_H) $(TM_P_H) $(CGRAPH_H) cp/parser.o: cp/parser.c $(CXX_TREE_H) $(TM_H) $(DIAGNOSTIC_CORE_H) \ gt-cp-parser.h output.h $(TARGET_H) $(PLUGIN_H) intl.h \ - c-family/c-objc.h + c-family/c-objc.h tree-threadsafe-analyze.h cp/cp-gimplify.o: cp/cp-gimplify.c $(CXX_TREE_H) $(C_COMMON_H) \ $(TM_H) coretypes.h pointer-set.h tree-iterator.h diff --git a/gcc/cp/call.c b/gcc/cp/call.c --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -39,6 +39,7 @@ along with GCC; see the file COPYING3. If not see #include "convert.h" #include "langhooks.h" #include "c-family/c-objc.h" +#include "tree-threadsafe-analyze.h" /* The various kinds of conversion. */ @@ -204,6 +205,8 @@ static conversion *direct_reference_binding (tree, conversion *); static bool promoted_arithmetic_type_p (tree); static conversion *conditional_conversion (tree, tree); static char *name_as_c_string (tree, tree, bool *); +static void find_const_memfunc_with_identical_prototype (tree, + struct z_candidate *); static tree prep_operand (tree); static void add_candidates (tree, tree, const VEC(tree,gc) *, tree, tree, bool, tree, tree, int, struct z_candidate **); @@ -4812,7 +4815,20 @@ build_new_op (enum tree_code code, int flags, tree arg1, tree arg2, tree arg3, if (resolve_args (arglist) == NULL) result = error_mark_node; else - result = build_over_call (cand, LOOKUP_NORMAL, complain); + { + result = build_over_call (cand, LOOKUP_NORMAL, complain); + /* If thread safety check is enabled and FN is not a const + member function, try to see if there is a const overload in + the candidates list (if we haven't done so already). */ + if (warn_thread_safety + && DECL_FUNCTION_MEMBER_P (cand->fn) + && !DECL_CONST_MEMFUNC_P (cand->fn) + && (!DECL_ATTRIBUTES (cand->fn) + || !lookup_attribute ("has_const_overload", + DECL_ATTRIBUTES (cand->fn)))) + find_const_memfunc_with_identical_prototype (cand->fn, + candidates); + } } else { @@ -6669,6 +6685,53 @@ name_as_c_string (tree name, tree type, bool *free_p) return pretty_name; } +/* Given a decl FN which is a non-const member function, try to find if + there is another candidate that is a const member function with the same + prototype (i.e. parameter list) as FN. And if found, attach an internal + attribute "has_const_overload" to FN. */ + +static void +find_const_memfunc_with_identical_prototype (tree fn, + struct z_candidate *candidates) +{ + bool find_const_overload = false; + struct z_candidate *cand; + + for (cand = candidates; cand; cand = cand->next) + { + tree candfunc = cand->fn; + tree t1, t2; + + /* candfunc (or cand->fn) can be an IDENTIFIER_NODE if the candidate + is a builtin (see the add_candidate call in build_builtin_candidate). + Since it's not a user-defined overload, just skip it. */ + if (TREE_CODE (candfunc) != FUNCTION_DECL) + continue; + + /* Skip FN itself and candidates that are not const. */ + if (candfunc == fn || !DECL_CONST_MEMFUNC_P (candfunc)) + continue; + + /* Compare the parameter lists of FN and CANDFUNC. */ + for (t1 = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (fn))), + t2 = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (candfunc))); + t1 && t2; + t1 = TREE_CHAIN (t1), t2 = TREE_CHAIN (t2)) + if (t1 != t2) + break; + + if (!t1 && !t2) + { + find_const_overload = true; + break; + } + } + + if (find_const_overload) + DECL_ATTRIBUTES (fn) = tree_cons (get_identifier ("has_const_overload"), + NULL_TREE, DECL_ATTRIBUTES (fn)); +} + /* Build a call to "INSTANCE.FN (ARGS)". If FN_P is non-NULL, it will be set, upon return, to the function called. ARGS may be NULL. This may change ARGS. */ @@ -6907,7 +6970,10 @@ build_new_method_call (tree instance, tree fns, VEC(tree,gc) **args, if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE && is_dummy_object (instance_ptr)) { - if (complain & tf_error) + /* If the call appears in the argument list of a lock + annotation attribute, don't emit an error. Just return + the error_mark_node. */ + if ((complain & tf_error) && !parsing_lock_attribute) error ("cannot call member function %qD without object", fn); call = error_mark_node; @@ -6944,6 +7010,15 @@ build_new_method_call (tree instance, tree fns, VEC(tree,gc) **args, "operator delete(~X(f()))" (rather than generating "t = f(), ~X(t), operator delete (t)"). */ call = build_nop (void_type_node, call); + /* If thread safety check is enabled and FN is not a const + member function, try to see if there is a const overload in + the candidates list (if we haven't done so already). */ + if (warn_thread_safety + && !DECL_CONST_MEMFUNC_P (fn) + && (!DECL_ATTRIBUTES (fn) + || !lookup_attribute ("has_const_overload", + DECL_ATTRIBUTES (fn)))) + find_const_memfunc_with_identical_prototype (fn, candidates); } } } diff --git a/gcc/cp/class.c b/gcc/cp/class.c --- a/gcc/cp/class.c +++ b/gcc/cp/class.c @@ -8380,11 +8380,12 @@ build_rtti_vtbl_entries (tree binfo, vtbl_init_data* vid) CONSTRUCTOR_APPEND_ELT (vid->inits, NULL_TREE, init); } -/* Fold a OBJ_TYPE_REF expression to the address of a function. - KNOWN_TYPE carries the true type of OBJ_TYPE_REF_OBJECT(REF). */ +/* Given an OBJ_TYPE_REF expression, REF, return the virtual function decl + using the method index. KNOWN_TYPE carries the true type of + OBJ_TYPE_REF_OBJECT(REF). */ tree -cp_fold_obj_type_ref (tree ref, tree known_type) +cp_get_virtual_function_decl (tree ref, tree known_type) { HOST_WIDE_INT index = tree_low_cst (OBJ_TYPE_REF_TOKEN (ref), 1); HOST_WIDE_INT i = 0; @@ -8405,9 +8406,67 @@ cp_fold_obj_type_ref (tree ref, tree known_type) DECL_VINDEX (fndecl))); #endif + return fndecl; +} + +/* Fold a OBJ_TYPE_REF expression to the address of a function. + KNOWN_TYPE carries the true type of OBJ_TYPE_REF_OBJECT(REF). */ + +tree +cp_fold_obj_type_ref (tree ref, tree known_type) +{ + + tree fndecl = cp_get_virtual_function_decl (ref, known_type); + cgraph_node (fndecl)->local.vtable_method = true; return build_address (fndecl); } +/* Determine whether the given DECL is a compiler-generated base field + in a derived class. */ + +bool +cp_decl_is_base_field (tree decl) +{ + if (TREE_CODE (decl) == FIELD_DECL && DECL_FIELD_IS_BASE (decl)) + return true; + else + return false; +} + +/* Return true if DECL is a constructor. */ + +bool +cp_decl_is_constructor (tree decl) +{ + return DECL_CONSTRUCTOR_P (decl); +} + +/* Return true if DECL is a destructor. */ + +bool +cp_decl_is_destructor (tree decl) +{ + return DECL_DESTRUCTOR_P (decl); +} + +/* Return + 1 if decl is a const member function, + 2 if decl is not a const member function but has a const overload that + has identical parameter list, + 0 otherwise. */ + +int +cp_decl_is_const_member_func (tree decl) +{ + if (DECL_CONST_MEMFUNC_P (decl)) + return 1; + else if (DECL_ATTRIBUTES (decl) + && lookup_attribute ("has_const_overload", DECL_ATTRIBUTES (decl))) + return 2; + else + return 0; +} + #include "gt-cp-class.h" diff --git a/gcc/cp/cp-lang.c b/gcc/cp/cp-lang.c --- a/gcc/cp/cp-lang.c +++ b/gcc/cp/cp-lang.c @@ -75,12 +75,22 @@ static tree get_template_argument_pack_elems_folded (const_tree); #define LANG_HOOKS_GET_GENERIC_FUNCTION_DECL get_function_template_decl #undef LANG_HOOKS_DWARF_NAME #define LANG_HOOKS_DWARF_NAME cxx_dwarf_name +#undef LANG_HOOKS_GET_VIRTUAL_FUNCTION_DECL +#define LANG_HOOKS_GET_VIRTUAL_FUNCTION_DECL cp_get_virtual_function_decl #undef LANG_HOOKS_INIT_TS #define LANG_HOOKS_INIT_TS cp_init_ts #undef LANG_HOOKS_EH_PERSONALITY #define LANG_HOOKS_EH_PERSONALITY cp_eh_personality #undef LANG_HOOKS_EH_RUNTIME_TYPE #define LANG_HOOKS_EH_RUNTIME_TYPE build_eh_type_type +#undef LANG_HOOKS_DECL_IS_BASE_FIELD +#define LANG_HOOKS_DECL_IS_BASE_FIELD cp_decl_is_base_field +#undef LANG_HOOKS_DECL_IS_CONSTRUCTOR +#define LANG_HOOKS_DECL_IS_CONSTRUCTOR cp_decl_is_constructor +#undef LANG_HOOKS_DECL_IS_DESTRUCTOR +#define LANG_HOOKS_DECL_IS_DESTRUCTOR cp_decl_is_destructor +#undef LANG_HOOKS_DECL_IS_CONST_MEMBER_FUNC +#define LANG_HOOKS_DECL_IS_CONST_MEMBER_FUNC cp_decl_is_const_member_func /* Each front end provides its own lang hook initializer. */ struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER; diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -4704,7 +4704,12 @@ extern void note_name_declared_in_class (tree, tree); extern tree get_vtbl_decl_for_binfo (tree); extern void debug_class (tree); extern void debug_thunks (tree); +extern tree cp_get_virtual_function_decl (tree, tree); extern tree cp_fold_obj_type_ref (tree, tree); +extern bool cp_decl_is_base_field (tree); +extern bool cp_decl_is_constructor (tree); +extern bool cp_decl_is_destructor (tree); +extern int cp_decl_is_const_member_func (tree); extern void set_linkage_according_to_type (tree, tree); extern void determine_key_method (tree); extern void check_for_override (tree, tree); diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c --- a/gcc/cp/decl2.c +++ b/gcc/cp/decl2.c @@ -1088,6 +1088,21 @@ is_late_template_attribute (tree attr, tree decl) if (is_attribute_p ("weak", name)) return true; + /* In general, the lock attributes with arguments need to be applied at + instantiation time as they could reference other members of the class. + However, if the purpose field of the arguments is an error_mark_node, + the arguments of the attributes have not been parsed yet. (See + cp_parser_save_attribute_arg_list in parser.c.) And in that case, + we want it to be applied at the definition time so that it will be + parsed at the end of the class/template definition. */ + if (is_lock_attribute_with_args (name)) + { + if (!args || TREE_PURPOSE (args) == error_mark_node) + return false; + else + return true; + } + /* If any of the arguments are dependent expressions, we can't evaluate the attribute until instantiation time. */ for (arg = args; arg; arg = TREE_CHAIN (arg)) diff --git a/gcc/cp/error.c b/gcc/cp/error.c --- a/gcc/cp/error.c +++ b/gcc/cp/error.c @@ -2361,6 +2361,11 @@ dump_expr (tree t, int flags) dump_expr (resolve_virtual_fun_from_obj_type_ref (t), flags); break; + case SSA_NAME: + dump_decl (SSA_NAME_VAR (t), + (flags & ~TFF_DECL_SPECIFIERS) | TFF_NO_FUNCTION_ARGUMENTS); + break; + /* This list is incomplete, but should suffice for now. It is very important that `sorry' does not call `report_error_function'. That could cause an infinite loop. */ diff --git a/gcc/cp/lex.c b/gcc/cp/lex.c --- a/gcc/cp/lex.c +++ b/gcc/cp/lex.c @@ -37,6 +37,7 @@ along with GCC; see the file COPYING3. If not see #include "output.h" #include "tm_p.h" #include "timevar.h" +#include "tree-threadsafe-analyze.h" static int interface_strcmp (const char *); static void init_cp_pragma (void); @@ -442,6 +443,13 @@ handle_pragma_java_exceptions (cpp_reader* dfile ATTRIBUTE_UNUSED) tree unqualified_name_lookup_error (tree name) { + /* Suppress the error message and return an error_mark_node if we are + parsing a lock attribute. We would like the lock attributes to + reference (and tolerate) names not in scope so that they provide + better code documentation capability. */ + if (parsing_lock_attribute) + return error_mark_node; + if (IDENTIFIER_OPNAME_P (name)) { if (name != ansi_opname (ERROR_MARK)) diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c --- a/gcc/cp/name-lookup.c +++ b/gcc/cp/name-lookup.c @@ -4624,6 +4624,43 @@ lookup_name_innermost_nonclass_level (tree name) POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, t); } +/* Given an identifier node (NAME), look up the name in the parameter list + (PARAM) of the function declaration that is being parsed, and return the + parm_decl if found. The parameter list can be either a tree list when + obtained from a cp_declarator object (e.g. when invoked from + cp_parser_init_declarator) or simply a chain of parameters when we have + the function decl (e.g. when invoked from + cp_parser_late_parsing_attribute_arg_lists). */ + +tree +lookup_name_in_func_params (tree param, tree name) +{ + /* If the compiler is instructed not to bind the names (in lock attributes) + to function parameters, just treat it as if the name lookup fails. */ + if (!warn_thread_attr_bind_param) + return unqualified_name_lookup_error (name); + + gcc_assert (TREE_CODE (name) == IDENTIFIER_NODE && param); + + for ( ; param; param = TREE_CHAIN (param)) + { + tree param_decl; + if (TREE_CODE (param) == TREE_LIST) + param_decl = TREE_VALUE (param); + else + { + gcc_assert (DECL_P (param)); + param_decl = param; + } + /* If param_decl is indeed a decl (it could be a VOID_TYPE if the + function has no parameter) and matches NAME, return it. */ + if (DECL_P (param_decl) && DECL_NAME (param_decl) == name) + return param_decl; + } + + return unqualified_name_lookup_error (name); +} + /* Returns true iff DECL is a block-scope extern declaration of a function or variable. */ diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -326,6 +326,7 @@ extern tree remove_hidden_names (tree); extern tree lookup_qualified_name (tree, tree, bool, bool); extern tree lookup_name_nonclass (tree); extern tree lookup_name_innermost_nonclass_level (tree); +extern tree lookup_name_in_func_params (tree, tree); extern bool is_local_extern (tree); extern tree lookup_function_nonclass (tree, VEC(tree,gc) *, bool); extern void push_local_binding (tree, tree, int); diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -37,6 +37,7 @@ along with GCC; see the file COPYING3. If not see #include "c-family/c-common.h" #include "c-family/c-objc.h" #include "plugin.h" +#include "tree-threadsafe-analyze.h" /* The lexer. */ @@ -1713,6 +1714,15 @@ typedef struct GTY(()) cp_parser { outermost class being defined is complete. */ VEC(cp_unparsed_functions_entry,gc) *unparsed_queues; + /* A list of attributes whose arguments are not yet parsed. The + TREE_VALUE of each list node contains a delayed attribute. + The argument of the attribute (i.e. TREE_VALUE of the attribute) + is a special tree list node, where the TREE_PURPOSE is error_mark_node + and the TREE_VALUE points to the cached tokens of the arguments. + This list is processed once the outermost class being defined is + complete. */ + tree unparsed_attribute_args_queue; + /* The number of classes whose definitions are currently in progress. */ unsigned num_classes_being_defined; @@ -1720,6 +1730,16 @@ typedef struct GTY(()) cp_parser { /* The number of template parameter lists that apply directly to the current declaration. */ unsigned num_template_parameter_lists; + + /* Record the paramter list of the function declaration that is being parsed + so that it can be looked up when later parsing the lock attributes which + might refer to a function parameter. */ + tree current_func_declarator_params; + + /* Record the scope of the current declarator as we might need to use it + parsing the lock attributes with arguments that should be considered in + the declarator's scope. */ + tree current_declarator_scope; } cp_parser; /* Managing the unparsed function queues. */ @@ -2087,9 +2107,9 @@ static tree cp_parser_asm_clobber_list static tree cp_parser_asm_label_list (cp_parser *); static tree cp_parser_attributes_opt - (cp_parser *); + (cp_parser *, bool); static tree cp_parser_attribute_list - (cp_parser *); + (cp_parser *, bool); static bool cp_parser_extension_opt (cp_parser *, int *); static void cp_parser_label_declaration @@ -2174,10 +2194,14 @@ static tree cp_parser_enclosed_template_argument_list (cp_parser *); static void cp_parser_save_default_args (cp_parser *, tree); +static tree cp_parser_save_attribute_arg_list + (cp_parser *); static void cp_parser_late_parsing_for_member (cp_parser *, tree); static void cp_parser_late_parsing_default_args (cp_parser *, tree); +static void cp_parser_late_parsing_attribute_arg_lists + (cp_parser *); static tree cp_parser_sizeof_operand (cp_parser *, enum rid); static tree cp_parser_trait_expr @@ -2346,6 +2370,12 @@ cp_parser_name_lookup_error (cp_parser* parser, name_lookup_error desired, location_t location) { + /* Suppress the error message if we are parsing a lock attribute. We would + like the lock attributes to reference (and tolerate) names not in scope + so that they provide better code documentation capability. */ + if (parsing_lock_attribute) + return; + /* If name lookup completely failed, tell the user that NAME was not declared. */ if (decl == error_mark_node) @@ -3255,6 +3285,9 @@ cp_parser_new (void) /* The unparsed function queue is empty. */ push_unparsed_function_queues (parser); + /* The unparsed attribute arguments queue is empty. */ + parser->unparsed_attribute_args_queue = NULL_TREE; + /* There are no classes being defined. */ parser->num_classes_being_defined = 0; @@ -5182,8 +5215,18 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p, && TREE_CODE (postfix_expression) == IDENTIFIER_NODE && cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_PAREN)) /* It is not a Koenig lookup function call. */ - postfix_expression - = unqualified_name_lookup_error (postfix_expression); + { + /* If we are parsing a lock attribute of a function decl, try to + see if the identifier is a function parameter first. */ + if (parsing_lock_attribute && parser->current_func_declarator_params) + postfix_expression + = lookup_name_in_func_params ( + parser->current_func_declarator_params, + postfix_expression); + else + postfix_expression + = unqualified_name_lookup_error (postfix_expression); + } /* Peek at the next token. */ token = cp_lexer_peek_token (parser->lexer); @@ -5700,10 +5743,31 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, { tree expr; - /* At the beginning of attribute lists, check to see if the - next token is an identifier. */ + /* At the beginning of attribute lists, check to see if the + next token is an identifier. If so, leave it as an identifier + without trying to look up the name. We need to make sure + it is a name followed by either a comma or a closing paren. + Otherwise, we would mistakenly treat 'foo' in 'foo->bar' as an + identifier and cause a parsing error. Note that if it is desirable + to bind the identifier (to its decl tree or something else) at + parsing, the identifier argument should be enclosed in parentheses, + as shown in the following example (taken from the test case + g++.dg/ext/tmplattr2.C). + + template <unsigned Len, unsigned Align> + struct aligned_storage + { + typedef char type[Len] __attribute__((aligned((Align)))); + }; + + In order for the 'Align' argument in attribute 'aligned' to be + bound to a TEMPLATE_PARM_INDEX, 'Align' needs to be enclosed in + parentheses. */ if (is_attribute_list == id_attr - && cp_lexer_peek_token (parser->lexer)->type == CPP_NAME) + && cp_lexer_peek_token (parser->lexer)->type == CPP_NAME + && ((cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_COMMA) + || (cp_lexer_peek_nth_token (parser->lexer, 2)->type + == CPP_CLOSE_PAREN))) { cp_token *token; @@ -5711,6 +5775,31 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, token = cp_lexer_consume_token (parser->lexer); /* Save the identifier. */ identifier = token->u.value; + /* When processing an attribute argument list, we used to set + is_attribute_list to false after finishing processing the + first argument so that the rest of the list was treated as + a normal expression list (which implies any identifier in the + rest of the list would be replaced with its corresponding DECL + node). However, that practice limits the ability for an + attribute to take as the second (or higher) argument an + identifier not currently in scope when the attribute is being + parsed. For example, assuming a function attribute + "exclusive_lock" takes a list of identifiers as parameters + (indicating what locks it acquires), the following usage would + get an error with the old implementation as mu2 is not yet in + scope when the attribute is parsed and therefore we couldn't + find its DECL node: + + int my_mutex_lock(Mutex *mu1, Mutex *mu2) + __attribute__ ((exclusive_lock(mu1, mu2))); + + In order to lift this limitation, the code is changed so that + we no longer set is_attribute_list to non_attr after processing + the first argument, and any remaining identifier node of the + list is not replaced with its DECL tree node. Instead, the + attribute handler routines (see c-common.c) will need to + take care of that. */ + VEC_safe_push (tree, gc, expression_list, identifier); } else { @@ -5736,7 +5825,13 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, else expr = cp_parser_assignment_expression (parser, cast_p, NULL); - if (fold_expr_p) + /* If EXPR is an attribute arg and a decl/param/field decl, + there is nothing to be folded. */ + if (fold_expr_p + && (!is_attribute_list + || (TREE_CODE (expr) != VAR_DECL + && TREE_CODE (expr) != PARM_DECL + && TREE_CODE (expr) != FIELD_DECL))) expr = fold_non_dependent_expr (expr); /* If we have an ellipsis, then this is an expression @@ -5761,10 +5856,6 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, goto skip_comma; } - /* After the first item, attribute lists look the same as - expression lists. */ - is_attribute_list = non_attr; - get_comma:; /* If the next token isn't a `,', then we are done. */ if (cp_lexer_next_token_is_not (parser->lexer, CPP_COMMA)) @@ -5798,9 +5889,6 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, parser->greater_than_is_operator_p = saved_greater_than_is_operator_p; - if (identifier) - VEC_safe_insert (tree, gc, expression_list, 0, identifier); - return expression_list; } @@ -7851,7 +7939,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) cp_parser_require (parser, CPP_CLOSE_PAREN, RT_CLOSE_PAREN); - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Parse optional `mutable' keyword. */ if (cp_lexer_next_token_is_keyword (parser->lexer, RID_MUTABLE)) @@ -8282,7 +8370,7 @@ cp_parser_label_for_labeled_statement (cp_parser* parser) tree attrs; cp_parser_parse_tentatively (parser); - attrs = cp_parser_attributes_opt (parser); + attrs = cp_parser_attributes_opt (parser, /*member_p=*/false); if (attrs == NULL_TREE || cp_lexer_next_token_is_not (parser->lexer, CPP_SEMICOLON)) cp_parser_abort_tentative_parse (parser); @@ -8643,7 +8731,7 @@ cp_parser_condition (cp_parser* parser) /*parenthesized_p=*/NULL, /*member_p=*/false); /* Parse the attributes. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Parse the asm-specification. */ asm_specification = cp_parser_asm_specification_opt (parser); /* If the next token is not an `=' or '{', then we might still be @@ -9861,7 +9949,7 @@ cp_parser_decl_specifier_seq (cp_parser* parser, /* Parse the attributes. */ decl_specs->attributes = chainon (decl_specs->attributes, - cp_parser_attributes_opt (parser)); + cp_parser_attributes_opt (parser, /*member_p=*/false)); continue; } /* Assume we will find a decl-specifier keyword. */ @@ -10533,7 +10621,7 @@ cp_parser_conversion_type_id (cp_parser* parser) tree type_specified; /* Parse the attributes. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Parse the type-specifiers. */ cp_parser_type_specifier_seq (parser, /*is_declaration=*/false, /*is_trailing_return=*/false, @@ -13081,7 +13169,7 @@ cp_parser_elaborated_type_specifier (cp_parser* parser, cp_lexer_consume_token (parser->lexer); } /* Parse the attributes. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); } /* Or, it might be `typename'. */ else if (cp_lexer_next_token_is_keyword (parser->lexer, @@ -13099,7 +13187,7 @@ cp_parser_elaborated_type_specifier (cp_parser* parser, if (tag_type == none_type) return error_mark_node; /* Parse the attributes. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); } /* Look for the `::' operator. */ @@ -13437,7 +13525,7 @@ cp_parser_enum_specifier (cp_parser* parser) scoped_enum_p = true; } - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Clear the qualification. */ parser->scope = NULL_TREE; @@ -13669,7 +13757,8 @@ cp_parser_enum_specifier (cp_parser* parser) apply them if appropriate. */ if (cp_parser_allow_gnu_extensions_p (parser)) { - tree trailing_attr = cp_parser_attributes_opt (parser); + tree trailing_attr = cp_parser_attributes_opt (parser, + /*member_p=*/false); trailing_attr = chainon (trailing_attr, attributes); cplus_decl_attributes (&type, trailing_attr, @@ -13892,7 +13981,7 @@ cp_parser_namespace_definition (cp_parser* parser) identifier = NULL_TREE; /* Parse any specified attributes. */ - attribs = cp_parser_attributes_opt (parser); + attribs = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Look for the `{' to start the namespace. */ cp_parser_require (parser, CPP_OPEN_BRACE, RT_OPEN_BRACE); @@ -14168,7 +14257,7 @@ cp_parser_using_directive (cp_parser* parser) /* Get the namespace being used. */ namespace_decl = cp_parser_namespace_name (parser); /* And any specified attributes. */ - attribs = cp_parser_attributes_opt (parser); + attribs = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Update the symbol table. */ parse_using_directive (namespace_decl, attribs); /* Look for the final `;'. */ @@ -14503,9 +14592,18 @@ cp_parser_init_declarator (cp_parser* parser, /* Look for an asm-specification. */ asm_spec_start_token = cp_lexer_peek_token (parser->lexer); asm_specification = cp_parser_asm_specification_opt (parser); + /* Record the functino parameter list for later use when we parse the + attributes. */ + if (warn_thread_safety) + { + parser->current_func_declarator_params = + (declarator->kind == cdk_function + ? declarator->u.function.parameters : NULL_TREE); + parser->current_declarator_scope = scope; + } /* And attributes. */ attributes_start_token = cp_lexer_peek_token (parser->lexer); - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, member_p); } else { @@ -14540,21 +14638,28 @@ cp_parser_init_declarator (cp_parser* parser, "an asm-specification is not allowed " "on a function-definition"); if (attributes) - error_at (attributes_start_token->location, - "attributes are not allowed on a function-definition"); + { + /* We allow lock attributes to be applied to function + definitions. */ + attributes = extract_lock_attributes (attributes); + if (!attributes) + error_at (attributes_start_token->location, + "attributes are not allowed on a " + "function-definition"); + } /* This is a function-definition. */ *function_definition_p = true; /* Parse the function definition. */ if (member_p) - decl = cp_parser_save_member_function_body (parser, - decl_specifiers, - declarator, - prefix_attributes); + decl = (cp_parser_save_member_function_body ( + parser, decl_specifiers, declarator, + chainon (attributes, prefix_attributes))); else decl = (cp_parser_function_definition_from_specifiers_and_declarator - (parser, decl_specifiers, prefix_attributes, declarator)); + (parser, decl_specifiers, + chainon (attributes, prefix_attributes), declarator)); if (decl != error_mark_node && DECL_STRUCT_FUNCTION (decl)) { @@ -14740,7 +14845,7 @@ cp_parser_init_declarator (cp_parser* parser, attributes -- but ignores them. */ if (cp_parser_allow_gnu_extensions_p (parser) && initialization_kind == CPP_OPEN_PAREN) - if (cp_parser_attributes_opt (parser)) + if (cp_parser_attributes_opt (parser, member_p)) warning (OPT_Wattributes, "attributes after parenthesized initializer ignored"); @@ -14756,7 +14861,7 @@ cp_parser_init_declarator (cp_parser* parser, decl = grokfield (declarator, decl_specifiers, initializer, !is_non_constant_init, /*asmspec=*/NULL_TREE, - prefix_attributes); + chainon (prefix_attributes, attributes)); if (decl && TREE_CODE (decl) == FUNCTION_DECL) cp_parser_save_default_args (parser, decl); } @@ -14847,7 +14952,7 @@ cp_parser_declarator (cp_parser* parser, *ctor_dtor_or_conv_p = 0; if (cp_parser_allow_gnu_extensions_p (parser)) - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, member_p); /* Check for the ptr-operator production. */ cp_parser_parse_tentatively (parser); @@ -15747,7 +15852,7 @@ cp_parser_type_specifier_seq (cp_parser* parser, { type_specifier_seq->attributes = chainon (type_specifier_seq->attributes, - cp_parser_attributes_opt (parser)); + cp_parser_attributes_opt (parser, /*member_p=*/false)); continue; } @@ -16138,7 +16243,7 @@ cp_parser_parameter_declaration (cp_parser *parser, /* After the declarator, allow more attributes. */ decl_specifiers.attributes = chainon (decl_specifiers.attributes, - cp_parser_attributes_opt (parser)); + cp_parser_attributes_opt (parser, /*member_p=*/false)); } /* If the next token is an ellipsis, and we have not seen a @@ -16968,7 +17073,7 @@ cp_parser_class_specifier (cp_parser* parser) closing_brace = cp_parser_require (parser, CPP_CLOSE_BRACE, RT_CLOSE_BRACE); /* Look for trailing attributes to apply to this class. */ if (cp_parser_allow_gnu_extensions_p (parser)) - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); if (type != error_mark_node) type = finish_struct (type, attributes); if (nested_name_specifier_p) @@ -17095,6 +17200,11 @@ cp_parser_class_specifier (cp_parser* parser) unsigned ix; cp_default_arg_entry *e; + /* Process the thread safety attributes that were not processed when they + were parsed if thread safety analysis is enabled. */ + if (warn_thread_safety) + cp_parser_late_parsing_attribute_arg_lists (parser); + /* In a first pass, parse default arguments to the functions. Then, in a second pass, parse the bodies of the functions. This two-phased approach handles cases like: @@ -17209,7 +17319,7 @@ cp_parser_class_head (cp_parser* parser, return error_mark_node; /* Parse the attributes. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); /* If the next token is `::', that is invalid -- but sometimes people do try to write: @@ -17892,7 +18002,7 @@ cp_parser_member_declaration (cp_parser* parser) NULL); /* Look for attributes that apply to the bitfield. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/true); /* Remember which attributes are prefix attributes and which are not. */ first_attribute = attributes; @@ -17948,7 +18058,7 @@ cp_parser_member_declaration (cp_parser* parser) /* Look for an asm-specification. */ asm_specification = cp_parser_asm_specification_opt (parser); /* Look for attributes that apply to the declaration. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/true); /* Remember which attributes are prefix attributes and which are not. */ first_attribute = attributes; @@ -18882,7 +18992,7 @@ cp_parser_asm_label_list (cp_parser* parser) The return value is as for cp_parser_attribute_list. */ static tree -cp_parser_attributes_opt (cp_parser* parser) +cp_parser_attributes_opt (cp_parser* parser, bool member_p) { tree attributes = NULL_TREE; @@ -18907,7 +19017,7 @@ cp_parser_attributes_opt (cp_parser* parser) token = cp_lexer_peek_token (parser->lexer); if (token->type != CPP_CLOSE_PAREN) /* Parse the attribute-list. */ - attribute_list = cp_parser_attribute_list (parser); + attribute_list = cp_parser_attribute_list (parser, member_p); else /* If the next token is a `)', then there is no attribute list. */ @@ -18924,6 +19034,33 @@ cp_parser_attributes_opt (cp_parser* parser) return attributes; } +/* Save the tokens that make up the argument list of an attribute applied to + a class member. */ + +static tree +cp_parser_save_attribute_arg_list (cp_parser* parser) +{ + cp_token *first; + cp_token *last; + cp_token_cache *cache; + tree new_list_node; + + /* Skip the tokens that make up the argument list. */ + gcc_assert (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_PAREN)); + first = parser->lexer->next_token; + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); + last = parser->lexer->next_token; + + /* Save away the argument list tokens; we will process them when the + class is complete. */ + cache = cp_token_cache_new (first, last); + + /* Create and return a special tree list node with error_mark_node as + the TREE_PURPOSE and the saved token cache as the TREE_VALUE. */ + new_list_node = build_tree_list (error_mark_node, (tree) cache); + return new_list_node; +} + /* Parse an attribute-list. attribute-list: @@ -18942,7 +19079,7 @@ cp_parser_attributes_opt (cp_parser* parser) the arguments, if any. */ static tree -cp_parser_attribute_list (cp_parser* parser) +cp_parser_attribute_list (cp_parser* parser, bool member_p) { tree attribute_list = NULL_TREE; bool save_translate_strings_p = parser->translate_strings_p; @@ -18961,6 +19098,7 @@ cp_parser_attribute_list (cp_parser* parser) || token->type == CPP_KEYWORD) { tree arguments = NULL_TREE; + tree pushed_scope = NULL_TREE; /* Consume the token. */ token = cp_lexer_consume_token (parser->lexer); @@ -18980,20 +19118,50 @@ cp_parser_attribute_list (cp_parser* parser) /* If it's an `(', then parse the attribute arguments. */ if (token->type == CPP_OPEN_PAREN) { - VEC(tree,gc) *vec; - int attr_flag = (attribute_takes_identifier_p (identifier) - ? id_attr : normal_attr); - vec = cp_parser_parenthesized_expression_list - (parser, attr_flag, /*cast_p=*/false, - /*allow_expansion_p=*/false, - /*non_constant_p=*/NULL); - if (vec == NULL) - arguments = error_mark_node; - else - { - arguments = build_tree_list_vec (vec); - release_tree_vector (vec); - } + /* If this is a lock annotation attribute that takes arguments, + set a flag so that we can make the parser tolerant of lock + names not in scope or unsupported. Also if the decl is a + member function defined outside the class, we want to enter + the scope of the decl so that the access check (and name + lookup) will happen in the correct scope. */ + if (is_lock_attribute_with_args (identifier)) + { + parsing_lock_attribute = true; + if (parser->current_declarator_scope) + pushed_scope = + push_scope (parser->current_declarator_scope); + } + + if (member_p && parsing_lock_attribute) + { + /* If the attribute is applied to a class member, save the + tokens of the argument list and delay the parsing until + after the class is finished parsing. */ + tree new_list_node; + arguments = cp_parser_save_attribute_arg_list (parser); + new_list_node = build_tree_list (NULL_TREE, attribute); + TREE_CHAIN (new_list_node) = + parser->unparsed_attribute_args_queue; + parser->unparsed_attribute_args_queue = new_list_node; + } + else + { + VEC(tree,gc) *vec; + int attr_flag = (attribute_takes_identifier_p (identifier) + ? id_attr : normal_attr); + vec = cp_parser_parenthesized_expression_list + (parser, attr_flag, /*cast_p=*/false, + /*allow_expansion_p=*/false, + /*non_constant_p=*/NULL); + if (vec == NULL) + arguments = error_mark_node; + else + { + arguments = build_tree_list_vec (vec); + release_tree_vector (vec); + } + } + /* Save the arguments away. */ TREE_VALUE (attribute) = arguments; } @@ -19005,6 +19173,9 @@ cp_parser_attribute_list (cp_parser* parser) attribute_list = attribute; } + parsing_lock_attribute = false; + if (pushed_scope) + pop_scope (pushed_scope); token = cp_lexer_peek_token (parser->lexer); } /* Now, look for more attributes. If the next token isn't a @@ -19021,6 +19192,110 @@ cp_parser_attribute_list (cp_parser* parser) return nreverse (attribute_list); } +/* Parse the attribute arguments that have not yet been parsed. This + function is called after the class is finished parsing as the + arguments of lock attributes could reference other class members. + For example, in the following code, if we try to bind identifier "mu" + when we parse the attribute, we will not be able to find its DECL tree. + We have to wait until the whole class specification is parsed. + + class Foo { + private: + int a __attribute__ ((guarded_by(mu))); + Mutex mu; + }; */ + +static void +cp_parser_late_parsing_attribute_arg_lists (cp_parser* parser) +{ + tree list_node; + + parsing_lock_attribute = true; + + for (list_node = nreverse (parser->unparsed_attribute_args_queue); list_node; + list_node = TREE_CHAIN (list_node)) + { + tree attr = TREE_VALUE (list_node); + tree arguments = NULL_TREE; + tree artificial_node = TREE_VALUE (attr); + tree decl = TREE_PURPOSE (artificial_node); + cp_token_cache *tokens = (cp_token_cache *) TREE_VALUE (artificial_node); + tree ctype; + VEC(tree,gc) *vec; + + gcc_assert (tokens); + gcc_assert (decl && decl != error_mark_node); + + /* Push the tokens of the attribute arguments onto the lexer stack. */ + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Set up current_class_type, and enter the scope of the class. */ + ctype = DECL_CONTEXT (decl); + gcc_assert (ctype && TREE_CODE (ctype) == RECORD_TYPE); + push_nested_class (ctype); + + /* Record the function parameters for later use when parsing the lock + attributes. */ + parser->current_func_declarator_params = + (TREE_CODE (decl) == FUNCTION_DECL + ? DECL_ARGUMENTS (decl) : NULL_TREE); + + /* Parse the saved tokens. */ + vec = cp_parser_parenthesized_expression_list ( + parser, id_attr, /*cast_p=*/false, /*allow_expansion_p=*/false, + /*non_constant_p=*/NULL); + if (vec == NULL) + arguments = error_mark_node; + else + { + arguments = build_tree_list_vec (vec); + release_tree_vector (vec); + } + + /* Save the arguments away. */ + TREE_VALUE (attr) = arguments; + + /* Cut the TREE_CHAIN link of the attribute. Otherwise + cplus_decl_attributes will try to handle the next attribute + which might not have been parsed yet. */ + TREE_CHAIN (attr) = NULL_TREE; + + /* Apply the attributes to the decl. */ + cplus_decl_attributes (&decl, attr, 0); + + /* If decl has clones (when it is a ctor or a dtor), we need to + modify the clones' attributes as well. */ + if (TREE_CODE (decl) == FUNCTION_DECL + && (DECL_CONSTRUCTOR_P (decl) || DECL_DESTRUCTOR_P (decl))) + { + tree clone; + for (clone = TREE_CHAIN (decl); clone; clone = TREE_CHAIN (clone)) + { + if (DECL_CLONED_FUNCTION (clone) == decl) + DECL_ATTRIBUTES (clone) = DECL_ATTRIBUTES (decl); + } + } + + pop_nested_class (); + + cp_parser_pop_lexer (parser); + + /* Reset the PURPOSE and VALUE of the artificial tree list node that + holds the decl and the token cache. It is especially important to + null out the pointer to the token cache because, without doing so, + the garbage collector will complain about unrecognized tree node + when the artificial node is GC'ed. */ + TREE_PURPOSE (artificial_node) = NULL_TREE; + TREE_VALUE (artificial_node) = NULL_TREE; + } + + parsing_lock_attribute = false; + + /* Reset the unparsed_attribute_args_queue. The tree list nodes + should be garbage collected. */ + parser->unparsed_attribute_args_queue = NULL_TREE; +} + /* Parse an optional `__extension__' keyword. Returns TRUE if it is present, and FALSE otherwise. *SAVED_PEDANTIC is set to the current value of the PEDANTIC flag, regardless of whether or not @@ -19775,6 +20050,35 @@ cp_parser_function_definition_from_specifiers_and_declarator might be a friend. */ perform_deferred_access_checks (); + /* If the function definition is annotated with lock attributes with + arguments that are data members in the class, the parser would not be + able to bind those names to their FIELD_DECLs earlier when parsing the + attributes because the class context was not in scope at that time. + Now that we have entered the class context, try and bind and resolve + those identifiers. */ + if (attributes) + { + tree attr; + for (attr = attributes; attr; attr = TREE_CHAIN (attr)) + { + tree arg; + if (!is_lock_attribute_with_args (TREE_PURPOSE (attr))) + continue; + for (arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg)) + { + tree lock = TREE_VALUE (arg); + if (TREE_CODE (lock) == IDENTIFIER_NODE) + { + tree lock_decl = + cp_parser_lookup_name_simple (parser, lock, + input_location); + if (lock_decl && lock_decl != error_mark_node) + TREE_VALUE (arg) = lock_decl; + } + } + } + } + if (!success_p) { /* Skip the entire function. */ @@ -22088,7 +22392,7 @@ cp_parser_objc_method_keyword_params (cp_parser* parser, tree* attributes) type_name = cp_parser_objc_typename (parser); /* New ObjC allows attributes on parameters too. */ if (cp_lexer_next_token_is_keyword (parser->lexer, RID_ATTRIBUTE)) - parm_attr = cp_parser_attributes_opt (parser); + parm_attr = cp_parser_attributes_opt (parser, /*member_p=*/false); identifier = cp_parser_identifier (parser); params @@ -22110,7 +22414,7 @@ cp_parser_objc_method_keyword_params (cp_parser* parser, tree* attributes) /* We allow tail attributes for the method. */ if (token->keyword == RID_ATTRIBUTE) { - *attributes = cp_parser_attributes_opt (parser); + *attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); if (cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON) || cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) return params; @@ -22169,7 +22473,7 @@ cp_parser_objc_method_tail_params_opt (cp_parser* parser, bool *ellipsisp, { if (*attributes == NULL_TREE) { - *attributes = cp_parser_attributes_opt (parser); + *attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); if (cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON) || cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) return params; @@ -22177,7 +22481,7 @@ cp_parser_objc_method_tail_params_opt (cp_parser* parser, bool *ellipsisp, else /* We have an error, but parse the attributes, so that we can carry on. */ - *attributes = cp_parser_attributes_opt (parser); + *attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); cp_parser_error (parser, "method attributes must be specified at the end"); @@ -22257,7 +22561,7 @@ cp_parser_objc_method_maybe_bad_prefix_attributes (cp_parser* parser) { tree tattr; cp_lexer_save_tokens (parser->lexer); - tattr = cp_parser_attributes_opt (parser); + tattr = cp_parser_attributes_opt (parser, /*member_p=*/false); gcc_assert (tattr) ; /* If the attributes are followed by a method introducer, this is not allowed. @@ -22489,7 +22793,7 @@ cp_parser_objc_class_ivars (cp_parser* parser) } /* Look for attributes that apply to the ivar. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Remember which attributes are prefix attributes and which are not. */ first_attribute = attributes; @@ -22950,7 +23254,7 @@ static bool cp_parser_objc_valid_prefix_attributes (cp_parser* parser, tree *attrib) { cp_lexer_save_tokens (parser->lexer); - *attrib = cp_parser_attributes_opt (parser); + *attrib = cp_parser_attributes_opt (parser, /*member_p=*/false); gcc_assert (*attrib); if (OBJC_IS_AT_KEYWORD (cp_lexer_peek_token (parser->lexer)->keyword)) { @@ -23025,7 +23329,7 @@ cp_parser_objc_struct_declaration (cp_parser *parser) NULL, NULL, false); /* Look for attributes that apply to the ivar. */ - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, /*member_p=*/false); /* Remember which attributes are prefix attributes and which are not. */ first_attribute = attributes; @@ -24417,7 +24721,8 @@ cp_parser_omp_for_loop (cp_parser *parser, tree clauses, tree *par_clauses) /*ctor_dtor_or_conv_p=*/NULL, /*parenthesized_p=*/NULL, /*member_p=*/false); - attributes = cp_parser_attributes_opt (parser); + attributes = cp_parser_attributes_opt (parser, + /*member_p=*/false); asm_specification = cp_parser_asm_specification_opt (parser); if (declarator == cp_error_declarator) diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -45,6 +45,7 @@ along with GCC; see the file COPYING3. If not see #include "timevar.h" #include "tree-iterator.h" #include "vecprim.h" +#include "tree-threadsafe-analyze.h" /* The type of functions taking a tree, and some additional data, and returning an int. */ @@ -61,6 +62,37 @@ struct GTY ((chain_next ("%h.next"))) pending_template { static GTY(()) struct pending_template *pending_templates; static GTY(()) struct pending_template *last_pending_template; +/* The PENDING_ATTRIBUTE is a list node that records an attribute whose + instantiation has been deferred until the whole class has been + instantiated. This deferral currently only happens to the lock attributes + whose arguments could be data members declared later in the class + specification, as shown in the following example: + + template <typename T> + class Bar { + T shared_var GUARDED_BY(lock); + Mutex lock; + }; */ +struct GTY(()) pending_attribute { + tree decl; + tree attributes; + int attr_flags; + tree args; + tsubst_flags_t complain; + tree in_decl; + struct pending_attribute *next; +}; + +static GTY(()) struct pending_attribute *pending_lock_attributes = NULL; + +typedef struct pending_attribute *pending_attribute_p; +DEF_VEC_P(pending_attribute_p); +DEF_VEC_ALLOC_P(pending_attribute_p,gc); + +static GTY(()) VEC(pending_attribute_p,gc) *pending_lock_attr_stack; + +static tree func_decl_params; + int processing_template_parmlist; static int template_header_count; @@ -3041,6 +3073,16 @@ find_parameter_packs_r (tree *tp, int *walk_subtrees, void* data) *walk_subtrees = 0; return NULL_TREE; + /* If T is a tree list node whose purpose field is an error_mark_node, + T actually contains the tokens for an lock attribute argument list in + its value field. (See cp_parser_save_attribute_arg_list in cp/parser.c.) + In that case, don't try to walk the tree value field as it is not a + valid tree node. */ + case TREE_LIST: + if (TREE_PURPOSE (t) == error_mark_node) + *walk_subtrees = 0; + return NULL_TREE; + default: return NULL_TREE; } @@ -8024,6 +8066,24 @@ apply_late_template_attributes (tree *decl_p, tree attributes, int attr_flags, { *p = TREE_CHAIN (t); TREE_CHAIN (t) = NULL_TREE; + /* If this is a lock attribute with arguments, we defer the + instantiation until the whole class specification has been + instantiated because the lock attribute arguments could + reference data members declared later (lexically). */ + if (TREE_VALUE (t) + && is_lock_attribute_with_args (TREE_PURPOSE (t))) + { + struct pending_attribute *pa = ggc_alloc_pending_attribute(); + pa->decl = *decl_p; + pa->attributes = t; + pa->attr_flags = attr_flags; + pa->args = args; + pa->complain = complain; + pa->in_decl = in_decl; + pa->next = pending_lock_attributes; + pending_lock_attributes = pa; + continue; + } /* If the first attribute argument is an identifier, don't pass it through tsubst. Attributes like mode, format, cleanup and several target specific attributes expect it @@ -8101,6 +8161,22 @@ perform_typedefs_access_check (tree tmpl, tree targs) input_location = saved_location; } +/* Reverse the order of elements in the pending attribute list, PA_LIST, + and return the new head of the list (old last element). */ + +static struct pending_attribute * +pa_reverse (struct pending_attribute *pa_list) +{ + struct pending_attribute *prev = NULL, *curr, *next; + for (curr = pa_list; curr; curr = next) + { + next = curr->next; + curr->next = prev; + prev = curr; + } + return prev; +} + tree instantiate_class_template (tree type) { @@ -8172,6 +8248,11 @@ instantiate_class_template (tree type) saved_maximum_field_alignment = maximum_field_alignment; maximum_field_alignment = TYPE_PRECISION (pattern); + /* Push the existing pending lock attributes to the stack. */ + VEC_safe_push (pending_attribute_p, gc, pending_lock_attr_stack, + pending_lock_attributes); + pending_lock_attributes = NULL; + SET_CLASSTYPE_INTERFACE_UNKNOWN (type); /* Set the input location to the most specialized template definition. @@ -8574,6 +8655,34 @@ instantiate_class_template (tree type) definitions or default arguments, of the class member functions, member classes, static data members and member templates.... */ + /* Instantiate the deferred lock attributes and apply them to the + corresponding decls. We don't do this earlier because the lock + attribute arguments may reference data members of the class. */ + if (pending_lock_attributes) + { + struct pending_attribute *pa = pa_reverse (pending_lock_attributes); + location_t saved_location = input_location; + parsing_lock_attribute = true; + for ( ; pa; pa = pa->next) + { + tree t = pa->attributes; + input_location = DECL_SOURCE_LOCATION (pa->decl); + func_decl_params = (TREE_CODE (pa->decl) == FUNCTION_DECL + ? DECL_ARGUMENTS (pa->decl) : NULL_TREE); + TREE_VALUE (t) + = tsubst_expr (TREE_VALUE (t), pa->args, pa->complain, + pa->in_decl, + /*integral_constant_expression_p=*/false); + cplus_decl_attributes (&pa->decl, t, pa->attr_flags); + } + parsing_lock_attribute = false; + input_location = saved_location; + } + + /* Pop out the pending attributes of the outer class/template. */ + pending_lock_attributes = VEC_pop (pending_attribute_p, + pending_lock_attr_stack); + /* Some typedefs referenced from within the template code need to be access checked at template instantiation time, i.e now. These types were added to the template at parsing time. Let's get those and perform @@ -11291,8 +11400,10 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl) tree c; /* This can happen for a parameter name used later in a function declaration (such as in a late-specified return type). Just - make a dummy decl, since it's only used for its type. */ - gcc_assert (cp_unevaluated_operand != 0); + make a dummy decl, since it's only used for its type. + Note that we can also reach here when processing lock attributes + whose arguments are function parameters. */ + gcc_assert (cp_unevaluated_operand != 0 || pending_lock_attributes); /* We copy T because want to tsubst the PARM_DECL only, not the following PARM_DECLs that are chained to T. */ c = copy_node (t); @@ -11362,6 +11473,10 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl) tree r = lookup_field (ctx, DECL_NAME (t), 0, false); if (!r) { + /* Suppress the error message if we are processing a lock + attribute. */ + if (parsing_lock_attribute) + return t; if (complain & tf_error) error ("using invalid field %qD", t); return error_mark_node; @@ -12527,7 +12642,14 @@ tsubst_copy_and_build (tree t, if (error_msg) error (error_msg); if (!function_p && TREE_CODE (decl) == IDENTIFIER_NODE) - decl = unqualified_name_lookup_error (decl); + { + /* If we are parsing a lock attribute of a function decl, try to + see if the identifier is a function parameter first. */ + if (parsing_lock_attribute && func_decl_params) + decl = lookup_name_in_func_params (func_decl_params, decl); + else + decl = unqualified_name_lookup_error (decl); + } return decl; } diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -45,6 +45,7 @@ along with GCC; see the file COPYING3. If not see #include "target.h" #include "gimple.h" #include "bitmap.h" +#include "tree-threadsafe-analyze.h" /* There routines provide a modular interface to perform many parsing operations. They may therefore be used during actual parsing, or @@ -1545,7 +1546,20 @@ finish_non_static_data_member (tree decl, tree object, tree qualifying_scope) && DECL_STATIC_FUNCTION_P (current_function_decl)) error ("invalid use of member %q+D in static member function", decl); else - error ("invalid use of non-static data member %q+D", decl); + { + /* Suppress the error message and return the decl node if we are + parsing a lock attribute. We would like the users to be able to + reference other members of the class in the lock attributes as + shown in the following example: + + class Bar { + Foo *foo; + int data __attribute__((guarded_by(foo->lock))); + }; */ + if (parsing_lock_attribute) + return decl; + error ("invalid use of non-static data member %q+D", decl); + } error ("from this location"); return error_mark_node; diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -41,6 +41,7 @@ along with GCC; see the file COPYING3. If not see #include "c-family/c-common.h" #include "c-family/c-objc.h" #include "params.h" +#include "tree-threadsafe-analyze.h" static tree pfn_from_ptrmemfunc (tree); static tree delta_from_ptrmemfunc (tree); @@ -2488,7 +2489,11 @@ finish_class_member_access_expr (tree object, tree name, bool template_p, return error_mark_node; if (!CLASS_TYPE_P (object_type)) { - if (complain & tf_error) + /* Suppress the error message and return an error_mark_node if we are + parsing a lock attribute. We would like the lock attributes to + reference (and tolerate) unkown names so that they provide better + code documentation capability. */ + if (complain & tf_error && !parsing_lock_attribute) error ("request for member %qD in %qE, which is of non-class type %qT", name, object, object_type); return error_mark_node; @@ -8234,4 +8239,3 @@ lvalue_or_else (tree ref, enum lvalue_use use, tsubst_flags_t complain) } return 1; } - diff --git a/gcc/cp/typeck2.c b/gcc/cp/typeck2.c --- a/gcc/cp/typeck2.c +++ b/gcc/cp/typeck2.c @@ -37,6 +37,7 @@ along with GCC; see the file COPYING3. If not see #include "flags.h" #include "output.h" #include "diagnostic-core.h" +#include "tree-threadsafe-analyze.h" static tree process_init_constructor (tree type, tree init); @@ -373,6 +374,15 @@ cxx_incomplete_type_diagnostic (const_tree value, const_tree type, if (TREE_CODE (type) == ERROR_MARK) return; + /* Suppress the error message and return an error_mark_node if we are + parsing a lock attribute. Users of the lock attributes could possibly + cross reference the locks between two classes, and the one that declares + later would get an incomplete type error. We would like the lock + attributes to tolerate such cases so that they provide better code + documentation capability. */ + if (parsing_lock_attribute) + return; + if (value != 0 && (TREE_CODE (value) == VAR_DECL || TREE_CODE (value) == PARM_DECL || TREE_CODE (value) == FIELD_DECL)) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -264,7 +264,11 @@ Objective-C and Objective-C++ Dialects}. -Wstrict-overflow -Wstrict-overflow=@var{n} @gol -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{]} @gol -Wswitch -Wswitch-default -Wswitch-enum -Wsync-nand @gol --Wsystem-headers -Wtrampolines -Wtrigraphs -Wtype-limits -Wundef @gol +-Wsystem-headers -Wthread-safety -Wthread-unguarded-var @gol +-Wthread-unguarded-func -Wthread-mismatched-lock-order @gol +-Wthread-mismatched-lock-acq-rel -Wthread-reentrant-lock @gol +-Wthread-unsupported-lock-name -Wthread-attr-bind-param @gol +-Wtrampolines -Wtrigraphs -Wtype-limits -Wundef @gol -Wuninitialized -Wunknown-pragmas -Wno-pragmas @gol -Wunsuffixed-float-constants -Wunused -Wunused-function @gol -Wunused-label -Wunused-parameter -Wno-unused-result -Wunused-value @gol @@ -3425,6 +3429,58 @@ warning about an omitted enumeration code even if there is a Warn when @code{__sync_fetch_and_nand} and @code{__sync_nand_and_fetch} built-in functions are used. These functions changed semantics in GCC 4.4. +@item -Wthread-safety +@opindex Wthread-safety +@opindex Wno-thread-safety +Warn about potential thread safety issues when the code is annotated with +thread safety attributes. + +@item Wthread-unguarded-var +@opindex -Wthread-unguarded-var +@opindex -Wno-thread-unguarded-var +Warn about shared variables not properly protected by locks specified in the +attributes. This flag is effective only with @option{-Wthread-safety} and +enabled by default. + +@item Wthread-unguarded-func +@opindex -Wthread-unguarded-func +@opindex -Wno-thread-unguarded-func +Warn about function calls not properly protected by locks specified in the +attributes. This flag is effective only with @option{-Wthread-safety} and +enabled by default. + +@item Wthread-mismatched-lock-order +@opindex -Wthread-mismatched-lock-order +@opindex -Wno-thread-mismatched-lock-order +Warn about lock acquisition order inconsistent with what specified in the +attributes. This flag is effective only with @option{-Wthread-safety} and +enabled by default. + +@item Wthread-mismatched-lock-acq-rel +@opindex -Wthread-mismatched-lock-acq-rel +@opindex -Wno-thread-mismatched-lock-acq-rel +Warn about mismatched lock acquisition and release. This flag is effective only +with @option{-Wthread-safety} and enabled by default. + +@item Wthread-reentrant-lock +@opindex -Wthread-reentrant-lock +@opindex -Wno-thread-reentrant-lock +Warn about a lock being acquired recursively. This flag is effective only +with @option{-Wthread-safety} and enabled by default. + +@item Wthread-unsupported-lock-name +@opindex -Wthread-unsupported-lock-name +@opindex -Wno-thread-unsupported-lock-name +Warn about uses of unsupported lock names in attributes. This flag is effective +only with @option{-Wthread-safety} and disabled by default. + +@item Wthread-attr-bind-param +@opindex -Wthread-attr-bind-param +@opindex -Wno-thread-attr-bind-param +Make the thread safety analysis try to bind the function parameters used in +the attributes. This flag is effective only with @option{-Wthread-safety} +and enabled by default. + @item -Wtrigraphs @opindex Wtrigraphs @opindex Wno-trigraphs diff --git a/gcc/gimplify.c b/gcc/gimplify.c --- a/gcc/gimplify.c +++ b/gcc/gimplify.c @@ -502,6 +502,7 @@ static tree lookup_tmp_var (tree val, bool is_formal) { tree ret; + bool tmp_reused = false; /* If not optimizing, never really reuse a temporary. local-alloc won't allocate any variable that is used in more than one basic @@ -531,9 +532,52 @@ lookup_tmp_var (tree val, bool is_formal) { elt_p = (elt_t *) *slot; ret = elt_p->temp; + tmp_reused = true; } } + /* If the original val is a pointer (to a shared memory location) with + either the "point_to_guarded_by" or "point_to_guarded" attribute, + we need to copy the attribute to the tmp variable. Note that we don't + need to do this for non-pointer variables because, after transformation, + the original variable would still be accessed the same way it was + accessed in the original code as shown in the following example: + + Original code: Transformed code: + + gx = gy + 1; // read gy, write gx gy.0 = gy; // read gy + gx.1 = gy.0 + 1; + gx = gx.1; // write gx + + On the other hand, if the original variable is a pointer, it + will not be accessed the same way as before: + + Original code: Transformed code: + + *gx = a + 5; // gx is read and gx.1 = gx; // gx is read but + // dereferenced // not deferenced + *gx.1 = a + 5; + */ + if (!tmp_reused && DECL_P (val) && POINTER_TYPE_P (TREE_TYPE (val))) + { + tree attr = lookup_attribute ("point_to_guarded_by", + DECL_ATTRIBUTES (val)); + if (!attr) + attr = lookup_attribute ("point_to_guarded", DECL_ATTRIBUTES (val)); + + if (attr) + { + DECL_ATTRIBUTES (ret) = build_tree_list (TREE_PURPOSE (attr), + TREE_VALUE (attr)); + /* When detecting an issue later in the thread safety analysis + phase, we would like to print out the original variable name + (instead of the tmp variable name). So stick the original + variable's decl in the debug expr. */ + DECL_DEBUG_EXPR_IS_FROM (ret) = 1; + SET_DECL_DEBUG_EXPR (ret, val); + } + } + return ret; } diff --git a/gcc/langhooks-def.h b/gcc/langhooks-def.h --- a/gcc/langhooks-def.h +++ b/gcc/langhooks-def.h @@ -38,6 +38,9 @@ struct diagnostic_info; extern void lhd_do_nothing (void); extern void lhd_do_nothing_t (tree); extern void lhd_do_nothing_f (struct function *); +extern int lhd_do_nothing_t_return_int (tree); +extern bool lhd_do_nothing_t_return_bool (tree); +extern tree lhd_do_nothing_t_t_return_null_tree (tree, tree); extern tree lhd_pass_through_t (tree); extern bool lhd_post_options (const char **); extern alias_set_type lhd_get_alias_set (tree); @@ -141,6 +144,15 @@ extern void lhd_omp_firstprivatize_type_sizes (struct gimplify_omp_ctx *, /* Hooks for tree gimplification. */ #define LANG_HOOKS_GIMPLIFY_EXPR lhd_gimplify_expr +/* Hook for getting the function decl from an obj_type_ref. */ +#define LANG_HOOKS_GET_VIRTUAL_FUNCTION_DECL lhd_do_nothing_t_t_return_null_tree + +/* Hooks for thread safety analysis. */ +#define LANG_HOOKS_DECL_IS_BASE_FIELD lhd_do_nothing_t_return_bool +#define LANG_HOOKS_DECL_IS_CONSTRUCTOR lhd_do_nothing_t_return_bool +#define LANG_HOOKS_DECL_IS_DESTRUCTOR lhd_do_nothing_t_return_bool +#define LANG_HOOKS_DECL_IS_CONST_MEMBER_FUNC lhd_do_nothing_t_return_int + /* Tree dump hooks. */ extern bool lhd_tree_dump_dump_tree (void *, tree); extern int lhd_tree_dump_type_quals (const_tree); @@ -299,6 +311,11 @@ extern void lhd_end_section (void); LANG_HOOKS_GET_INNERMOST_GENERIC_ARGS, \ LANG_HOOKS_FUNCTION_PARAMETER_PACK_P, \ LANG_HOOKS_GIMPLIFY_EXPR, \ + LANG_HOOKS_GET_VIRTUAL_FUNCTION_DECL, \ + LANG_HOOKS_DECL_IS_BASE_FIELD, \ + LANG_HOOKS_DECL_IS_CONSTRUCTOR, \ + LANG_HOOKS_DECL_IS_DESTRUCTOR, \ + LANG_HOOKS_DECL_IS_CONST_MEMBER_FUNC, \ LANG_HOOKS_BUILTIN_FUNCTION, \ LANG_HOOKS_BUILTIN_FUNCTION_EXT_SCOPE, \ LANG_HOOKS_INIT_TS, \ diff --git a/gcc/langhooks.c b/gcc/langhooks.c --- a/gcc/langhooks.c +++ b/gcc/langhooks.c @@ -79,6 +79,27 @@ lhd_do_nothing_f (struct function * ARG_UNUSED (f)) { } +/* Do nothing (tree). Return int. */ +int +lhd_do_nothing_t_return_int (tree ARG_UNUSED (t)) +{ + return 0; +} + +/* Do nothing (tree). Return false. */ +bool +lhd_do_nothing_t_return_bool (tree ARG_UNUSED (t)) +{ + return 0; +} + +/* Do nothing (tree, tree). Return NULL_TREE. */ +tree +lhd_do_nothing_t_t_return_null_tree (tree ARG_UNUSED (t), tree ARG_UNUSED (t2)) +{ + return NULL_TREE; +} + /* Do nothing (return NULL_TREE). */ tree diff --git a/gcc/langhooks.h b/gcc/langhooks.h --- a/gcc/langhooks.h +++ b/gcc/langhooks.h @@ -426,6 +426,26 @@ struct lang_hooks enum gimplify_status, though we can't see that type here. */ int (*gimplify_expr) (tree *, gimple_seq *, gimple_seq *); + /* Return the virtual function decl for the given OBJ_TYPE_REF expression. */ + tree (*get_virtual_function_decl) (tree, tree); + + /* Determine whether the given DECL is a compiler-generated base field + in a derived class. */ + bool (*decl_is_base_field) (tree); + + /* Return true if DECL is a constructor. */ + bool (*decl_is_constructor) (tree); + + /* Return true if DECL is a destructor. */ + bool (*decl_is_destructor) (tree); + + /* Return + 1 if decl is a const member function, + 2 if decl is not a const member function but has a const overload that + has identical parameter list, + 0 otherwise. */ + int (*decl_is_const_member_func) (tree); + /* Do language specific processing in the builtin function DECL */ tree (*builtin_function) (tree decl); diff --git a/gcc/passes.c b/gcc/passes.c --- a/gcc/passes.c +++ b/gcc/passes.c @@ -747,6 +747,7 @@ init_optimization_passes (void) NEXT_PASS (pass_lower_vector); NEXT_PASS (pass_early_warn_uninitialized); NEXT_PASS (pass_rebuild_cgraph_edges); + NEXT_PASS (pass_threadsafe_analyze); NEXT_PASS (pass_inline_parameters); NEXT_PASS (pass_early_inline); NEXT_PASS (pass_all_early_optimizations); diff --git a/gcc/pointer-set.c b/gcc/pointer-set.c --- a/gcc/pointer-set.c +++ b/gcc/pointer-set.c @@ -89,6 +89,23 @@ pointer_set_destroy (struct pointer_set_t *pset) XDELETE (pset); } +/* Create a copy of PSET. */ +struct pointer_set_t * +pointer_set_copy (const struct pointer_set_t *pset) +{ + struct pointer_set_t *result = XNEW (struct pointer_set_t); + + result->n_elements = pset->n_elements; + result->log_slots = pset->log_slots; + result->n_slots = pset->n_slots; + result->slots = XCNEWVEC (const void *, result->n_slots); + + /* Now copy the members of the set */ + memcpy(result->slots, pset->slots, pset->n_slots * sizeof(void *)); + + return result; +} + /* Returns nonzero if PSET contains P. P must be nonnull. Collisions are resolved by linear probing. */ @@ -169,6 +186,34 @@ pointer_set_insert (struct pointer_set_t *pset, const void *p) return 0; } +/* Delete P from PSET if PSET contains P. Returns nonzero if P is in the set. + P must be nonnull. */ +int +pointer_set_delete (struct pointer_set_t *pset, const void *p) +{ + size_t n = hash1 (p, pset->n_slots, pset->log_slots); + + while (true) + { + if (pset->slots[n] == p) + { + pset->slots[n] = 0; + --pset->n_elements; + return 1; + } + else if (pset->slots[n] == 0) + { + return 0; + } + else + { + ++n; + if (n == pset->n_slots) + n = 0; + } + } +} + /* Pass each pointer in PSET to the function in FN, together with the fixed parameter DATA. If FN returns false, the iteration stops. */ @@ -181,6 +226,66 @@ void pointer_set_traverse (const struct pointer_set_t *pset, break; } +/* Intersect DST_PSET with SRC_PSET and return the intersection set in + DST_PSET, i.e., + + DST_PSET = DST_PSET ^ SRC_PSET + + Also, if CMPL_PSET is not NULL, calculate the complement set that + contains elements not in the intersection, i.e., + + CMPL_PSET = (DST_PSET U SRC_PSET) - (DST_PSET ^ SRC_PSET) */ +void +pointer_set_intersection_complement(struct pointer_set_t *dst_pset, + const struct pointer_set_t *src_pset, + struct pointer_set_t *cmpl_pset) +{ + size_t i; + for (i = 0; i < dst_pset->n_slots; ++i) + { + const void *p = dst_pset->slots[i]; + if (p && !pointer_set_contains(src_pset, p)) + { + dst_pset->slots[i] = 0; + --dst_pset->n_elements; + if (cmpl_pset) + pointer_set_insert (cmpl_pset, p); + } + } + + if (cmpl_pset) + { + for (i = 0; i < src_pset->n_slots; ++i) + { + const void *p = src_pset->slots[i]; + if (p && !pointer_set_contains(dst_pset, p)) + pointer_set_insert (cmpl_pset, p); + } + } +} + +/* Take the union of DST_PSET and SRC_PSET and return the union in DST_PSET. */ +void +pointer_set_union_inplace (struct pointer_set_t *dst_pset, + const struct pointer_set_t *src_pset) +{ + size_t i; + for (i = 0; i < src_pset->n_slots; ++i) + { + const void *p = src_pset->slots[i]; + if (p) + pointer_set_insert (dst_pset, p); + } +} + +/* Return the number of elements in PSET. */ +size_t +pointer_set_cardinality (const struct pointer_set_t *pset) +{ + return pset->n_elements; +} + + /* A pointer map is represented the same way as a pointer_set, so the hash code is based on the address of the key, rather than diff --git a/gcc/pointer-set.h b/gcc/pointer-set.h --- a/gcc/pointer-set.h +++ b/gcc/pointer-set.h @@ -23,13 +23,24 @@ along with GCC; see the file COPYING3. If not see struct pointer_set_t; struct pointer_set_t *pointer_set_create (void); void pointer_set_destroy (struct pointer_set_t *pset); +struct pointer_set_t *pointer_set_copy (const struct pointer_set_t *pset); int pointer_set_contains (const struct pointer_set_t *pset, const void *p); int pointer_set_insert (struct pointer_set_t *pset, const void *p); +int pointer_set_delete (struct pointer_set_t *pset, const void *p); void pointer_set_traverse (const struct pointer_set_t *, bool (*) (const void *, void *), void *); +void pointer_set_intersection_complement (struct pointer_set_t *dst_pset, + const struct pointer_set_t *src_pset, + struct pointer_set_t *comp_pset); + +void pointer_set_union_inplace (struct pointer_set_t *dst_pset, + const struct pointer_set_t *src_pset); + +size_t pointer_set_cardinality (const struct pointer_set_t *pset); + struct pointer_map_t; struct pointer_map_t *pointer_map_create (void); void pointer_map_destroy (struct pointer_map_t *pmap); diff --git a/gcc/testsuite/g++.dg/README b/gcc/testsuite/g++.dg/README --- a/gcc/testsuite/g++.dg/README +++ b/gcc/testsuite/g++.dg/README @@ -23,6 +23,7 @@ plugin Tests for plugin support. rtti Tests for run-time type identification (typeid, dynamic_cast, etc.) template Tests for templates. tc1 Tests for Technical Corrigendum 1 conformance. +thread-ann Tests for thread safety annotations and analysis. tls Tests for support of thread-local data. tree-ssa Tests for Tree SSA optimizations. warn Tests for compiler warnings. diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_common.h b/gcc/testsuite/g++.dg/thread-ann/thread_annot_common.h new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_common.h @@ -0,0 +1,131 @@ +#ifndef THREAD_ANNOT_COMMON_H +#define THREAD_ANNOT_COMMON_H + +#if defined(__GNUC__) && defined(__SUPPORT_TS_ANNOTATION__) + +#define LOCKABLE __attribute__ ((lockable)) +#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable)) +#define GUARDED_BY(x) __attribute__ ((guarded_by(x))) +#define GUARDED_VAR __attribute__ ((guarded)) +#define PT_GUARDED_BY(x) __attribute__ ((point_to_guarded_by(x))) +#define PT_GUARDED_VAR __attribute__ ((point_to_guarded)) +#define ACQUIRED_AFTER(...) __attribute__ ((acquired_after(__VA_ARGS__))) +#define ACQUIRED_BEFORE(...) __attribute__ ((acquired_before(__VA_ARGS__))) +#define EXCLUSIVE_LOCK_FUNCTION(...) \ + __attribute__ ((exclusive_lock(__VA_ARGS__))) +#define SHARED_LOCK_FUNCTION(...) \ + __attribute__ ((shared_lock(__VA_ARGS__))) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \ + __attribute__ ((exclusive_trylock(__VA_ARGS__))) +#define SHARED_TRYLOCK_FUNCTION(...) \ + __attribute__ ((shared_trylock(__VA_ARGS__))) +#define UNLOCK_FUNCTION(...) __attribute__ ((unlock(__VA_ARGS__))) +#define LOCK_RETURNED(x) __attribute__ ((lock_returned(x))) +#define LOCKS_EXCLUDED(...) __attribute__ ((locks_excluded(__VA_ARGS__))) +#define EXCLUSIVE_LOCKS_REQUIRED(...) \ + __attribute__ ((exclusive_locks_required(__VA_ARGS__))) +#define SHARED_LOCKS_REQUIRED(...) \ + __attribute__ ((shared_locks_required(__VA_ARGS__))) +#define NO_THREAD_SAFETY_ANALYSIS __attribute__ ((no_thread_safety_analysis)) +#define IGNORE_READS_BEGIN __attribute__ ((ignore_reads_begin)) +#define IGNORE_READS_END __attribute__ ((ignore_reads_end)) +#define IGNORE_WRITES_BEGIN __attribute__ ((ignore_writes_begin)) +#define IGNORE_WRITES_END __attribute__ ((ignore_writes_end)) +#define UNPROTECTED_READ __attribute__ ((unprotected_read)) + +#else + +#define LOCKABLE +#define SCOPED_LOCKABLE +#define GUARDED_BY(x) +#define GUARDED_VAR +#define PT_GUARDED_BY(x) +#define PT_GUARDED_VAR +#define ACQUIRED_AFTER(...) +#define ACQUIRED_BEFORE(...) +#define EXCLUSIVE_LOCK_FUNCTION(...) +#define SHARED_LOCK_FUNCTION(...) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) +#define SHARED_TRYLOCK_FUNCTION(...) +#define UNLOCK_FUNCTION(...) +#define LOCK_RETURNED(x) +#define LOCKS_EXCLUDED(...) +#define EXCLUSIVE_LOCKS_REQUIRED(...) +#define SHARED_LOCKS_REQUIRED(...) +#define NO_THREAD_SAFETY_ANALYSIS +#define IGNORE_READS_BEGIN +#define IGNORE_READS_END +#define IGNORE_WRITES_BEGIN +#define IGNORE_WRITES_END +#define UNPROTECTED_READ + +#endif // defined(__GNUC__) && defined(__SUPPORT_TS_ANNOTATION__) + + +class LOCKABLE Mutex { + public: + void Lock() EXCLUSIVE_LOCK_FUNCTION(); + void ReaderLock() SHARED_LOCK_FUNCTION(); + void Unlock() UNLOCK_FUNCTION(); + bool TryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true); + bool ReaderTryLock() SHARED_TRYLOCK_FUNCTION(true); + void LockWhen(const int &cond) EXCLUSIVE_LOCK_FUNCTION(); +}; + +class SCOPED_LOCKABLE MutexLock { + public: + MutexLock(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu); + ~MutexLock() UNLOCK_FUNCTION(); +}; + +class SCOPED_LOCKABLE ReaderMutexLock { + public: + ReaderMutexLock(Mutex *mu) SHARED_LOCK_FUNCTION(mu); + ~ReaderMutexLock() UNLOCK_FUNCTION(); +}; + +class SCOPED_LOCKABLE ReleasableMutexLock { + public: + explicit ReleasableMutexLock(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu); + ~ReleasableMutexLock() UNLOCK_FUNCTION(); + void Release() UNLOCK_FUNCTION(); +}; + +void AnnotateIgnoreReadsBegin(const char *file, int line) IGNORE_READS_BEGIN; +void AnnotateIgnoreReadsEnd(const char *file, int line) IGNORE_READS_END; +void AnnotateIgnoreWritesBegin(const char *file, int line) IGNORE_WRITES_BEGIN; +void AnnotateIgnoreWritesEnd(const char *file, int line) IGNORE_WRITES_END; + +#define ANNOTATE_IGNORE_READS_BEGIN() \ + AnnotateIgnoreReadsBegin(__FILE__, __LINE__) + +#define ANNOTATE_IGNORE_READS_END() \ + AnnotateIgnoreReadsEnd(__FILE__, __LINE__) + +#define ANNOTATE_IGNORE_WRITES_BEGIN() \ + AnnotateIgnoreWritesBegin(__FILE__, __LINE__) + +#define ANNOTATE_IGNORE_WRITES_END() \ + AnnotateIgnoreWritesEnd(__FILE__, __LINE__) + +#define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() \ + do { \ + ANNOTATE_IGNORE_READS_BEGIN(); \ + ANNOTATE_IGNORE_WRITES_BEGIN(); \ + }while(0) \ + +#define ANNOTATE_IGNORE_READS_AND_WRITES_END() \ + do { \ + ANNOTATE_IGNORE_WRITES_END(); \ + ANNOTATE_IGNORE_READS_END(); \ + }while(0) \ + +template <class T> +inline T ANNOTATE_UNPROTECTED_READ(const T &x) UNPROTECTED_READ { + ANNOTATE_IGNORE_READS_BEGIN(); + T res = x; + ANNOTATE_IGNORE_READS_END(); + return res; +} + +#endif // THREAD_ANNOT_COMMON_H diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-1.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-1.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-1.C @@ -0,0 +1,75 @@ +// Test guarded_by/guarded/pt_guarded_by annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +Mutex mu2 ACQUIRED_AFTER(mu1); +Mutex mu3 ACQUIRED_AFTER(mu2); + +int gx GUARDED_BY(mu1) = 3; +float gy GUARDED_BY(mu2) = 5.0; +int *gp GUARDED_BY(mu3) PT_GUARDED_BY(mu1); +int gw GUARDED_VAR = 4; + +class Foo { + private: + Mutex mu_ ACQUIRED_AFTER(mu2); + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + + Foo() : a_(1) { b_ = 5.0; } + + ~Foo() { a_ = 0; b_ = 0.0; } + + void incrementA(int i) + { + a_ += i; // { dg-warning "Reading variable" } + } + + float decrementB(float f) LOCKS_EXCLUDED(mu_) SHARED_LOCKS_REQUIRED(mu1) + { + float res; + mu_.Lock(); + if (gx > 2) + b_ -= f; + else + b_ -= 2 * f; + res = b_; + mu_.Unlock(); + return res; + } +}; + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY(mu2) = &gy; + Foo foo; + + gp = &gx; // { dg-warning "Writing to variable 'gp' requires lock 'mu3'" } + + if (gx > 3) { // { dg-warning "Reading variable 'gx' requires lock 'mu1'" } + la = gx + gw; // { dg-warning "Reading variable 'gw' requires a lock" } + } + else { + *p = foo.decrementB(gy); // { dg-warning "Reading variable 'gy' requires lock 'mu2'" } + } + + foo.incrementA(la); + + if (la < 10) { + *gp = 7; // { dg-warning "Reading variable 'gp' requires lock 'mu3'" } + } +} + +// { dg-warning "Writing to variable" "" { target *-*-* } 30 } +// { dg-warning "Reading variable 'gx' requires lock 'mu1'" "" { target *-*-* } 58 } +// { dg-warning "Calling function 'decrementB' requires lock 'mu1'" "" { target *-*-* } 61 } +// { dg-warning "Access to memory location pointed to by variable 'p' requires lock 'mu2'" "" { target *-*-* } 61 } +// { dg-warning "Access to memory location pointed to by variable 'gp' requires lock 'mu1'" "" { target *-*-* } 67 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-10.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-10.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-10.C @@ -0,0 +1,45 @@ +// Test lock annotations and analysis escape hatches +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Bar { + private: + Mutex mu_; + void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_.Lock(); } + void Unlock() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); } + float foo GUARDED_BY(mu_); + + public: + float get_foo() { + float ret; + Lock(); + ret = foo; + Unlock(); + return ret; + } + + // Thread safety analysis will skip this function + void set_foo(float a) NO_THREAD_SAFETY_ANALYSIS { + // Lock(); + foo = a; + Unlock(); + } +}; + +int pthread_mutex_lock(int i, int j, Mutex *mutex, int k) EXCLUSIVE_LOCK_FUNCTION(3); +int pthread_mutex_unlock(int i, int j, Mutex *mutex, int k) UNLOCK_FUNCTION(3); + +Bar *x; +Mutex fastmutex; +float val GUARDED_BY(fastmutex); + +main() +{ + pthread_mutex_lock(1, 2, &fastmutex, 3); + x->set_foo(2.5); + val = x->get_foo(); + pthread_mutex_unlock(4, 5, &fastmutex, 6); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-11.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-11.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-11.C @@ -0,0 +1,100 @@ +// Test lock annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +extern Mutex mu3; + +Mutex mu2 ACQUIRED_BEFORE(mu3); +Mutex mu1 ACQUIRED_BEFORE(mu2); +Mutex mu4 ACQUIRED_BEFORE(mu3); +Mutex mu3 ACQUIRED_BEFORE(mu1); + +int gx GUARDED_BY(mu1) = 3; +float gy GUARDED_BY(mu2) = 5.0; +int gz GUARDED_BY(mu4) = 2; +int *gp GUARDED_BY(mu3) PT_GUARDED_BY(mu1); + +class Foo { + private: + Mutex mu_ ACQUIRED_AFTER(mu3); + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + + Foo() : a_(1) { b_ = 5.0; } + + ~Foo() { a_ = 0; b_ = 0.0; } + + int incrementA(int i) LOCKS_EXCLUDED(mu_) + { + int res; + mu_.Lock(); + a_ += i; + res = a_; + mu_.Unlock(); + return res; + } + + float updateB(float f) LOCKS_EXCLUDED(mu_, mu3) SHARED_LOCKS_REQUIRED(mu1) + EXCLUSIVE_LOCKS_REQUIRED(mu2) + { + float res; + + mu3.Lock(); // { dg-warning "There is a cycle in the acquisition order between locks 'mu2' and 'mu3'" } + incrementA(*gp); + mu3.Unlock(); + + mu1.Lock(); // { dg-warning "Try to acquire lock 'mu1' that is already held \\(at function entry\\)" } + mu_.Lock(); + if (gx > 2) + b_ -= f; + else + b_ -= ++gy; + res = b_; + mu_.Unlock(); + mu1.Unlock(); + return res; + } +}; + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY(mu2) = &gy; + Foo foo; + + if (la > 3) { + mu1.ReaderLock(); + la = gx + 1; + mu1.Unlock(); + } + else { + mu2.Lock(); + mu3.ReaderLock(); // { dg-warning "There is a cycle in the acquisition order between locks 'mu2' and 'mu3'" } + + *p = foo.updateB(gy); // { dg-warning "Calling function 'updateB' requires lock 'mu1'" } + + mu3.Unlock(); + } + + mu2.Unlock(); // { dg-warning "Try to unlock 'mu2' that was not acquired" } + + mu3.Lock(); + mu4.Lock(); + + gp = &gx; + gz += 2; + + mu4.Unlock(); +} + +// { dg-warning "There is a cycle in the acquisition order between locks 'mu1' and 'mu3'" "" { target *-*-* } 46 } +// { dg-warning "Lock 'mu2' \\(acquired at line 77\\) is not released at the end of its scope in function 'func1'" "" { target *-*-* } 77 } +// { dg-warning "Cannot call function 'updateB' with lock 'mu3' held \\(previously acquired at line 78\\)" "" { target *-*-* } 80 } +// { dg-warning "Lock 'mu3' \\(acquired at line 87\\) is not released at the end of function 'func1'" "" { target *-*-* } 87 } +// { dg-warning "Lock 'mu4' is acquired after lock 'mu3' \\(acquired at line 87\\) but is annotated otherwise" "" { target *-*-* } 88 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-12.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-12.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-12.C @@ -0,0 +1,94 @@ +// Test lock annotations +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +extern Mutex mu1; + +Mutex mu2 ACQUIRED_AFTER(mu1); +Mutex mu1 ACQUIRED_BEFORE(mu2); +Mutex mu3 ACQUIRED_AFTER(mu2); +Mutex mu4 ACQUIRED_BEFORE(mu2, mu3); + +int gx GUARDED_BY(mu1) = 3; +float gy GUARDED_BY(mu2) = 5.0; +int gz GUARDED_BY(mu4) = 2; +int *gp GUARDED_BY(mu3) PT_GUARDED_BY(mu1); + +class Foo { + private: + Mutex mu_ ACQUIRED_AFTER(mu3); + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + + Foo() : a_(1) { b_ = 5.0; } + + ~Foo() { a_ = 0; b_ = 0.0; } + + int incrementA(int i) LOCKS_EXCLUDED(mu_) + { + int res; + mu_.Lock(); + a_ += i; + res = a_; + mu_.Unlock(); + return res; + } + + float updateB(float f) LOCKS_EXCLUDED(mu_) SHARED_LOCKS_REQUIRED(mu1, mu3) + EXCLUSIVE_LOCKS_REQUIRED(mu2) + { + float res; + + incrementA(*gp); + + mu_.Lock(); + if (gx > 2) + b_ -= f; + else + b_ -= ++gy; + res = b_; + mu_.Unlock(); + return res; + } +}; + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY(mu2) = &gy; + Foo foo; + + + mu1.ReaderLock(); + + if (gx > 3) { + la = gx + 1; + } + else { + mu2.Lock(); + mu3.ReaderLock(); + + *p = foo.updateB(gy); + + mu3.Unlock(); + mu2.Unlock(); + } + + mu1.Unlock(); + + mu4.Lock(); + mu3.Lock(); + + gp = &gx; + gz += 2; + + mu3.Unlock(); + mu4.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-13.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-13.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-13.C @@ -0,0 +1,63 @@ +// Test lock annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +int g GUARDED_BY(mu1); +int w GUARDED_BY(mu2); + +class Foo { + public: + void bar() LOCKS_EXCLUDED(mu_, mu1) + { + int x; + + mu_.Lock(); + x = foo(); // { dg-warning "Calling function 'foo' requires lock 'mu2'" } + a_ = x + 1; + mu_.Unlock(); + if (x > 5) { + mu1.Lock(); + g = 2.3; + mu1.Unlock(); + } + } + int foo() SHARED_LOCKS_REQUIRED(mu_) EXCLUSIVE_LOCKS_REQUIRED(mu2); + + private: + int a_ GUARDED_BY(mu_); + public: + Mutex mu_ ACQUIRED_AFTER(mu1); +}; + +Mutex mu2; + +int Foo::foo() +{ + int res; + w = 5.2; + res = a_ + 5; + return res; +} + +main() +{ + Foo f1, *f2; + f1.mu_.Lock(); + f1.bar(); + mu2.Lock(); + f1.foo(); + mu2.Unlock(); + f1.mu_.Unlock(); + f2->mu_.Lock(); + f2->bar(); + f2->mu_.Unlock(); + mu2.Lock(); + w = 2.5; + mu2.Unlock(); +} + +// { dg-warning "Cannot call function 'bar' with lock 'f1.mu_' held \\(previously acquired at line 48\\)" "" { target *-*-* } 49 } +// { dg-warning "Cannot call function 'bar' with lock 'f2->mu_' held \\(previously acquired at line 54\\)" "" { target *-*-* } 55 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-14.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-14.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-14.C @@ -0,0 +1,33 @@ +// Test lock annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1, mu2; +int x; +int a GUARDED_BY(mu); + +void bar(Mutex &mu) EXCLUSIVE_LOCKS_REQUIRED(mu); + +void bar(Mutex &mu) +{ + if (x) { + a = x + 1; + mu.Unlock(); + } + else + { + // mu.Unlock(); + } +} + + +void foo() +{ + mu2.Lock(); + bar(mu1); // { dg-warning "Calling function 'bar' requires lock 'mu1'" } + mu2.Unlock(); +} + +// { dg-warning "Lock 'mu' \\(held at entry\\) is released on some but not all control flow paths in function 'bar'" "" { target *-*-* } 13 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-15.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-15.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-15.C @@ -0,0 +1,30 @@ +// Test lock annotations +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1, mu2; +int x; +int a GUARDED_BY(mu); + +void bar(Mutex &mu) EXCLUSIVE_LOCKS_REQUIRED(mu); + +void bar(Mutex &mu) +{ + if (x) { + a = x + 1; + mu.Unlock(); + } + else + mu.Unlock(); +} + + +void foo() +{ + mu1.Lock(); + bar(mu1); + mu1.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-16.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-16.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-16.C @@ -0,0 +1,52 @@ +// Test trylock annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + private: + Mutex mu_; + Mutex mu2_; + int a_ GUARDED_BY(mu_); + int b_ GUARDED_BY(mu2_); + public: + bool func(int y) + { + int x; + x = y - 2; + if (mu_.TryLock()) { + a_ = x * 3; + if (mu2_.TryLock()) { + b_ += y; + mu2_.Unlock(); + } + mu_.Unlock(); + return true; + } + mu_.Unlock(); // { dg-warning "Try to unlock 'mu_' that was not acquired" } + return false; + } +}; + +Mutex mu1 ; + +Foo *foo GUARDED_BY(mu1); + +int main() +{ + bool a, b, c, d; + a = mu1.TryLock(); + b = !a; + c = b; + d = !c; + if (d) + { + return 1; + } + foo->func(2); // { dg-warning "Reading variable 'foo' requires lock 'mu1'" } + mu1.Unlock(); // { dg-warning "Try to unlock 'mu1' that was not acquired" } + return 0; +} + +// { dg-warning "Lock 'mu1' \\(acquired at line 39\\) is not released at the end of its scope in function 'main'" "" { target *-*-* } 39 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-17.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-17.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-17.C @@ -0,0 +1,72 @@ +// Test trylock annotations +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu3; + +class Foo { + private: + Mutex mu_; + Mutex mu2_; + int a_ GUARDED_BY(mu_); + int b_ GUARDED_BY(mu2_); + bool MyTryLock() EXCLUSIVE_TRYLOCK_FUNCTION(false, mu2_) { + return !mu2_.TryLock(); + } + int MyTryLock2(int a, float c, Mutex *lock1, Mutex *lock2) + EXCLUSIVE_TRYLOCK_FUNCTION(2, lock2, lock1) + { + if (lock2->TryLock() && lock1->TryLock()) + return 2; + else + return 0; + } + public: + bool func(int y) + { + int x; + x = y - 2; + if (mu_.TryLock()) { + a_ = x * 3; + if (!MyTryLock()) { + b_ += y; + mu2_.Unlock(); + } + mu_.Unlock(); + return true; + } + int ret = MyTryLock2(17, 0.5, &mu2_, &mu3); + bool cond = ret == 2; + bool t = !cond; + bool s = !t; + if (s) { + b_ += y; + mu2_.Unlock(); + mu3.Unlock(); + } + return false; + } +}; + +Mutex mu1 ; + +Foo *foo GUARDED_BY(mu1); + +int main() +{ + bool a, b, c, d; + a = mu1.TryLock(); + b = !a; + c = b; + d = !c; + if (!d) + { + return 1; + } + foo->func(2); + mu1.Unlock(); + return 0; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-18.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-18.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-18.C @@ -0,0 +1,30 @@ +// Test the ability to distinguish between the same lock field of +// different objects of a class. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Bar { + public: + bool MyTryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true, mu1_); + void MyUnlock() UNLOCK_FUNCTION(mu1_); + int a_ GUARDED_BY(mu1_); + + private: + Mutex mu1_; +}; + +Bar *b1, *b2; + +void func() +{ + if (b1->MyTryLock()) { + b1->a_ = 5; + b2->a_ = 3; // { dg-warning "Writing to variable 'b2->a_' requires lock 'b2->mu1_'" } + if (b2->MyTryLock()) { + b2->MyUnlock(); + } + b1->MyUnlock(); + } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-19.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-19.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-19.C @@ -0,0 +1,31 @@ +// Test the ability to distinguish between the same lock field of +// different objects of a class. +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Bar { + public: + bool MyTryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true, mu1_); + void MyUnlock() UNLOCK_FUNCTION(mu1_); + int a_ GUARDED_BY(mu1_); + + private: + Mutex mu1_; +}; + +Bar *b1, *b2; + +void func() +{ + if (b1->MyTryLock()) { + b1->a_ = 5; + if (b2->MyTryLock()) { + b2->a_ = 3; + b2->MyUnlock(); + } + b1->MyUnlock(); + } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-2.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-2.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-2.C @@ -0,0 +1,82 @@ +// Test guarded_by/guarded/pt_guarded_by annotations +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +Mutex mu2 ACQUIRED_AFTER(mu1); +Mutex mu3 ACQUIRED_AFTER(mu2); + +int gx GUARDED_BY(mu1) = 3; +float gy GUARDED_BY(mu2) = 5.0; +int *gp GUARDED_BY(mu3) PT_GUARDED_BY(mu1); +int gw GUARDED_VAR = 2; + +class Foo { + private: + Mutex mu_ ACQUIRED_AFTER(mu2); + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + + Foo() : a_(1) { b_ = 5.0; } + + ~Foo() { a_ = 0; b_ = 0.0; } + + void incrementA(int i) LOCKS_EXCLUDED(mu_) + { + mu_.Lock(); + a_ += i; + mu_.Unlock(); + } + + float decrementB(float f) LOCKS_EXCLUDED(mu_) SHARED_LOCKS_REQUIRED(mu1) + { + float res; + mu_.Lock(); + if (gx > 2) + b_ -= f; + else + b_ -= 2 * f; + res = b_; + mu_.Unlock(); + return res; + } +}; + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY(mu2) = &gy; + Foo foo; + + mu3.Lock(); + gp = &gx; + mu3.Unlock(); + + mu1.ReaderLock(); + + if (gx > 3) { + la = gx + gw; + } + else { + mu2.Lock(); + *p = foo.decrementB(gy); + mu2.Unlock(); + } + + foo.incrementA(gx); + + mu1.Unlock(); + + if (la < 10) { + MutexLock l(&mu1); + ReaderMutexLock rl(&mu3); + *gp = 7; + } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-20.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-20.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-20.C @@ -0,0 +1,25 @@ +// Test if delayed binding works with static class members. +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Bar { + public: + static int func1() EXCLUSIVE_LOCKS_REQUIRED(mu1_); + static int b_ GUARDED_BY(mu1_); + static Mutex mu1_; + static int a_ GUARDED_BY(mu1_); +}; + +Bar b1; + +int Bar::func1() +{ + int res = 5; + + if (a_ == 4) + res = b_; + return res; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-21.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-21.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-21.C @@ -0,0 +1,39 @@ +// Test various usage of GUARDED_BY and PT_GUARDED_BY annotations, especially +// uses in class definitions. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu; + +class Bar { + public: + int a_ GUARDED_BY(mu1_); + int b_; + int *q PT_GUARDED_BY(mu); + Mutex mu1_ ACQUIRED_AFTER(mu); +}; + +Bar b1, *b3; +int *p GUARDED_BY(mu) PT_GUARDED_BY(mu); + +int res GUARDED_BY(mu) = 5; + +int func(int i) +{ + int x; + b3->mu1_.Lock(); + res = b1.a_ + b3->b_; // { dg-warning "Reading variable 'b1.a_' requires lock 'b1.mu1_'" } + *p = i; // { dg-warning "Reading variable 'p' requires lock 'mu'" } + b1.a_ = res + b3->b_; // { dg-warning "Reading variable 'res' requires lock 'mu'" } + b3->b_ = *b1.q; // { dg-warning "Access to memory location pointed to by variable 'b1.q' requires lock 'mu'" } + b3->mu1_.Unlock(); + b1.b_ = res; // { dg-warning "Reading variable 'res' requires lock 'mu'" } + x = res; // { dg-warning "Reading variable 'res' requires lock 'mu'" } + return x; +} + +// { dg-warning "Writing to variable 'res' requires lock 'mu'" "" { target *-*-* } 27 } +// { dg-warning "Access to memory location pointed to by variable 'p' requires lock 'mu'" "" { target *-*-* } 28 } +// { dg-warning "Writing to variable 'b1.a_' requires lock 'b1.mu1_'" "" { target *-*-* } 29 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-22.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-22.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-22.C @@ -0,0 +1,37 @@ +// Test various usage of GUARDED_BY and PT_GUARDED_BY annotations, especially +// uses in class definitions. +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu; + +class Bar { + public: + int a_ GUARDED_BY(mu1_); + int b_; + int *q PT_GUARDED_BY(mu); + Mutex mu1_ ACQUIRED_AFTER(mu); +}; + +Bar b1, *b3; +int *p GUARDED_BY(mu) PT_GUARDED_BY(mu); +int res GUARDED_BY(mu) = 5; + +int func(int i) +{ + int x; + mu.Lock(); + b1.mu1_.Lock(); + res = b1.a_ + b3->b_; + *p = i; + b1.a_ = res + b3->b_; + b3->b_ = *b1.q; + b1.mu1_.Unlock(); + b1.b_ = res; + x = res; + mu.Unlock(); + return x; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-23.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-23.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-23.C @@ -0,0 +1,55 @@ +// Test guarded_by/pt_guarded_by annotations with unsupported or unrecognized +// lock names/expressions +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +namespace ns1 { + +int p GUARDED_BY(a->mu); // { dg-warning "'guarded_by' attribute downgraded to 'guarded'" } +int q GUARDED_BY(f2()->get_lock()); // { dg-warning "'guarded_by' attribute downgraded to 'guarded'" } + +class Foo { + public: + Mutex mu_; +}; + +int r GUARDED_BY(ns2::Foo::mu_); // { dg-warning "'guarded_by' attribute downgraded to 'guarded'" } + +template <typename T> +class Bar { + public: + + T func1() { + T ret; + mu1_.Lock(); + a_ = 5; // { dg-warning "Writing to variable 'a_' requires lock 'foo_->mu_'" } + x_ = 3 + q; + ret = x_ - *y_; + mu1_.Unlock(); + return ret; + } + + T x_ GUARDED_BY(((mu1_))); + Mutex mu1_ ACQUIRED_AFTER(mu2); // { dg-warning "Unsupported argument of 'acquired_after' attribute ignored" } + T a_ GUARDED_BY(foo_->mu_); + T *y_ PT_GUARDED_BY(array[2]->mu_); // { dg-warning "'point_to_guarded_by' attribute downgraded to 'point_to_guarded'" } + Foo *foo_; +}; + +Bar<int> *b1 GUARDED_BY((a->mu)); // { dg-warning "'guarded_by' attribute downgraded to 'guarded'" } +Foo *f2; + +int main() +{ + f2->mu_.Lock(); + b1->mu1_.Lock(); + b1->func1(); + p = r + 5; + b1->x_ = 3; + b1->mu1_.Unlock(); + f2->mu_.Unlock(); +} + +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-24.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-24.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-24.C @@ -0,0 +1,73 @@ +// Test lock/trylock/unlock annotations with unsupported or unrecognized lock +// names/expressions +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +namespace ns1 { + +template <typename T> +class Bar { + public: + void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_); + void Unlock() UNLOCK_FUNCTION(mu_); + void MyLock() EXCLUSIVE_LOCK_FUNCTION(mu1); // { dg-warning "Unsupported argument of 'exclusive_lock' attribute ignored" } + void MyUnlock() UNLOCK_FUNCTION(mu1); // { dg-warning "Unsupported argument of 'unlock' attribute ignored" } + + T get_foo() { + T ret; + MyLock(); + ret = foo; + MyUnlock(); + return ret; + } + + void set_foo(T a) { + Lock(); + foo = a; + Unlock(); + } + + private: + Mutex mu_; + T foo GUARDED_BY(mu_); +}; + +int pthread_mutex_lock(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu) +{ + return 5; +} + +int pthread_mutex_unlock(Mutex *mu) UNLOCK_FUNCTION(mu); +int pthread_mutex_trylock() EXCLUSIVE_TRYLOCK_FUNCTION(0, (t->mu1)); // { dg-warning "Unsupported argument of 'exclusive_trylock' attribute ignored" } + +Mutex mu2; +int p GUARDED_BY(mu2); +int r; + +Bar<float> *b1; + +void func1() +{ + b1->set_foo(3.5); + pthread_mutex_lock(&mu2); + r = b1->get_foo(); + p = r + 5; + pthread_mutex_unlock(&mu2); + if (!pthread_mutex_trylock()) + { + p = 2; + // Since the annotation on pthread_mutex_trylock contains an unrecognized + // lock (t->mu1), we don't warn if it is not released at the end of the + // scope. + } + pthread_mutex_lock(&mu2); + // Since the annotation on Bar<float>::MyUnlock contains an unrecognized + // lock name, the analysis would conservatively disable the check for + // mismatched lock acquire/release. Therefore even though mu2 is not + // released, we don't emit a warning. + b1->MyUnlock(); +} + +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-25.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-25.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-25.C @@ -0,0 +1,55 @@ +// Test function lock requirement annotations with unsupported or unrecognized +// lock names/expressions +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +namespace ns1 { + +struct Foo { + Mutex mu_; +}; + +template <typename T> +class Bar { + private: + Mutex mu_; + void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_); + void Unlock() UNLOCK_FUNCTION(mu_); + T foo GUARDED_BY(mu_); + Foo *x; + + public: + Mutex *GetLock1() LOCK_RETURNED(mu_); + Mutex *GetLock2() LOCK_RETURNED(x->mu_); + Mutex *GetLock3() LOCK_RETURNED(y->mu_); // { dg-warning "'lock_returned' attribute ignored due to the unsupported argument" } + + T get_foo() SHARED_LOCKS_REQUIRED(x->mu_, (mu2)) { // { dg-warning "Unsupported argument of 'shared_locks_required' attribute ignored" } + T ret; + ret = foo; + return ret; + } + + void set_foo(T a) EXCLUSIVE_LOCKS_REQUIRED((mu_), GetLock2()) { // { dg-warning "Unsupported argument of 'exclusive_locks_required' attribute ignored" } + foo = a; + } +}; + +Mutex mu2; +float r; +Bar<float> *b1; + +void func1() +{ + { + MutexLock ml(b1->GetLock1()); + b1->set_foo(3.5); + r = b1->get_foo(); // { dg-warning "Calling function 'get_foo' requires lock 'x->mu_'" } + } + b1->GetLock2()->Lock(); + r += b1->get_foo(); + b1->GetLock2()->Unlock(); +} + +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-26.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-26.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-26.C @@ -0,0 +1,42 @@ +// Test lock annotations applied to function definitions +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +Mutex mu2 ACQUIRED_AFTER(mu1); + +class Foo { + public: + int method1(int i) SHARED_LOCKS_REQUIRED(mu2); +}; + +int Foo::method1(int i) EXCLUSIVE_LOCKS_REQUIRED(mu1) +{ + return i; +} + + +int foo(int i) EXCLUSIVE_LOCKS_REQUIRED(mu2); +int foo(int i) SHARED_LOCKS_REQUIRED(mu1) +{ + return i; +} + +static int bar(int i) EXCLUSIVE_LOCKS_REQUIRED(mu1) +{ + return i; +} + +main() +{ + Foo a; + + a.method1(1); // { dg-warning "Calling function 'method1' requires lock 'mu1'" } + foo(2); // { dg-warning "Calling function 'foo' requires lock 'mu2'" } + bar(3); // { dg-warning "Calling function 'bar' requires lock 'mu1'" } +} + +// { dg-warning "Calling function 'method1' requires lock 'mu2'" "" { target *-*-* } 36 } +// { dg-warning "Calling function 'foo' requires lock 'mu1'" "" { target *-*-* } 37 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-27.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-27.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-27.C @@ -0,0 +1,44 @@ +// Test lock annotations applied to function definitions. This is a "good" +// test that should not incur any compilation wanrings. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +Mutex mu2 ACQUIRED_AFTER(mu1); + +class Foo { + public: + int method1(int i) SHARED_LOCKS_REQUIRED(mu2); +}; + +int Foo::method1(int i) EXCLUSIVE_LOCKS_REQUIRED(mu1) +{ + return i; +} + + +int foo(int i) EXCLUSIVE_LOCKS_REQUIRED(mu2); +int foo(int i) SHARED_LOCKS_REQUIRED(mu1) +{ + return i; +} + +static int bar(int i) EXCLUSIVE_LOCKS_REQUIRED(mu1) +{ + return i; +} + +main() +{ + Foo a; + + mu1.Lock(); + mu2.Lock(); + a.method1(1); + foo(2); + mu2.Unlock(); + bar(3); + mu1.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-28.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-28.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-28.C @@ -0,0 +1,37 @@ +// Test the unlock attribute that takes a lock name not in scope when parsing. +// The late binding mechanism should be able to match the lock name with the +// decl in the analysis. This is a "good" test that should not incur any +// compilation wanrings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +class FooBar { + public: + bool Foo(); + private: + void ReleaseFooBar() UNLOCK_FUNCTION(my_lock); +}; + +static Mutex my_lock; + +int a = 0; + +bool FooBar::Foo() { + my_lock.Lock(); + if (a) { + a += 1; + my_lock.Unlock(); + return true; + } + else { + a += 2; + ReleaseFooBar(); + return false; + } +} + +void FooBar::ReleaseFooBar() { + my_lock.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-29.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-29.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-29.C @@ -0,0 +1,25 @@ +// Test the handling of lock expressions that contain non-const array indices. +// This is a "good" test that should not incur any compilation wanrings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +struct Foo { + Mutex mu; + int data; +}; + +const int MaxArraySize = 16; + +Foo foo[MaxArraySize]; + +int func() { + int result = 0; + for (int cpu = 0; cpu != MaxArraySize; cpu++) { + foo[cpu].mu.Lock(); + result += foo[cpu].data; + foo[cpu].mu.Unlock(); + } + return result; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-3.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-3.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-3.C @@ -0,0 +1,59 @@ +// Test guarded_by/guarded/pt_guarded_by/pt_guarded annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +Mutex mu2 ACQUIRED_AFTER(mu1); +Mutex mu3 ACQUIRED_AFTER(mu2); +Mutex mu4[5]; + +class Foo { + private: + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + Mutex mu_ ACQUIRED_AFTER(mu2); + Mutex *get_lock() __attribute__((pure)) LOCK_RETURNED(mu_) { return &mu_; } + void incrementA(int i); + float decrementB(float f); +}; + +class Bar { + public: + Mutex mu_ ACQUIRED_AFTER ((mu4[1])); + void method1() const; + void method2(); +}; + +Foo *foo; +Bar bar[2]; +int gx GUARDED_BY((mu4[1])) = 3; +float gy GUARDED_BY((foo->mu_)) = 5.0; +int *gp PT_GUARDED_VAR GUARDED_BY((bar[0].mu_)); + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY((foo->mu_)) = &gy; + + gp = &gx; // { dg-warning "Writing to variable 'gp' requires lock 'bar\\\[0\\\].mu_'" } + + if (gx > 3) { // { dg-warning "Reading variable 'gx' requires lock 'mu4\\\[1\\\]'" } + la = gx + 1; // { dg-warning "Reading variable 'gx' requires lock 'mu4\\\[1\\\]'" } + } + else { + *p = foo->decrementB(gy); // { dg-warning "Reading variable 'gy' requires lock 'foo->mu_'" } + } + + if (la < 10) { + *gp = 7; // { dg-warning "Reading variable 'gp' requires lock 'bar\\\[0\\\].mu_'" } + } +} + +// { dg-warning "Access to memory location pointed to by variable 'p' requires lock 'foo->mu_'" "" { target *-*-* } 50 } +// { dg-warning "Access to memory location pointed to by variable 'gp' requires a lock" "" { target *-*-* } 54 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-30.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-30.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-30.C @@ -0,0 +1,44 @@ +// Test delay parsing of lock attribute arguments with nested classes. +// This is a "good" test that should not incur any compilation wanrings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +int a = 0; + +class Bar { + struct Foo; + + public: + Foo *my_trylock() EXCLUSIVE_TRYLOCK_FUNCTION(true, mu); + + int func() { + const Foo *foo = my_trylock(); + + if (foo == 0) { + return 0; + } + + a = 5; + + mu.Unlock(); + + return 1; + } + + class FooBar { + int x; + int y; + }; + + private: + Mutex mu; +}; + +Bar *bar; + +main() +{ + bar->func(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-31.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-31.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-31.C @@ -0,0 +1,68 @@ +// Test lock expressions that contain function calls. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + + +class Foo { + private: + Mutex mu; + + public: + void Lock() EXCLUSIVE_LOCK_FUNCTION(mu); + void Unlock() UNLOCK_FUNCTION(mu); +}; + + +class DerivedFoo : public Foo { + private: + Mutex mu1; + int b_; +}; + +class Bar { + protected: + DerivedFoo *foo_; + DerivedFoo *GetFoo(int y); +}; + +class DerivedBar : public Bar { + private: + int a_; + + public: + void UpdateA(int x); + void SelectA(int x); +}; + +void DerivedBar::UpdateA(int x) { + GetFoo(2)->Lock(); // { dg-warning "is not released at the end of function" } + a_ += x; + GetFoo(3)->Unlock(); // { dg-warning "Try to unlock" } +} + +void DerivedBar::SelectA(int x) { + GetFoo(5)->Lock(); + GetFoo(x)->Lock(); // { dg-warning "is not released at the end of function" } + a_ = x; + GetFoo(a_)->Unlock(); // { dg-warning "Try to unlock" } + GetFoo(5)->Unlock(); +} + +int g; + +int func(int x, DerivedFoo * (*getfoo1)(int), DerivedFoo * (*getfoo2)(int)) { + getfoo1(5)->Lock(); // { dg-warning "is not released at the end of function" } + getfoo2(x)->Lock(); // { dg-warning "is not released at the end of function" } + g += x; + getfoo2(5)->Unlock(); // { dg-warning "Try to unlock" } + getfoo1(g)->Unlock(); // { dg-warning "Try to unlock" } +} + +DerivedBar *bar; + +main() { + bar->UpdateA(3); + bar->SelectA(2); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-32.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-32.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-32.C @@ -0,0 +1,71 @@ +// Test lock expressions that contain function calls. +// This is a "good" test that should not incur any compilation wanrings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + + +class Foo { + private: + Mutex mu; + + public: + void Lock() EXCLUSIVE_LOCK_FUNCTION(mu); + void Unlock() UNLOCK_FUNCTION(mu); +}; + + +class DerivedFoo : public Foo { + private: + Mutex mu1; + int b_; +}; + +class Bar { + protected: + DerivedFoo *foo_; + DerivedFoo *GetFoo(int y); +}; + +class DerivedBar : public Bar { + private: + int a_; + + public: + void UpdateA(int x); + void SelectA(int x); +}; + +void DerivedBar::UpdateA(int x) { + GetFoo(2)->Lock(); + a_ += x; + GetFoo(2)->Unlock(); +} + +void DerivedBar::SelectA(int x) { + GetFoo(x)->Lock(); + GetFoo(a_)->Lock(); + a_ = x; + GetFoo(a_)->Unlock(); + GetFoo(x)->Unlock(); +} + +int g; + +int func(int x, DerivedFoo * (*getfoo1)(int), DerivedFoo * (*getfoo2)(int)) { + getfoo1(5)->Lock(); + getfoo2(x)->Lock(); + getfoo1(g)->Lock(); + g += x; + getfoo1(g)->Unlock(); + getfoo2(x)->Unlock(); + getfoo1(5)->Unlock(); +} + +DerivedBar *bar; + +main() { + bar->UpdateA(3); + bar->SelectA(2); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-33.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-33.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-33.C @@ -0,0 +1,38 @@ +// Test lockable objects wrapped in smart pointers. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" +#include <memory> + +using namespace std; + +class Foo { + private: + auto_ptr<Mutex> lock; + int a GUARDED_BY(lock); + + public: + int GetA() EXCLUSIVE_LOCKS_REQUIRED(lock) { + int result; + lock->Unlock(); + result = a; // { dg-warning "Reading variable 'a' requires lock 'lock'" } + lock->Lock(); + return result; + } + + int GetValue() { + int result; + result = GetA(); // { dg-warning "Calling function 'GetA' requires lock 'lock'" } + lock->Unlock(); // { dg-warning "Try to unlock 'lock' that was not acquired" } + return result; + } +}; + +Foo *foo; +int x; + +main() +{ + x = foo->GetValue(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-34.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-34.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-34.C @@ -0,0 +1,48 @@ +// Test lockable objects wrapped in smart pointers. +// This is a "good" program that should not incur any compiler warnings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" +#include <memory> + +using namespace std; + +class Foo { + private: + auto_ptr<Mutex> lock; + int a GUARDED_BY(lock); + + public: + int GetA() EXCLUSIVE_LOCKS_REQUIRED(lock) { + int result; + result = a; + lock->Unlock(); + lock->Lock(); + return result; + } + + int GetValue1() { + int result; + lock->Lock(); + result = GetA(); + lock->Unlock(); + return result; + } + + int GetValue2() { + int result; + MutexLock l(lock.get()); + result = GetA(); + return result; + } +}; + +Foo *foo; +int x; + +main() +{ + x = foo->GetValue1(); + x += foo->GetValue2(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-35.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-35.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-35.C @@ -0,0 +1,35 @@ +// Test the analyzer's ability to distinguish the lock field of different +// objects. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +class Foo { + private: + Mutex lock_; + int a_ GUARDED_BY(lock_); + + public: + void Func(Foo* child) LOCKS_EXCLUDED(lock_) { + Foo *new_foo = new Foo; + + MutexLock l(&lock_); + + child->Func(new_foo); // There shouldn't be any warning here as the + // acquired lock is not in child. + child->bar(7); // { dg-warning "Calling function 'bar' requires lock" } + child->a_ = 5; // { dg-warning "Writing to variable 'child->a_' requires lock" } + } + + void bar(int y) EXCLUSIVE_LOCKS_REQUIRED(lock_) { + a_ = y; + } +}; + +Foo *x; + +main() { + Foo *child = new Foo; + x->Func(child); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-36.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-36.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-36.C @@ -0,0 +1,36 @@ +// Test the analyzer's ability to distinguish the lock field of different +// objects. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +class Foo { + private: + Mutex lock_; + int a_ GUARDED_BY(lock_); + + public: + void Func(Foo* child) LOCKS_EXCLUDED(lock_) { + Foo *new_foo = new Foo; + + MutexLock l(&lock_); + + child->lock_.Lock(); + child->Func(new_foo); // { dg-warning "Cannot call function 'Func' with lock 'child->lock_' held" } + child->bar(7); + child->a_ = 5; + child->lock_.Unlock(); + } + + void bar(int y) EXCLUSIVE_LOCKS_REQUIRED(lock_) { + a_ = y; + } +}; + +Foo *x; + +main() { + Foo *child = new Foo; + x->Func(child); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-37.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-37.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-37.C @@ -0,0 +1,23 @@ +// Test the case where a template member function is annotated with lock +// attributes in a non-template class. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + void func1(int y) EXCLUSIVE_LOCKS_REQUIRED(mu_); + template <typename T> void func2(T x) LOCKS_EXCLUDED(mu_); + Mutex mu_; +}; + +Foo *foo; + +int main() +{ + foo->mu_.Lock(); + foo->func1(5); + foo->func2(5); // { dg-warning "Cannot call function 'func2' with lock 'foo->mu_' held" } + foo->mu_.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-38.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-38.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-38.C @@ -0,0 +1,23 @@ +// Test the case where a template member function is annotated with lock +// attributes in a non-template class. +// This is a "good" test that should not incur any compiler warnings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + void func1(int y) LOCKS_EXCLUDED(mu_); + template <typename T> void func2(T x) LOCKS_EXCLUDED(mu_); + private: + Mutex mu_; +}; + +Foo *foo; + +int main() +{ + foo->func1(5); + foo->func2(5); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-39.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-39.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-39.C @@ -0,0 +1,43 @@ +// Test the case where the member function of a class member object protected +// by a lock is invoked. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" +#include <memory> + +class Foo { + public: + Mutex lock; + std::auto_ptr<int> data GUARDED_BY(lock); + + int GetValue() { + int result; + if (data.get()) // { dg-warning "Reading variable 'data' requires lock 'lock'" } + result = *data.get(); // { dg-warning "Reading variable 'data' requires lock 'lock'" } + return result; + } +}; + +class Bar { + private: + Foo *foo; + + public: + int GetB() { + if (foo->data.get()) // { dg-warning "Reading variable 'foo->data' requires lock 'foo->lock'" } + return 1; + else + return 2; + } +}; + +Foo *foo; +Bar *bar; +int x; + +int func() +{ + x = foo->GetValue(); + x += bar->GetB(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-4.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-4.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-4.C @@ -0,0 +1,66 @@ +// Test guarded_by/guarded/pt_guarded_by/pt_guarded annotations +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +Mutex mu2 ACQUIRED_AFTER(mu1); +Mutex mu3 ACQUIRED_AFTER(mu2); +Mutex mu4[5]; + +class Foo { + private: + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + Mutex mu_ ACQUIRED_AFTER(mu2); + Mutex *get_lock() LOCK_RETURNED(mu_) __attribute__((pure)) { return &mu_; } + void incrementA(int i); + float decrementB(float f); +}; + +class Bar { + public: + Mutex mu_ ACQUIRED_AFTER ((mu4[1])); + void method1() const; + void method2(); +}; + +Foo *foo; +Bar bar[2]; +int gx GUARDED_BY((mu4[1])) = 3; +float gy GUARDED_BY((foo->mu_)) = 5.0; +int *gp PT_GUARDED_VAR GUARDED_BY((bar[0].mu_)); + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY((foo->mu_)) = &gy; + + bar[0].mu_.Lock(); + gp = &gx; + bar[0].mu_.Unlock(); + + mu4[1].ReaderLock(); + + if (gx > 3) { + la = gx + 1; + mu4[1].Unlock(); + } + else { + mu4[1].Unlock(); + foo->get_lock()->Lock(); + *p = foo->decrementB(gy); + foo->get_lock()->Unlock(); + } + + if (la < 10) { + MutexLock rl(&bar[0].mu_); + *gp = 7; + } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-40.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-40.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-40.C @@ -0,0 +1,46 @@ +// Test the case where the member function of a class member object protected +// by a lock is invoked. +// This is a "good" test that should not incur any compiler warnings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" +#include <memory> + +class Foo { + public: + Mutex lock; + std::auto_ptr<int> data GUARDED_BY(lock); + + int GetValue() { + int result; + MutexLock l(&lock); + if (data.get()) + result = *data.get(); + return result; + } +}; + +class Bar { + private: + Foo *foo; + + public: + int GetB() { + MutexLock l(&foo->lock); + if (foo->data.get()) + return 1; + else + return 2; + } +}; + +Foo *foo; +Bar *bar; +int x; + +int func() +{ + x = foo->GetValue(); + x += bar->GetB(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-41.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-41.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-41.C @@ -0,0 +1,36 @@ +// Test the case where locks from a base class are used in derived classes. +// This is a "good" test that should not incur any compiler warnings. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } + +#include "thread_annot_common.h" + +class A { + protected: + Mutex lock_; + void BaseMethod() EXCLUSIVE_LOCKS_REQUIRED(lock_) { }; +}; + +class B; +B *foo; + +class B : public A { + public: + void ChildMethodOne() { + foo->lock_.Lock(); + foo->BaseMethod(); + lock_.Lock(); + ChildMethodTwo(); + lock_.Unlock(); + foo->lock_.Unlock(); + } + + void ChildMethodTwo() EXCLUSIVE_LOCKS_REQUIRED(lock_) { + BaseMethod(); + } +}; + +int main() { + B b; + b.ChildMethodOne(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-42.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-42.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-42.C @@ -0,0 +1,34 @@ +// Test support of multiple lock attributes of the same kind on a decl. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + private: + Mutex mu1, mu2, mu3; + int x GUARDED_BY(mu1) GUARDED_BY(mu3); // { dg-warning "ignored" } + int y GUARDED_BY(mu2); + + void f2() LOCKS_EXCLUDED(mu1) LOCKS_EXCLUDED(mu2) LOCKS_EXCLUDED(mu3) { + mu2.Lock(); + y = 2; + mu2.Unlock(); + } + + public: + void f1() EXCLUSIVE_LOCKS_REQUIRED(mu2) EXCLUSIVE_LOCKS_REQUIRED(mu1) { + x = 5; + f2(); // { dg-warning "Cannot call function 'f2' with lock 'mu1' held" } + } +}; + +Foo *foo; + +void func() +{ + foo->f1(); // { dg-warning "Calling function 'f1' requires lock 'foo->mu2'" } +} + +// { dg-warning "Cannot call function 'f2' with lock 'mu2' held" "" { target *-*-* } 22 } +// { dg-warning "Calling function 'f1' requires lock 'foo->mu1'" "" { target *-*-* } 30 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-43.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-43.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-43.C @@ -0,0 +1,31 @@ +// Test lock canonicalization when populating the initial lock sets of a +// function. It locks are properly canonicalized, the analysis should not +// complained about a_ in function FooBar::GetA() is not protected. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex *mu_; +}; + +class FooBar { + public: + Foo *foo_; + int GetA() EXCLUSIVE_LOCKS_REQUIRED(foo_->mu_) { return a_; } + int a_ GUARDED_BY(foo_->mu_); +}; + +FooBar *fb; + +main() +{ + int x; + fb->foo_->mu_->Lock(); + x = fb->GetA(); + fb->foo_->mu_->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-44.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-44.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-44.C @@ -0,0 +1,58 @@ +// Test the support for releasable scoped lock (e.g std::unique_lock). +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +extern void bar(); + +Mutex mu1, mu2; +int a GUARDED_BY(mu1); + +void foo(int x) { + if (x > 2) { + ReleasableMutexLock l(&mu1); + if (a < 3) { + a = x + 1; + l.Release(); + bar(); + } + else { + a = x + 2; + } + } +} + +void func(int x) { + ReleasableMutexLock l(&mu1); + ReleasableMutexLock m(&mu2); + switch (x) { + case 1: + { + a = x + 1; + l.Release(); + bar(); + break; + } + case 3: + { + a = x + 3; + m.Release(); + break; + } + case 2: + { + a = x + 2; + l.Release(); + bar(); + break; + } + default: + { + a = x + 3; + break; + } + } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-45.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-45.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-45.C @@ -0,0 +1,47 @@ +// Test the case where locks are members of the parent classes. We used to +// have problems handling such cases as the canonical forms of the lock +// expressions are not consistent. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + Mutex *mu_; + public: + Mutex* GetMu() LOCK_RETURNED(mu_); +}; + +class FooChild : public Foo { + public: + int a_ GUARDED_BY(mu_); + void Func2() EXCLUSIVE_LOCKS_REQUIRED(mu_); +}; + +class FooChildChild : public FooChild { + public: + int x_ GUARDED_BY(mu_); +}; + +class Bar { + FooChild *cfoo_; + FooChildChild *ccfoo_; + int b_; + + public: + void Func1() LOCKS_EXCLUDED(cfoo_->GetMu()) { + MutexLock l(cfoo_->GetMu()); + b_ = cfoo_->a_; + cfoo_->Func2(); + ccfoo_->GetMu()->Lock(); + ccfoo_->x_ = b_; + ccfoo_->GetMu()->Unlock(); + } +}; + +int main() { + Bar bar; + bar.Func1(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-46.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-46.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-46.C @@ -0,0 +1,33 @@ +// Test the support for annotations on virtual functions. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Base { + public: + virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_); + virtual void func2() LOCKS_EXCLUDED(mu_); + Mutex mu_; +}; + +class Child : public Base { + public: + virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_); + virtual void func2() LOCKS_EXCLUDED(mu_); +}; + +main() { + Child *c; + Base *b = c; + + b->func1(); // { dg-warning "Calling function 'func1' requires lock" } + b->mu_.Lock(); + b->func2(); // { dg-warning "Cannot call function" } + b->mu_.Unlock(); + + c->func1(); // { dg-warning "Calling function 'func1' requires lock" } + c->mu_.Lock(); + c->func2(); // { dg-warning "Cannot call function" } + c->mu_.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-47.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-47.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-47.C @@ -0,0 +1,35 @@ +// Test the support for annotations on virtual functions. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Base { + public: + virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_); + virtual void func2() LOCKS_EXCLUDED(mu_); + Mutex mu_; +}; + +class Child : public Base { + public: + virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_); + virtual void func2() LOCKS_EXCLUDED(mu_); +}; + +main() { + Child *c; + Base *b = c; + + b->mu_.Lock(); + b->func1(); + b->mu_.Unlock(); + b->func2(); + + c->mu_.Lock(); + c->func1(); + c->mu_.Unlock(); + c->func2(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-48.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-48.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-48.C @@ -0,0 +1,29 @@ +// Test the support for use of lock expression in the annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex foo_mu_; +}; + +class Bar { + private: + Foo *foo; + Mutex bar_mu_ ACQUIRED_AFTER(foo->foo_mu_); + + public: + void Test1() { + bar_mu_.Lock(); + foo->foo_mu_.Lock(); // { dg-warning "is acquired after" } + bar_mu_.Unlock(); + foo->foo_mu_.Unlock(); + } +}; + +main() { + Bar bar; + bar.Test1(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-49.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-49.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-49.C @@ -0,0 +1,31 @@ +// Test the support for use of lock expression in the annotations +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex foo_mu_; +}; + +class Bar { + private: + Foo *foo; + Mutex bar_mu_ ACQUIRED_AFTER(foo->foo_mu_); + + public: + void Test1() { + foo->foo_mu_.Lock(); + bar_mu_.Lock(); + bar_mu_.Unlock(); + foo->foo_mu_.Unlock(); + } +}; + +main() { + Bar bar; + bar.Test1(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-5.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-5.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-5.C @@ -0,0 +1,100 @@ +// Test lock annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +extern Mutex mu3; + +Mutex mu1 ACQUIRED_AFTER(mu3); +Mutex mu2 ACQUIRED_AFTER(mu1); +Mutex mu4; +Mutex mu3 ACQUIRED_AFTER(mu2, mu4); + +int gx GUARDED_BY(mu1) = 3; +float gy GUARDED_BY(mu2) = 5.0; +int gz GUARDED_BY(mu4) = 2; +int *gp GUARDED_BY(mu3) PT_GUARDED_BY(mu1); + +class Foo { + private: + Mutex mu_ ACQUIRED_AFTER(mu3); + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + + Foo() : a_(1) { b_ = 5.0; } + + ~Foo() { a_ = 0; b_ = 0.0; } + + int incrementA(int i) LOCKS_EXCLUDED(mu_) + { + int res; + mu_.Lock(); + a_ += i; + res = a_; + mu_.Unlock(); + return res; + } + + float updateB(float f) LOCKS_EXCLUDED(mu_, mu3) SHARED_LOCKS_REQUIRED(mu1) + EXCLUSIVE_LOCKS_REQUIRED(mu2) + { + float res; + + mu3.Lock(); // { dg-warning "There is a cycle in the acquisition order between locks 'mu2' and 'mu3'" } + incrementA(*gp); + mu3.Unlock(); + + mu1.Lock(); // { dg-warning "Try to acquire lock 'mu1' that is already held \\(at function entry\\)" } + mu_.Lock(); + if (gx > 2) + b_ -= f; + else + b_ -= ++gy; + res = b_; + mu_.Unlock(); + mu1.Unlock(); + return res; + } +}; + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY(mu2) = &gy; + Foo foo; + + if (la > 3) { + mu1.ReaderLock(); + la = gx + 1; + mu1.Unlock(); + } + else { + mu2.Lock(); + mu3.ReaderLock(); // { dg-warning "There is a cycle in the acquisition order between locks 'mu2' and 'mu3'" } + + *p = foo.updateB(gy); // { dg-warning "Calling function 'updateB' requires lock 'mu1'" } + + mu3.Unlock(); + } + + mu2.Unlock(); // { dg-warning "Try to unlock 'mu2' that was not acquired" } + + mu3.Lock(); + mu4.Lock(); + + gp = &gx; + gz += 2; + + mu4.Unlock(); +} + +// { dg-warning "There is a cycle in the acquisition order between locks 'mu1' and 'mu3'" "" { target *-*-* } 46 } +// { dg-warning "Lock 'mu2' \\(acquired at line 77\\) is not released at the end of its scope in function 'func1'" "" { target *-*-* } 77 } +// { dg-warning "Cannot call function 'updateB' with lock 'mu3' held \\(previously acquired at line 78\\)" "" { target *-*-* } 80 } +// { dg-warning "Lock 'mu3' \\(acquired at line 87\\) is not released at the end of function 'func1'" "" { target *-*-* } 87 } +// { dg-warning "Lock 'mu4' is acquired after lock 'mu3' \\(acquired at line 87\\) but is annotated otherwise" "" { target *-*-* } 88 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-50.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-50.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-50.C @@ -0,0 +1,22 @@ +// Test the support for allowing non-const but non-modifying methods to be +// protected by reader locks. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include <map> +#include "thread_annot_common.h" + +typedef std::map<int, int> MyMapType; + +Mutex mu; +MyMapType MyMap GUARDED_BY(mu); + +int foo(int key) { + ReaderMutexLock l(&mu); + MyMapType::const_iterator iter = MyMap.find(key); + if (iter != MyMap.end()) { + return iter->second; + } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-51.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-51.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-51.C @@ -0,0 +1,44 @@ +// Test the support for allowing non-const but non-modifying methods to be +// protected by reader locks. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + int GetVal1(int a) const { + return a + val1; + } + + int GetVal1(int a) { + return a + val1; + } + + int GetVal1(int a, float b) { + return a + b + val1; + } + + int GetVal2(int a) { + return a + val2; + } + + int GetVal2(float a) { + return val2; + } + + + private: + int val1; + int val2; +}; + +Mutex mu; +Foo foo GUARDED_BY(mu); + +int main() { + mu.ReaderLock(); + int x = foo.GetVal1(3); // should not warn + int y = foo.GetVal2(3); // { dg-warning "Writing to variable" } + mu.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-52.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-52.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-52.C @@ -0,0 +1,39 @@ +// Test the support for use of point_to_guarded{_by} on smart/scoped pointers. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +template<class T> +class scoped_ptr { + public: + typedef T element_type; + + explicit scoped_ptr(T * p = 0); + ~scoped_ptr(); + + void reset(T * p = 0); + + T & operator*() const; + T * operator->() const; + T * get() const; +}; + +class Foo { + public: + int x; +}; + +Mutex mu1, mu2; +scoped_ptr<int> a PT_GUARDED_BY(mu1); +scoped_ptr<Foo> b GUARDED_BY(mu2) PT_GUARDED_VAR; + +main() +{ + *a = 5; // { dg-warning "Access to memory location pointed to" } + a.reset(); + b->x = 3 + *a; // { dg-warning "Reading variable" } +} + +// { dg-warning "Access to memory location pointed to by variable 'b'" "" { target *-*-* } 35 } +// { dg-warning "Access to memory location pointed to by variable 'a'" "" { target *-*-* } 35 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-53.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-53.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-53.C @@ -0,0 +1,40 @@ +// Test the support for use of point_to_guarded{_by} on smart/scoped pointers. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +template<class T> +class scoped_ptr { + public: + typedef T element_type; + + explicit scoped_ptr(T * p = 0); + ~scoped_ptr(); + + void reset(T * p = 0); + + T & operator*() const; + T * operator->() const; + T * get() const; +}; + +class Foo { + public: + int x; +}; + +Mutex mu1, mu2; +scoped_ptr<int> a PT_GUARDED_BY(mu1); +scoped_ptr<Foo> b GUARDED_BY(mu2) PT_GUARDED_VAR; + +main() +{ + MutexLock l1(&mu1); + MutexLock l2(&mu2); + *a = 5; + a.reset(); + b->x = 3 + *a; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-54.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-54.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-54.C @@ -0,0 +1,35 @@ +// Test the handling of the annotations with function parameters. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Base { + private: + Mutex mu1_; + + public: + Mutex *mutable_mu() LOCK_RETURNED(mu1_) { return &mu1_; } +}; + +class Foo { + public: + Mutex mu2_; + void Test1(Mutex* mu) const EXCLUSIVE_LOCKS_REQUIRED(mu, mu2_); + void Test2(Mutex* mu) const LOCKS_EXCLUDED(mu); +}; + +class Bar : public Base { + private: + Foo foo_; + + public: + void Test3(); +}; + +void Bar::Test3() { + mutable_mu()->Lock(); + foo_.Test1(mutable_mu()); // { dg-warning "Calling function" } + foo_.Test2(mutable_mu()); // { dg-warning "Cannot call function" } + mutable_mu()->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-55.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-55.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-55.C @@ -0,0 +1,38 @@ +// Test the handling of the annotations with function parameters. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Base { + private: + Mutex mu1_; + + public: + Mutex *mutable_mu() LOCK_RETURNED(mu1_) { return &mu1_; } +}; + +class Foo { + public: + Mutex mu2_; + void Test1(Mutex* mu) const EXCLUSIVE_LOCKS_REQUIRED(mu, mu2_); + void Test2(Mutex* mu) const LOCKS_EXCLUDED(mu); +}; + +class Bar : public Base { + private: + Foo foo_; + + public: + void Test3(); +}; + +void Bar::Test3() { + MutexLock l(&foo_.mu2_); + mutable_mu()->Lock(); + foo_.Test1(mutable_mu()); + mutable_mu()->Unlock(); + foo_.Test2(mutable_mu()); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-56.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-56.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-56.C @@ -0,0 +1,36 @@ +// Test the handling of a method with lock annotations accessed through a +// smart/scoped pointer. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +template<class T> +class scoped_ptr { + public: + typedef T element_type; + + explicit scoped_ptr(T * p = 0); + ~scoped_ptr(); + + void reset(T * p = 0); + + T & operator*() const; + T * operator->() const; + T * get() const; +}; + +class LOCKABLE Foo { + public: + Mutex *mutex_; + int x; + int GetValue() EXCLUSIVE_LOCKS_REQUIRED(mutex); +}; + +scoped_ptr<Foo> b; + +main() +{ + int a; + a = b->GetValue(); // { dg-warning "Calling function 'GetValue' requires" } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-57.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-57.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-57.C @@ -0,0 +1,22 @@ +// Test handling of arguments passed to reference parameters. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +struct Foo { + int a; +}; + +void func1(Foo &f); + +void func2(Foo *f); + +Mutex mu; + +Foo foo GUARDED_BY(mu); + +main() { + func1(foo); // { dg-warning "Reading variable 'foo' requires lock" } + func2(&foo); // should not warn +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-58.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-58.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-58.C @@ -0,0 +1,32 @@ +// Test handling of arguments passed to reference parameters. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include <string> +#include "thread_annot_common.h" + +class Base { + public: + Base() {} + protected: + Mutex* mutex() const LOCK_RETURNED(mutex_) { return &mutex_; } + private: + mutable Mutex mutex_; +}; + +class Subclass : public Base { + public: + Subclass() {} + + void ClearValue() { SetValueLocked(0); } + std::string GetValue() const; + + private: + void SetValueLocked(std::string value) { value_ = value; } + + std::string value_ GUARDED_BY(mutex_); +}; + +std::string Subclass::GetValue() const { + return value_; // { dg-warning "Reading variable 'value_' requires lock" } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-59.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-59.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-59.C @@ -0,0 +1,34 @@ +// Test handling of additional (finer-grained) escape hatche attributes. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex *mu1, *mu2; + +struct Foo { + int a GUARDED_BY(mu1); +}; + +int x GUARDED_BY(mu1) = 1; +int y GUARDED_BY(mu2); + +main() { + int z; + Foo w; + ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN(); + y = x + 1; + x = y; + ANNOTATE_IGNORE_READS_AND_WRITES_END(); + + z = ANNOTATE_UNPROTECTED_READ(w.a) + 1; + if (z > 1) { + ANNOTATE_IGNORE_READS_BEGIN(); + z = x + 2; + } + else { + ANNOTATE_IGNORE_READS_BEGIN(); + z = x + 1; + } + z = y; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-6.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-6.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-6.C @@ -0,0 +1,92 @@ +// Test lock annotations +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; +Mutex mu2 ACQUIRED_AFTER(mu1); +Mutex mu4; +Mutex mu3 ACQUIRED_AFTER(mu2, mu4); + +int gx GUARDED_BY(mu1) = 3; +float gy GUARDED_BY(mu2) = 5.0; +int gz GUARDED_BY(mu4) = 2; +int *gp GUARDED_BY(mu3) PT_GUARDED_BY(mu1); + +class Foo { + private: + Mutex mu_ ACQUIRED_AFTER(mu3); + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + + public: + + Foo() : a_(1) { b_ = 5.0; } + + ~Foo() { a_ = 0; b_ = 0.0; } + + int incrementA(int i) LOCKS_EXCLUDED(mu_) + { + int res; + mu_.Lock(); + a_ += i; + res = a_; + mu_.Unlock(); + return res; + } + + float updateB(float f) LOCKS_EXCLUDED(mu_) SHARED_LOCKS_REQUIRED(mu1, mu3) + EXCLUSIVE_LOCKS_REQUIRED(mu2) + { + float res; + + incrementA(*gp); + + mu_.Lock(); + if (gx > 2) + b_ -= f; + else + b_ -= ++gy; + res = b_; + mu_.Unlock(); + return res; + } +}; + +void func1(void) LOCKS_EXCLUDED(mu1, mu2, mu3); + +void func1(void) +{ + int la; + float *p PT_GUARDED_BY(mu2) = &gy; + Foo foo; + + + mu1.ReaderLock(); + + if (gx > 3) { + la = gx + 1; + } + else { + mu2.Lock(); + mu3.ReaderLock(); + + *p = foo.updateB(gy); + + mu3.Unlock(); + mu2.Unlock(); + } + + mu1.Unlock(); + + mu4.Lock(); + mu3.Lock(); + + gp = &gx; + gz += 2; + + mu3.Unlock(); + mu4.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-60.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-60.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-60.C @@ -0,0 +1,37 @@ +// Test handling of additional (finer-grained) escape hatche attributes. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex *mu1, *mu2; + +struct Foo { + int a GUARDED_BY(mu1); +}; + +int x GUARDED_BY(mu1) = 1; +int y GUARDED_BY(mu2); + +main() { + int z; + Foo w; + ANNOTATE_IGNORE_READS_BEGIN(); + y = x + 1; // { dg-warning "Writing to variable 'y' requires lock" } + x = y; // { dg-warning "Writing to variable 'x' requires lock" } + ANNOTATE_IGNORE_READS_END(); + ANNOTATE_IGNORE_WRITES_BEGIN(); + y = x + 1; // { dg-warning "Reading variable 'x' requires lock" } + x = y; // { dg-warning "Reading variable 'y' requires lock" } + ANNOTATE_IGNORE_WRITES_END(); + + z = w.a + 1; // { dg-warning "Reading variable 'w.a' requires lock" } + if (z > 1) { + z = x + 2; // { dg-warning "Reading variable 'x' requires lock" } + } + else { + ANNOTATE_IGNORE_READS_BEGIN(); + z = x + 1; + } + z = y; // { dg-warning "Reading variable 'y' requires lock" } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-61.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-61.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-61.C @@ -0,0 +1,15 @@ +// Test the fix for a bug introduced by the support of pass-by-reference +// paramters. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +struct Foo { Foo & operator<< (bool) {} }; +struct Bar { Foo & func () {} }; +struct Bas { void operator& (Foo &) {} }; +void mumble() +{ + Bas() & Bar().func() << "" << ""; + Bas() & Bar().func() << ""; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-62.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-62.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-62.C @@ -0,0 +1,18 @@ +// Test the support for allowing non-const but non-modifying overloaded +// operator to be protected by reader locks. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include <vector> +#include "thread_annot_common.h" + +Mutex mu; + +std::vector<int> counts GUARDED_BY(mu); + +int foo(int key) { + ReaderMutexLock l(&mu); + return counts[key]; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-63.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-63.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-63.C @@ -0,0 +1,60 @@ +// Test the support for users to specify in the annotations a lock that is a +// class/struct member of a function parameter. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex *mu1_; + int data_ GUARDED_BY(mu1_); +}; + +class Bar { + public: + Foo *foo_; + void MyLock(Foo *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_); + void MyUnlock(Foo *f) UNLOCK_FUNCTION(f->mu1_); + void func2(Foo *f) SHARED_LOCKS_REQUIRED(f->mu1_); + void func3(); +}; + +class SCOPED_LOCKABLE MyMutexLock { + public: + MyMutexLock(Foo *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_) + : mu_(f->mu1_) { + this->mu_->Lock(); + } + + ~MyMutexLock() UNLOCK_FUNCTION(){ this->mu_->Unlock(); } + + private: + Mutex *const mu_; +}; + +Mutex *mu2; + +void func1(Bar *bar, Mutex *mu) EXCLUSIVE_LOCKS_REQUIRED(bar->foo_->mu1_, mu) { + bar->foo_->data_ = 5; +} + +void Bar::func3() { + MyMutexLock l(foo_); + func2(foo_); +} + +main() { + Bar *b; + + MutexLock l(mu2); + b->MyLock(b->foo_); + func1(b, mu2); + b->MyUnlock(b->foo_); + b->func3(); + b->foo_->mu1_->Lock(); + func1(b, mu2); + b->foo_->mu1_->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-64.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-64.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-64.C @@ -0,0 +1,53 @@ +// Test the support for users to specify in the annotations a lock that is a +// class/struct member of a function parameter. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex *mu1_; + int data_ GUARDED_BY(mu1_); +}; + +class Bar { + public: + Foo *foo_; + void MyLock(Foo *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_); + void MyUnlock(Foo *f) UNLOCK_FUNCTION(f->mu1_); + void func2(Foo *f) SHARED_LOCKS_REQUIRED(f->mu1_); + void func3(); +}; + +class SCOPED_LOCKABLE MyMutexLock { + public: + MyMutexLock(Foo *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_) + : mu_(f->mu1_) { + this->mu_->Lock(); + } + + ~MyMutexLock() UNLOCK_FUNCTION(){ this->mu_->Unlock(); } + + private: + Mutex *const mu_; +}; + +Mutex *mu2; + +void func1(Bar *bar, Mutex *mu) EXCLUSIVE_LOCKS_REQUIRED(bar->foo_->mu1_, mu) { + bar->foo_->data_ = 5; +} + +void Bar::func3() { + func2(foo_); // { dg-warning "Calling function 'func2' requires lock" } +} + +main() { + Bar *b; + + MutexLock l(mu2); + func1(b, mu2); // { dg-warning "Calling function 'func1' requires lock" } + b->func3(); + func1(b, mu2); // { dg-warning "Calling function 'func1' requires lock" } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-65.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-65.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-65.C @@ -0,0 +1,30 @@ +// Test the fix for a bug in the support of allowing reader locks for +// non-const, non-modifying overload functions. (We didn't handle the builtin +// properly.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +enum MyFlags { + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine +}; + +inline MyFlags +operator|(MyFlags a, MyFlags b) +{ + return MyFlags(static_cast<int>(a) | static_cast<int>(b)); +} + +inline MyFlags& +operator|=(MyFlags& a, MyFlags b) +{ + return a = a | b; +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-66.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-66.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-66.C @@ -0,0 +1,35 @@ +// Test annotations on out-of-line definitions of member functions where the +// annotations refer to locks that are also data members in the class. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu; + +class Foo { + public: + int method1(int i); + int data GUARDED_BY(mu1); + Mutex *mu1; + Mutex *mu2; +}; + +int Foo::method1(int i) SHARED_LOCKS_REQUIRED(mu1, mu, mu2) +{ + return data + i; +} + +main() +{ + Foo a; + + MutexLock l(a.mu2); + a.mu1->Lock(); + mu.Lock(); + a.method1(1); + mu.Unlock(); + a.mu1->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-67.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-67.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-67.C @@ -0,0 +1,32 @@ +// Test annotations on out-of-line definitions of member functions where the +// annotations refer to locks that are also data members in the class. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu; + +class Foo { + public: + int method1(int i); + int data GUARDED_BY(mu1); + Mutex *mu1; + Mutex *mu2; +}; + +int Foo::method1(int i) SHARED_LOCKS_REQUIRED(mu1, mu, mu2, mu3) +{ + return data + i; +} + +main() +{ + Foo a; + + a.method1(1); // { dg-warning "Calling function 'method1' requires lock" } +} + +// { dg-warning "Calling function 'method1' requires lock 'mu'" { target *-*-* } 27 } +// { dg-warning "Calling function 'method1' requires lock 'a.mu2'" { target *-*-* } 27 } +// { dg-warning "Calling function 'method1' requires lock 'mu3'" { target *-*-* } 27 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-68.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-68.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-68.C @@ -0,0 +1,34 @@ +// Test a fix to a bug in the delayed name binding with nested template +// instantiation. We use a stack to make sure a name is not resolved to an +// inner context. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +template <typename T> +class Bar { + Mutex mu_; +}; + +template <typename T> +class Foo { + public: + void func(T x) { + MutexLock l(&mu_); + count_ = x; + } + + private: + T count_ GUARDED_BY(mu_); + Bar<T> bar_; + Mutex mu_; +}; + +int main() +{ + Foo<int> *foo; + foo->func(5); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-69.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-69.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-69.C @@ -0,0 +1,31 @@ +// Test a fix to a bug in the delayed name binding with nested template +// instantiation. We use a stack to make sure a name is not resolved to an +// inner context. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +template <typename T> +class Bar { + Mutex mu_; +}; + +template <typename T> +class Foo { + public: + void func(T x) { + count_ = x; // { dg-warning "Writing to variable 'count_' requires lock" } + } + + private: + T count_ GUARDED_BY(mu_); + Bar<T> bar_; + Mutex mu_; +}; + +int main() +{ + Foo<int> *foo; + foo->func(5); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-7.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-7.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-7.C @@ -0,0 +1,76 @@ +// Test lock annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; + +class Foo { + public: + Mutex mu_ ACQUIRED_AFTER(mu1); + static Mutex mu2_[2]; + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + void foo() const SHARED_LOCKS_REQUIRED(mu_); + void bar() EXCLUSIVE_LOCKS_REQUIRED((mu2_[0])); + inline Mutex *get_lock() LOCK_RETURNED(mu_) { return &mu_; } + inline Mutex *get_lock2() { return &mu_; } + static bool compare(Foo& x, Foo& y) + { + if (x.a_ > y.a_ && x.b_ > y.b_) { // { dg-warning "Reading variable 'x->a_' requires lock 'x->mu_'" } + return true; + } + else { + return false; + } + } +}; + +class Bar { + public: + Foo foo[3]; + Foo *get_foo() __attribute__((pure)) { return &foo[2]; } +}; + +class Cat { + public: + Bar *bar; +}; + +Foo *q GUARDED_BY(mu1); +Foo y; +Cat w[3]; +Bar *p GUARDED_BY((y.mu_)); +int gx GUARDED_BY((w[1].bar->foo[2].mu_)); + + +main() +{ + Foo x; + Foo::compare(x, y); + mu1.Lock(); + q->mu_.Lock(); + q->mu2_[0].Lock(); + q->foo(); + q->a_ = 5; + q->bar(); + q->mu2_[0].Unlock(); + q->mu_.Unlock(); + mu1.Unlock(); + w[2].bar->foo[1].a_ = 3; // { dg-warning "Writing to variable 'w\\\[2\\\].bar->foo\\\[1\\\].a_' requires lock 'w\\\[2\\\].bar->foo\\\[1\\\].mu_'" } + w[2].bar->foo[1].bar(); // { dg-warning " Calling function 'bar' requires lock 'mu2_\\\[0\\\]'" } + w[1].bar->foo[2].get_lock()->Lock(); + gx = 7; + y.mu_.Lock(); + p->foo[2].get_lock()->Lock(); + p->foo[2].b_ += 1; + y.a_ = 2; + y.mu_.Unlock(); +} + +// { dg-warning "Reading variable 'y->a_' requires lock 'y->mu_'" "" { target *-*-* } 21 } +// { dg-warning "Reading variable 'x->b_' requires lock 'x->mu_'" "" { target *-*-* } 21 } +// { dg-warning "Reading variable 'y->b_' requires lock 'y->mu_'" "" { target *-*-* } 21 } +// { dg-warning "Lock 'w\\\[1\\\].bar->foo\\\[2\\\].mu_' \\(acquired at line 63\\) is not released at the end of function 'main'" "" { target *-*-* } 63 } +// { dg-warning "Lock 'p->foo\\\[2\\\].mu_' \\(acquired at line 66\\) is not released at the end of function 'main'" "" { target *-*-* } 66 } diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-70.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-70.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-70.C @@ -0,0 +1,38 @@ +// Test a fix to a bug when handling calls to virtual functions that are +// annotated with LOCK/UNLOCK_FUNCTION. More specifically, the bug happens +// when we tried to assert the function decl of a gimple call statement +// returned by gimple_call_fndecl is non-NULL, which is not true when the call +// is a virtual function call. Instead, we should either get the function decl +// through the reference object, or (as is the fix) simply pass the function +// decl that we have extracted earlier all the way to +// handle_lock_primitive_attrs where the assertion fails. +// +// This is a good test case. (i.e. There should be no error/warning/ICE +// triggered.) +// +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Base { + protected: + virtual void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_.Lock(); } + virtual void Unlock() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); } + Mutex mu_; +}; + +class Child: public Base { + int a; + public: + void func1() { + Lock(); + a += 1; + Unlock(); + } +}; + +main() { + Child c; + c.func1(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-71.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-71.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-71.C @@ -0,0 +1,63 @@ +// Test the support for users to specify in the annotations a lock that is a +// class/struct member of a function parameter in templates. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex *mu1_; + int data_ GUARDED_BY(mu1_); +}; + +template<typename T> +class Bar { + public: + T *foo_; + void MyLock(T *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_); + void MyUnlock(T *f) UNLOCK_FUNCTION(f->mu1_); + void func2(T *f) SHARED_LOCKS_REQUIRED(f->mu1_); + void func3(); +}; + +class SCOPED_LOCKABLE MyMutexLock { + public: + MyMutexLock(Foo *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_) + : mu_(f->mu1_) { + this->mu_->Lock(); + } + + ~MyMutexLock() UNLOCK_FUNCTION(){ this->mu_->Unlock(); } + + private: + Mutex *const mu_; +}; + +Mutex *mu2; + +void func1(Bar<Foo> *bar, Mutex *mu) + EXCLUSIVE_LOCKS_REQUIRED(bar->foo_->mu1_, mu) { + bar->foo_->data_ = 5; +} + +template<typename T> +void Bar<T>::func3() { + MyMutexLock l(foo_); + func2(foo_); +} + +main() { + Bar<Foo> *b; + + MutexLock l(mu2); + b->MyLock(b->foo_); + func1(b, mu2); + b->MyUnlock(b->foo_); + b->func3(); + b->foo_->mu1_->Lock(); + func1(b, mu2); + b->foo_->mu1_->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-72.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-72.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-72.C @@ -0,0 +1,56 @@ +// Test the support for users to specify in the annotations a lock that is a +// class/struct member of a function parameter in templates. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex *mu1_; + int data_ GUARDED_BY(mu1_); +}; + +template<typename T> +class Bar { + public: + T *foo_; + void MyLock(T *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_); + void MyUnlock(T *f) UNLOCK_FUNCTION(f->mu1_); + void func2(T *f) SHARED_LOCKS_REQUIRED(f->mu1_); + void func3(); +}; + +class SCOPED_LOCKABLE MyMutexLock { + public: + MyMutexLock(Foo *f) EXCLUSIVE_LOCK_FUNCTION(f->mu1_) + : mu_(f->mu1_) { + this->mu_->Lock(); + } + + ~MyMutexLock() UNLOCK_FUNCTION(){ this->mu_->Unlock(); } + + private: + Mutex *const mu_; +}; + +Mutex *mu2; + +void func1(Bar<Foo> *bar, Mutex *mu) + EXCLUSIVE_LOCKS_REQUIRED(bar->foo_->mu1_, mu) { + bar->foo_->data_ = 5; +} + +template<typename T> +void Bar<T>::func3() { + func2(foo_); // { dg-warning "Calling function 'func2' requires lock" } +} + +main() { + Bar<Foo> *b; + + MutexLock l(mu2); + func1(b, mu2); // { dg-warning "Calling function 'func1' requires lock" } + b->func3(); + func1(b, mu2); // { dg-warning "Calling function 'func1' requires lock" } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-73.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-73.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-73.C @@ -0,0 +1,36 @@ +// Test the support for users to specify in the annotations a lock that is a +// function parameter in templates. +// This is a good test case. (i.e. There should be no warning emitted by the +// compiler.) +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +template<typename T> +class Foo { + public: + T func1(T a, Mutex *mutex) EXCLUSIVE_LOCKS_REQUIRED(mutex); +}; + +template<typename T> +T Foo<T>::func1(T a, Mutex *mutex) { + if (a > 1) { + mutex->Unlock(); + a = 0; + mutex->Lock(); + } + else { + a += 1; + } + return a; +} + +Mutex *mu; + +main() { + Foo<int> foo; + mu->Lock(); + foo.func1(20, mu); + mu->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-74.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-74.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-74.C @@ -0,0 +1,38 @@ +// Test a bug fix that resolves a compiler complaint on using a private member +// of a friend class in the lock annotations. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +namespace ns { +class Bar; +} + +class Foo { + private: + Mutex *mymu_; + friend class ns::Bar; +}; + +namespace ns { + +class Bar { + Foo *foo; + int data; + + public: + void func(Foo *fu); +}; + +void Bar::func(Foo *fu) EXCLUSIVE_LOCKS_REQUIRED(fu->mymu_) { + data = 5; +} + +void Test() { + Foo foo; + Bar bar; + bar.func(&foo); // { dg-warning "requires lock" } +} + +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-75.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-75.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-75.C @@ -0,0 +1,27 @@ +// Test that the compiler tolerates the use of incomplete type in the lock +// annotations (i.e. doesn't emit a error on invalid use of incomplete type). +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Foo; + +class Bar { + public: + void func2(Foo& old_foo) EXCLUSIVE_LOCKS_REQUIRED(old_foo.mu1_); + Mutex *mu2_; +}; + +class Foo { + public: + void func1(Bar& old_bar) EXCLUSIVE_LOCKS_REQUIRED(old_bar.mu2_); + Mutex *mu1_; +}; + +void Test() { + Foo foo1, foo2; + Bar bar1, bar2; + foo2.func1(bar1); // { dg-warning "requires a lock" } + bar2.func2(foo1); // { dg-warning "requires a lock" } +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-76.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-76.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-76.C @@ -0,0 +1,36 @@ +// Test that when disabling -Wthread-attr-bind-param, the compiler will not +// try to bind the names (used in lock attributes) to function parameters. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wno-thread-attr-bind-param -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex *mu1_; +}; + +class Bar { + public: + int func1(int a, Foo *foo) EXCLUSIVE_LOCKS_REQUIRED(foo->mu1_) { + if (a > 1) { + foo->mu1_->Unlock(); + a = 0; + foo->mu1_->Lock(); // { dg-warning "not released" } + } + else { + a += 1; + } + return a; + } +}; + +Mutex *mu; +Foo *fu; + +void Test1() { + Bar bar; + mu->Lock(); + bar.func1(20, fu); // no warning here because of -Wno-thread-attr-bind-param + mu->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-77.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-77.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-77.C @@ -0,0 +1,37 @@ +// Test that when -Wthread-attr-bind-param is enabled (which is the default), +// the compiler will try to bind the names (used in lock attributes) to +// function parameters. +// { dg-do compile } +// { dg-options "-Wthread-safety -Wthread-attr-bind-param -O" } + +#include "thread_annot_common.h" + +class Foo { + public: + Mutex *mu1_; +}; + +class Bar { + public: + int func1(int a, Foo *foo) EXCLUSIVE_LOCKS_REQUIRED(foo->mu1_) { + if (a > 1) { + foo->mu1_->Unlock(); + a = 0; + foo->mu1_->Lock(); + } + else { + a += 1; + } + return a; + } +}; + +Mutex *mu; +Foo *fu; + +void Test1() { + Bar bar; + mu->Lock(); + bar.func1(20, fu); // { dg-warning "Calling function 'func1' requires lock" } + mu->Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-8.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-8.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-8.C @@ -0,0 +1,83 @@ +// Test lock annotations +// This is a "good" test case that should not incur any thread safety warning. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +Mutex mu1; + +class Foo { + public: + Mutex mu_ ACQUIRED_AFTER(mu1); + static Mutex mu2_[7] ACQUIRED_AFTER(mu_); + int a_ GUARDED_BY(mu_); + float b_ GUARDED_BY(mu_); + void foo() const SHARED_LOCKS_REQUIRED(mu_); + void bar() EXCLUSIVE_LOCKS_REQUIRED((mu2_[0])); + inline Mutex *get_lock() LOCK_RETURNED(mu_) { return &mu_; } + inline Mutex *get_lock2() { return &mu_; } + static bool compare(Foo& x, Foo& y) + { + x.mu_.Lock(); + y.get_lock()->Lock(); + if (x.a_ > y.a_ && x.b_ > y.b_) { + y.mu_.Unlock(); + x.get_lock()->Unlock(); + return true; + } + else { + y.get_lock()->Unlock(); + x.mu_.Unlock(); + return false; + } + } +}; + +class Bar { + public: + Foo foo[3]; + Foo *get_foo() __attribute__((pure)) { return &foo[2]; } +}; + +class Cat { + public: + Bar *bar; +}; + +Foo *q GUARDED_BY(mu1); +Foo y; +Cat w[3]; +Bar *p GUARDED_BY((y.mu_)); +int gx GUARDED_BY((w[1].bar->foo[2].mu_)); + + +main() +{ + Foo x; + Foo::compare(x, y); + mu1.Lock(); + q->mu_.Lock(); + q->mu2_[0].Lock(); + q->foo(); + q->a_ = 5; + q->bar(); + q->mu2_[0].Unlock(); + q->mu_.Unlock(); + mu1.Unlock(); + w[2].bar->foo[1].mu_.Lock(); + w[2].bar->foo[1].a_ = 3; + Foo::mu2_[0].Lock(); + w[2].bar->foo[1].bar(); + Foo::mu2_[0].Unlock(); + w[2].bar->foo[1].mu_.Unlock(); + w[1].bar->foo[2].get_lock()->Lock(); + gx = 7; + w[1].bar->foo[2].get_lock()->Unlock(); + y.mu_.Lock(); + p->foo[2].get_lock()->Lock(); + p->foo[2].b_ += 1; + p->foo[2].mu_.Unlock(); + y.a_ = 2; + y.mu_.Unlock(); +} diff --git a/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-9.C b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-9.C new file mode 100644 --- /dev/null +++ b/gcc/testsuite/g++.dg/thread-ann/thread_annot_lock-9.C @@ -0,0 +1,33 @@ +// Test lock annotations +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common.h" + +class Bar { + private: + Mutex mu_; + void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_.Lock(); } + void Unlock() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); } + float foo GUARDED_BY(mu_); + public: + float get_foo() { + float ret; + ret = foo; // { dg-warning "Reading variable 'foo' requires lock 'mu_'" } + Unlock(); // { dg-warning "Try to unlock 'mu_' that was not acquired" } + return ret; + } +}; + +int pthread_mutex_lock(int i, int j, Mutex *mutex, int k) EXCLUSIVE_LOCK_FUNCTION(3); +int pthread_mutex_unlock(int i, int j, Mutex *mutex, int k) UNLOCK_FUNCTION(3); + +Bar *x; +Mutex fastmutex; +float val GUARDED_BY(fastmutex); + +main() +{ + val = x->get_foo(); // { dg-warning "Writing to variable 'val' requires lock 'fastmutex'" } + pthread_mutex_unlock(4, 5, &fastmutex, 6); // { dg-warning "Try to unlock 'fastmutex' that was not acquired" } +} diff --git a/gcc/testsuite/gcc.dg/thread_annot_common_c.h b/gcc/testsuite/gcc.dg/thread_annot_common_c.h new file mode 100644 --- /dev/null +++ b/gcc/testsuite/gcc.dg/thread_annot_common_c.h @@ -0,0 +1,56 @@ + +#ifndef TEST_COMMON_C_H +#define TEST_COMMON_C_H + + +#if defined(__GNUC__) && defined(__SUPPORT_TS_ANNOTATION__) + +#define LOCKABLE __attribute__ ((lockable)) +#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable)) +#define GUARDED_BY(x) __attribute__ ((guarded_by(x))) +#define GUARDED_VAR __attribute__ ((guarded)) +#define PT_GUARDED_BY(x) __attribute__ ((point_to_guarded_by(x))) +#define PT_GUARDED_VAR __attribute__ ((point_to_guarded)) +#define ACQUIRED_AFTER(...) __attribute__ ((acquired_after(__VA_ARGS__))) +#define ACQUIRED_BEFORE(...) __attribute__ ((acquired_before(__VA_ARGS__))) +#define EXCLUSIVE_LOCK_FUNCTION(...) __attribute__ ((exclusive_lock(__VA_ARGS__))) +#define SHARED_LOCK_FUNCTION(...) __attribute__ ((shared_lock(__VA_ARGS__))) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) __attribute__ ((exclusive_trylock(__VA_ARGS__))) +#define SHARED_TRYLOCK_FUNCTION(...) __attribute__ ((shared_trylock(__VA_ARGS__))) +#define UNLOCK_FUNCTION(...) __attribute__ ((unlock(__VA_ARGS__))) +#define LOCK_RETURNED(x) __attribute__ ((lock_returned(x))) +#define LOCKS_EXCLUDED(...) __attribute__ ((locks_excluded(__VA_ARGS__))) +#define EXCLUSIVE_LOCKS_REQUIRED(...) \ + __attribute__ ((exclusive_locks_required(__VA_ARGS__))) +#define SHARED_LOCKS_REQUIRED(...) \ + __attribute__ ((shared_locks_required(__VA_ARGS__))) +#define NO_THREAD_SAFETY_ANALYSIS __attribute__ ((no_thread_safety_analysis)) + +#else + +#define LOCKABLE +#define SCOPED_LOCKABLE +#define GUARDED_BY(x) +#define GUARDED_VAR +#define PT_GUARDED_BY(x) +#define PT_GUARDED_VAR +#define ACQUIRED_AFTER(...) +#define ACQUIRED_BEFORE(...) +#define EXCLUSIVE_LOCK_FUNCTION(...) +#define SHARED_LOCK_FUNCTION(...) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) +#define SHARED_TRYLOCK_FUNCTION(...) +#define UNLOCK_FUNCTION(...) +#define LOCK_RETURNED(x) +#define LOCKS_EXCLUDED(...) +#define EXCLUSIVE_LOCKS_REQUIRED(...) +#define SHARED_LOCKS_REQUIRED(...) +#define NO_THREAD_SAFETY_ANALYSIS + +#endif // defined(__GNUC__) && defined(SUPPORT_TS_ANNOTATION) + +struct LOCKABLE Mutex { + int mu_; +}; + +#endif // TEST_COMMON_C_H diff --git a/gcc/testsuite/gcc.dg/thread_annot_lock-23.c b/gcc/testsuite/gcc.dg/thread_annot_lock-23.c new file mode 100644 --- /dev/null +++ b/gcc/testsuite/gcc.dg/thread_annot_lock-23.c @@ -0,0 +1,38 @@ +/* Test guarded_by/pt_guarded_by annotations with unsupported or unrecognized + lock names/expressions. */ +/* { dg-do compile } */ +/* { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } */ + +#include "thread_annot_common_c.h" + +int pthread_mutex_lock(struct Mutex *mu, int i) EXCLUSIVE_LOCK_FUNCTION(1); +int pthread_mutex_unlock(struct Mutex *mu) UNLOCK_FUNCTION(1); +int pthread_mutex_trylock() EXCLUSIVE_TRYLOCK_FUNCTION(0, t->mu1); /* { dg-warning "Unsupported argument of 'exclusive_trylock' attribute ignored" } */ + +struct Bar { + float x_ GUARDED_BY(((mu1_))); /* { dg-warning "'guarded_by' attribute downgraded to 'guarded'" } */ + struct Mutex mu1_ ACQUIRED_AFTER(mu2); + float a_ GUARDED_BY(foo_->mu_); /* { dg-warning "'guarded_by' attribute downgraded to 'guarded'" } */ +}; + +int p GUARDED_BY(a->mu); /* { dg-warning "'guarded_by' attribute downgraded to 'guarded'" } */ +int *r PT_GUARDED_BY(f1.mu_); /* { dg-warning "'point_to_guarded_by' attribute downgraded to 'point_to_guarded'" } */ + +struct Bar *b1; +struct Mutex *mu3; + +int foo(int i, int j, struct Mutex *mu) EXCLUSIVE_LOCKS_REQUIRED(3) +{ + int *q PT_GUARDED_BY(mu); + int t = *q; + *q = 4; + return t; +} + +int main() +{ + pthread_mutex_lock(&b1->mu1_, 2); + p = *r + 5; + b1->x_ = 3; + pthread_mutex_unlock(&b1->mu1_); +} diff --git a/gcc/testsuite/gcc.dg/thread_annot_lock-24.c b/gcc/testsuite/gcc.dg/thread_annot_lock-24.c new file mode 100644 --- /dev/null +++ b/gcc/testsuite/gcc.dg/thread_annot_lock-24.c @@ -0,0 +1,44 @@ +/* Test lock/trylock/unlock annotations with unsupported or unrecognized lock + names/expressions. */ +/* { dg-do compile } */ +/* { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } */ + +#include "thread_annot_common_c.h" + +int pthread_mutex_lock(int i) EXCLUSIVE_LOCK_FUNCTION(((&f)->mu)); /* { dg-warning "Unsupported argument of 'exclusive_lock' attribute ignored" } */ +int pthread_mutex_unlock() UNLOCK_FUNCTION(f[0].mu); /* { dg-warning "Unsupported argument of 'unlock' attribute ignored" } */ +int pthread_mutex_trylock() EXCLUSIVE_TRYLOCK_FUNCTION(0, t->mu1); /* { dg-warning "Unsupported argument of 'exclusive_trylock' attribute ignored" } */ + +struct Mutex *mu3; +int p GUARDED_BY(mu3); +int r GUARDED_BY(mu3); + +void my_lock() EXCLUSIVE_LOCK_FUNCTION(mu3); +void my_unlock() UNLOCK_FUNCTION(mu3); + +static int foo(int i, int j, struct Mutex *mu) EXCLUSIVE_LOCKS_REQUIRED(3) +{ + int *q PT_GUARDED_BY(mu); + int t = *q; + *q = 4; + pthread_mutex_unlock(); + /* Since the annotation on pthread_mutex_unlock contains an + unrecognized lock name, the analysis would conservatively disable + the check for mismatched lock acquire/release. Therefore even though + my_unlock is trying to release mu3 which is never acquired, we don't + emit a warning. */ + my_unlock(); + return t; +} + +int main() +{ + my_lock(); + foo(2, 3, mu3); + my_unlock(); + pthread_mutex_lock(2); + p = r + 5; + /* Because the annotation on pthread_mutex_lock contains an unrecognized + lock, we don't emit a warning even though there is no corresponding + unlock call. */ +} diff --git a/gcc/testsuite/gcc.dg/thread_annot_lock-25.c b/gcc/testsuite/gcc.dg/thread_annot_lock-25.c new file mode 100644 --- /dev/null +++ b/gcc/testsuite/gcc.dg/thread_annot_lock-25.c @@ -0,0 +1,46 @@ +/* Test function lock requirement annotations with unsupported or unrecognized + lock names/expressions. */ +/* { dg-do compile } */ +/* { dg-options "-Wthread-safety -Wthread-unsupported-lock-name -O" } */ + +#include "thread_annot_common_c.h" + +int pthread_mutex_lock(int i) EXCLUSIVE_LOCK_FUNCTION(((&f)->mu)); /* { dg-warning "Unsupported argument of 'exclusive_lock' attribute ignored" } */ +int pthread_mutex_unlock() UNLOCK_FUNCTION(f[0].mu); /* { dg-warning "Unsupported argument of 'unlock' attribute ignored" } */ +int pthread_mutex_trylock() EXCLUSIVE_TRYLOCK_FUNCTION(0, t->mu1); /* { dg-warning "Unsupported argument of 'exclusive_trylock' attribute ignored" } */ + +struct Mutex *mu3; +int p GUARDED_BY(mu3); +int r GUARDED_BY(mu3); + +void my_lock() EXCLUSIVE_LOCK_FUNCTION(mu3); +void my_unlock() UNLOCK_FUNCTION(mu3); + +extern void foo(int i, int j, struct Mutex *mu) EXCLUSIVE_LOCKS_REQUIRED(3); + +void foo(int i, int j, struct Mutex *mu) LOCKS_EXCLUDED(t->mu3) +{ /* { dg-warning "Unsupported argument of 'locks_excluded' attribute ignored" } */ + int *q PT_GUARDED_BY(mu); + my_lock(); + p = *q; + *q = 4; + my_unlock(); +} + +int bar() SHARED_LOCKS_REQUIRED(y->mu); /* { dg-warning "Unsupported argument of 'shared_locks_required' attribute ignored" } */ + +int bar() +{ + int t = r; + return t; +} + +main() +{ + foo(2, 3, mu3); /* { dg-warning "Calling function 'foo' requires lock 'mu3'" } */ + my_lock(); + foo(2, 3, mu3); + bar(); + my_unlock(); + bar(); /* { dg-warning "Calling function 'bar' requires a lock" } */ +} diff --git a/gcc/testsuite/gcc.dg/thread_annot_lock-26.c b/gcc/testsuite/gcc.dg/thread_annot_lock-26.c new file mode 100644 --- /dev/null +++ b/gcc/testsuite/gcc.dg/thread_annot_lock-26.c @@ -0,0 +1,27 @@ +/* Test lock annotations applied to function definitions. */ +/* { dg-do compile } */ +/* { dg-options "-Wthread-safety -O" } */ + +#include "thread_annot_common_c.h" + +struct Mutex mu1; +struct Mutex mu2 ACQUIRED_AFTER(mu1); + +static int foo(int i) EXCLUSIVE_LOCKS_REQUIRED(mu2); + +int bar(int i) LOCKS_EXCLUDED(mu1) +{ + return i; +} + +static int foo(int i) SHARED_LOCKS_REQUIRED(mu1) +{ + return bar(i); /* { dg-warning "Cannot call function 'bar' with lock 'mu1' held" } */ +} + +main() +{ + foo(2); /* { dg-warning "Calling function 'foo' requires lock 'mu2'" } */ +} + +/* { dg-warning "Calling function 'foo' requires lock 'mu1'" "" { target *-*-* } 24 } */ diff --git a/gcc/testsuite/gcc.dg/thread_annot_lock-27.c b/gcc/testsuite/gcc.dg/thread_annot_lock-27.c new file mode 100644 --- /dev/null +++ b/gcc/testsuite/gcc.dg/thread_annot_lock-27.c @@ -0,0 +1,37 @@ +/* Test lock annotations applied to function definitions. This is a "good" + test that should not incur any compilation warnings. */ +/* { dg-do compile } */ +/* { dg-options "-Wthread-safety -O" } */ + +#include "thread_annot_common_c.h" + +int pthread_mutex_lock(struct Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(1); +int pthread_mutex_unlock(struct Mutex *mu) UNLOCK_FUNCTION(1); + +struct Mutex mu1; +struct Mutex mu2 ACQUIRED_AFTER(mu1); + +static int foo(int i) EXCLUSIVE_LOCKS_REQUIRED(mu2); + +int bar(int i) LOCKS_EXCLUDED(mu2) +{ + return i; +} + +static int foo(int i) SHARED_LOCKS_REQUIRED(mu1) +{ + int result; + pthread_mutex_unlock(&mu2); + result = bar(i); + pthread_mutex_lock(&mu2); + return result; +} + +main() +{ + pthread_mutex_lock(&mu1); + pthread_mutex_lock(&mu2); + foo(2); + pthread_mutex_unlock(&mu2); + pthread_mutex_unlock(&mu1); +} diff --git a/gcc/testsuite/gcc.dg/thread_annot_lock-42.c b/gcc/testsuite/gcc.dg/thread_annot_lock-42.c new file mode 100644 --- /dev/null +++ b/gcc/testsuite/gcc.dg/thread_annot_lock-42.c @@ -0,0 +1,31 @@ +// Test support of multiple lock attributes of the same kind on a decl. +// { dg-do compile } +// { dg-options "-Wthread-safety -O" } + +#include "thread_annot_common_c.h" + +struct Mutex mu1, mu2, mu3; + +int x GUARDED_BY(mu1) GUARDED_BY(mu3); // { dg-warning "ignored" } +int y; + +void f2() LOCKS_EXCLUDED(mu1) LOCKS_EXCLUDED(mu2) LOCKS_EXCLUDED(mu3); + +void f2() +{ + y = 2; +} + +void f1() EXCLUSIVE_LOCKS_REQUIRED(mu2) EXCLUSIVE_LOCKS_REQUIRED(mu1) +{ + x = 5; + f2(); // { dg-warning "Cannot call function 'f2' with lock 'mu1' held" } +} + +void func() +{ + f1(); // { dg-warning "Calling function 'f1' requires lock 'mu2'" } +} + +// { dg-warning "Cannot call function 'f2' with lock 'mu2' held" "" { target *-*-* } 22 } +// { dg-warning "Calling function 'f1' requires lock 'mu1'" "" { target *-*-* } 27 } diff --git a/gcc/timevar.def b/gcc/timevar.def --- a/gcc/timevar.def +++ b/gcc/timevar.def @@ -234,6 +234,7 @@ DEFTIMEVAR (TV_VAR_TRACKING_DATAFLOW , "var-tracking dataflow") DEFTIMEVAR (TV_VAR_TRACKING_EMIT , "var-tracking emit") DEFTIMEVAR (TV_TREE_IFCOMBINE , "tree if-combine") DEFTIMEVAR (TV_TREE_UNINIT , "uninit var analysis") +DEFTIMEVAR (TV_TREE_THREADSAFE , "thread safety analysis") DEFTIMEVAR (TV_PLUGIN_INIT , "plugin initialization") DEFTIMEVAR (TV_PLUGIN_RUN , "plugin execution") diff --git a/gcc/toplev.c b/gcc/toplev.c --- a/gcc/toplev.c +++ b/gcc/toplev.c @@ -77,6 +77,7 @@ along with GCC; see the file COPYING3. If not see #include "gimple.h" #include "tree-ssa-alias.h" #include "plugin.h" +#include "tree-threadsafe-analyze.h" #if defined (DWARF2_UNWIND_INFO) || defined (DWARF2_DEBUGGING_INFO) #include "dwarf2out.h" @@ -593,6 +594,11 @@ compile_file (void) if (seen_error ()) return; + /* Clean up the global data structures used by the thread safety + analysis. */ + if (warn_thread_safety) + clean_up_threadsafe_analysis (); + varpool_assemble_pending_decls (); finish_aliases_2 (); diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -446,6 +446,7 @@ extern struct gimple_opt_pass pass_tracer; extern struct gimple_opt_pass pass_warn_unused_result; extern struct gimple_opt_pass pass_split_functions; extern struct gimple_opt_pass pass_feedback_split_functions; +extern struct gimple_opt_pass pass_threadsafe_analyze; /* IPA Passes */ extern struct simple_ipa_opt_pass pass_ipa_lower_emutls; diff --git a/gcc/tree-threadsafe-analyze.c b/gcc/tree-threadsafe-analyze.c new file mode 100644 --- /dev/null +++ b/gcc/tree-threadsafe-analyze.c @@ -0,0 +1,3546 @@ +/* Thread Safety Annotations and Analysis. + Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. + Contributed by Le-Chun Wu <lcwu@google.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + + +/* This file contains an analysis pass that uses the thread safety attributes + to identify and warn about potential issues that could result in data + races and deadlocks. The thread safety attributes currently support only + lock-based synchronization. They help developers document the locks that + need to be used to safely read and write shared variables and also the + order in which they intend to acquire locks. Here is the list of the + attributes that this analysis pass uses: + + __attribute__ ((lockable)) + __attribute__ ((scoped_lockable)) + __attribute__ ((guarded_by(x))) + __attribute__ ((guarded)) + __attribute__ ((point_to_guarded_by(x))) + __attribute__ ((point_to_guarded)) + __attribute__ ((acquired_after(__VA_ARGS__))) + __attribute__ ((acquired_before(__VA_ARGS__))) + __attribute__ ((exclusive_lock(__VA_ARGS__))) + __attribute__ ((shared_lock(__VA_ARGS__))) + __attribute__ ((exclusive_trylock(__VA_ARGS__))) + __attribute__ ((shared_trylock(__VA_ARGS__))) + __attribute__ ((unlock(__VA_ARGS__))) + __attribute__ ((exclusive_locks_required(__VA_ARGS__))) + __attribute__ ((shared_locks_required(__VA_ARGS__))) + __attribute__ ((locks_excluded(__VA_ARGS__))) + __attribute__ ((lock_returned(x))) + __attribute__ ((no_thread_safety_analysis)) + __attribute__ ((ignore_reads_begin)) + __attribute__ ((ignore_reads_end)) + __attribute__ ((ignore_writes_begin)) + __attribute__ ((ignore_writes_end)) + __attribute__ ((unprotected_read)) + + If multi-threaded code is annotated with these attributes, this analysis + pass can detect the following potential thread safety issues: + + * Accesses to shared variables and function calls are not guarded by + proper (read or write) locks + * Locks are not acquired in the specified order + * A cycle in the lock acquisition order + * Try to acquire a lock that is already held by the same thread + - Useful when locks are non-reentrant + * Locks are not acquired and released in the same routine (or in the + control-equivalent blocks) + - Having critical sections starting and ending in the same routine + is a better practice + + The analysis pass uses a single-pass (or single iteration) data-flow + analysis to maintain live lock sets at each program point, using the + attributes to decide when to add locks to the live sets and when to + remove them from the sets. With the live lock sets and the attributes + attached to shared variables and functions, we are able to check whether + the variables and functions are well protected. Note that the reason why + we don't need iterative data flow analysis is because critical sections + across back edges are considered a bad practice. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "tree.h" +#include "c-family/c-common.h" +#include "toplev.h" +#include "input.h" +#include "diagnostic.h" +#include "intl.h" +#include "basic-block.h" +#include "timevar.h" +#include "tree-flow.h" +#include "tree-pass.h" +#include "tree-dump.h" +#include "langhooks.h" +#include "pointer-set.h" +#include "tree-pretty-print.h" +#include "tree-threadsafe-analyze.h" +#include "tree-ssa-propagate.h" + + +/* A per-BB data structure used for topological traversal and data flow + analysis. */ +struct bb_threadsafe_info +{ + /* Indicating whether the BB has been visited in the analysis. */ + bool visited; + + /* Flags indicating whether we should ignore reads or writes in the + analysis. The flags are set and unset during the analysis when seeing + function calls annotated with ignore_{reads|writes}_{begin|end} + attributes. */ + bool reads_ignored; + bool writes_ignored; + + /* Number of predecessors visited. Used for topological traversal of BBs. */ + unsigned int n_preds_visited; + + /* Live out exclusive/shared lock sets. */ + struct pointer_set_t *liveout_exclusive_locks; + struct pointer_set_t *liveout_shared_locks; + + /* Locks released by the Release routine of a scoped lock (e.g. + std::unique_lock::Release()). When a lock is released by such routines + on certain control-flow paths but not all, we consider it weakly + released and keep track of it in this set so that later when we encounter + the destructor of the scoped lock (which is also an UNLOCK function), + we will not emit a bogus warning. */ + struct pointer_set_t *weak_released_locks; + + /* Working live lock sets. These sets are used and updated during the + analysis of statements. */ + struct pointer_set_t *live_excl_locks; + struct pointer_set_t *live_shared_locks; + + /* The outgoing edge that a successful trylock call takes. */ + edge trylock_live_edge; + + /* Sets of live-out locks acquired by a successful trylock. */ + struct pointer_set_t *edge_exclusive_locks; + struct pointer_set_t *edge_shared_locks; +}; + + +/* This data structure is created when we see a function call that is a + trylock. A map entry that maps the trylock call to its associated + trylock_info structure is inserted to trylock_info_map (see below). + The map (and the trylock_info structure) will later be used when we + analyze a block-ending if-statement whose condition expression is + fed by a trylock. */ +struct trylock_info +{ + /* The set of locks the trylock call tries to acquire. */ + struct pointer_set_t *locks; + + /* Indicating whether the locks acquired by trylock is exclusive + (i.e. writer lock) or not. */ + bool is_exclusive; + + /* Specify the trylock return value on a successful lock acquisition. */ + int succ_retval; +}; + +/* Access mode used for indicating whether an access to a shared variable + is a read or a write. */ +enum access_mode +{ + TSA_READ, + TSA_WRITE +}; + +/* True if the parser is currently parsing a lock attribute. */ +bool parsing_lock_attribute = false; + +/* A map of which each entry maps a lock, say A, to a set of locks that + lock A should be acquired after. This map is first populated when we + parse the lock declarations that are annotated with "acquired_after" + or "acquired_before" attributes. Later at the beginning of the thread + safety analysis (see build_transitive_acquired_after_sets()), we + calculate the transitive closures of the acquired_after sets for the + locks and modify the map. For example, if we have global variables + declarations like the following: + + Mutex mu1; + Mutex mu2 __attribute__ ((acquired_after(mu1))); + Mutex mu3 __attribute__ ((acquired_after(mu2))); + + After parsing, the contents of the map is shown below: + + lock acquired_after set + -------------------------- + mu2 -> { mu1 } + mu3 -> { mu2 } + + After we call build_transitive_acquired_after_sets(), the map would be + modified as shown below: + + lock acquired_after set + -------------------------- + mu2 -> { mu1 } + mu3 -> { mu2, mu1 } */ +struct pointer_map_t *lock_acquired_after_map = NULL; + +/* This flag is used for indicating whether transitive acquired_after sets + for the locks have been built so that we only build them once per + compilation unit. */ +static bool transitive_acq_after_sets_built = false; + +/* These two variables are used during the process of building acquired_after + transitive closures. A lock is considered finalized (and then added to the + finalized_locks set) when every member of its current acquired_after set + (1) is finalized, or + (2) doesn't have an acquired_after set (i.e. a root in the partial order + of the acquired_after relations) + + Once a lock is finalized, we never have to calculate its acquired_after set + again during the transitive closure building process. This helps make the + calculation converge faster. + + The reason why we needed to use global variables (instead of passing them + in as parameters) is because we use pointer_set_traverse routine to visit + set members, and the routine only take one additional parameter (besides + the set and the applied function). */ +static struct pointer_set_t *finalized_locks; +static bool finalized = true; + +/* This map contains the locks specified in attributes that couldn't be bound + to any decl tree in scope when they were parsed. We would try to bind them + during the analysis. */ +struct pointer_map_t *unbound_lock_map = NULL; + +/* A map of which each entry maps a scoped lock to the lock it acquires + at construction. An entry is created and added to the map when we see + the constructor of a scoped lock. It is later used when we see the + destructor of the scoped lock because the destructor doesn't take an + argument that specifies the lock. */ +static struct pointer_map_t *scopedlock_to_lock_map; + +/* Each entry maps a lock to the source location at which it was last + acquired. */ +static struct pointer_map_t *lock_locus_map; + +/* Each entry maps a lock to its canonicalized expression (see + get_canonical_lock_expr()). */ +static htab_t lock_expr_tab; + +/* Each entry is a gimple call statement. Calls to the same function with + symbolically identical arguments will hash to the same entry. */ +static htab_t gimple_call_tab; + +/* Each entry maps a trylock call expr to its trylock_info. */ +static struct pointer_map_t *trylock_info_map; + +/* Source location of the currently processed expression. In our analysis, + we actually tried to pass the source location around through function + parameters. However, in the cases where we need to use pointer_set_traverse + or pointer_map_traverse, this global variable is used. */ +static const location_t *current_loc; + +/* Buffer for pretty print the lock expression in the warning messages. */ +static pretty_printer pp_buf; + +/* Forward declaration */ +static void analyze_expr (tree, tree, bool, struct pointer_set_t *, + struct pointer_set_t *, const location_t *, + enum access_mode); + + +/* This function hashes an expr tree to a hash value by doing the following: + - for a decl, returns the pointer hash of the tree, + - for an integer constant, returns the sum of low and high parts, + - for other expressions, sums up the hash values of all operands and + multiplies it by the opcode, + - for all other trees, returns 0. */ + +static hashval_t +lock_expr_hash (const void *exp) +{ + const_tree expr = (const_tree) exp; + + STRIP_NOPS (expr); + + if (DECL_P (expr)) + return htab_hash_pointer (expr); + else if (TREE_CODE (expr) == INTEGER_CST) + return (hashval_t) (TREE_INT_CST_LOW (expr) + TREE_INT_CST_HIGH (expr)); + else if (EXPR_P (expr)) + { + int nops = TREE_OPERAND_LENGTH (expr); + int i; + hashval_t sum = 0; + for (i = 0; i < nops; i++) + { + tree op = TREE_OPERAND (expr, i); + if (op != 0) + sum += lock_expr_hash (op); + } + sum *= (hashval_t) TREE_CODE (expr); + return sum; + } + else + return 0; +} + +/* Given two lock expressions/trees, determine whether they are equal. + This is basically a wrapper around operand_equal_p so please see its + comments for how two expression trees are considered equal + (in fold-const.c). */ + +static int +lock_expr_eq (const void *exp1, const void* exp2) +{ + const_tree expr1 = (const_tree) exp1; + const_tree expr2 = (const_tree) exp2; + + return operand_equal_p (expr1, expr2, OEP_PURE_SAME); +} + +/* This function hashes a gimple call statement to a hash value. + Calls to the same function would be hashed to the same value. */ + +static hashval_t +call_gs_hash (const void *call) +{ + const_gimple call_gs = (const_gimple) call; + tree fdecl = gimple_call_fndecl (call_gs); + if (fdecl) + return htab_hash_pointer (fdecl); + else + { + tree fn_ptr = gimple_call_fn (call_gs); + return lock_expr_hash (get_canonical_lock_expr (fn_ptr, NULL_TREE, true, + NULL_TREE)); + } +} + +/* Given two gimple call statements, determine whether they are equal. + Two calls are consider equal if they call the same function with the + same arguments (which is determined using operand_equal_p). This is + a helper function used by gimple_call_tab hash table. */ + +static int +call_gs_eq (const void *call1, const void* call2) +{ + const_gimple call_gs1 = (const_gimple) call1; + const_gimple call_gs2 = (const_gimple) call2; + tree fdecl1 = gimple_call_fndecl (call_gs1); + tree fdecl2 = gimple_call_fndecl (call_gs2); + unsigned i, num_args1, num_args2; + + if (call_gs1 == call_gs2) + return 1; + + if (fdecl1 != fdecl2) + return 0; + + if (!fdecl1) + { + tree fn_ptr1 = get_canonical_lock_expr (gimple_call_fn (call_gs1), + NULL_TREE, true, NULL_TREE); + tree fn_ptr2 = get_canonical_lock_expr (gimple_call_fn (call_gs2), + NULL_TREE, true, NULL_TREE); + if (!operand_equal_p (fn_ptr1, fn_ptr2, OEP_PURE_SAME)) + return 0; + } + + num_args1 = gimple_call_num_args (call_gs1); + num_args2 = gimple_call_num_args (call_gs2); + + if (num_args1 != num_args2) + return 0; + + for (i = 0; i < num_args1; ++i) + { + tree arg1 = get_canonical_lock_expr (gimple_call_arg (call_gs1, i), + NULL_TREE, true, NULL_TREE); + tree arg2 = get_canonical_lock_expr (gimple_call_arg (call_gs2, i), + NULL_TREE, true, NULL_TREE); + if (!operand_equal_p (arg1, arg2, OEP_PURE_SAME)) + return 0; + } + + return 1; +} + +/* This is a helper function passed in (as a parameter) to the + pointer_set_traverse routine when we traverse the acquired_after set + of a lock, say lock A, to populate the transitive closure. It should + not be called by other functions. Parameter LOCK is a member of lock A's + acquired_after set and TRANSITIVE_LOCKS is the set of locks that will + eventually be added to lock A's acquired_after set. */ + +static bool +add_transitive_locks (const void *lock, void *transitive_locks) +{ + void **entry = pointer_map_contains (lock_acquired_after_map, lock); + + if (!entry) + return true; + + /* Add LOCK's acquired_after set to lock A's transitive closure. */ + pointer_set_union_inplace ((struct pointer_set_t *) transitive_locks, + (struct pointer_set_t *) *entry); + + /* If LOCK, which is a member of lock A's acquired_after set, is not + finalized, lock A is not finalized. */ + if (!pointer_set_contains (finalized_locks, lock)) + finalized = false; + + return true; +} + +/* This is a helper function passed in (as a parameter) to the + pointer_map_traverse routine when we traverse lock_acquired_after_map + to update the acquired_after set for each lock. It should not be + called by other functions. + + This function iterates over members of LOCK's acquired_after set + (i.e. ACQ_AFTER_SET) and adds their acquired_after sets to + "transitive_lock", which is then union-ed with ACQ_AFTER_SET. + If there is any new member added to the ACQ_AFTER_SET, we need to + set *UPDATED to true so that the main loop that calculates the transitive + closures will iterate again (see build_transitive_acquired_after_sets()). + Also if every member of ACQ_AFTER_SET is finalized, LOCK is also finalized + and added to the finalized_locks set. */ + +static bool +update_acquired_after (const void *lock, void **acq_after_set, + void *updated) +{ + struct pointer_set_t *transitive_locks; + size_t old_num_elements; + size_t new_num_elements; + + /* Skip locks whose acquired_after set is already finalized. */ + if (pointer_set_contains (finalized_locks, lock)) + return true; + + transitive_locks = pointer_set_create(); + + /* Before we traverse the acq_after_set, set finalized to true. If any + of acq_after_set's members is not finalized, the flag will be set to + false. */ + finalized = true; + + pointer_set_traverse ((struct pointer_set_t *) *acq_after_set, + add_transitive_locks, transitive_locks); + + /* Before we union transitive_locks with acq_after_set, get the original + member number of acq_after_set. */ + old_num_elements = + pointer_set_cardinality ((struct pointer_set_t *) *acq_after_set); + + pointer_set_union_inplace ((struct pointer_set_t *) *acq_after_set, + transitive_locks); + + new_num_elements = + pointer_set_cardinality ((struct pointer_set_t *) *acq_after_set); + + gcc_assert (new_num_elements >= old_num_elements); + + /* If new member number is greater than the original, which means some new + members (locks) were added to acq_after_set, set *update to true. */ + if (new_num_elements > old_num_elements) + { + *((bool *)updated) = true; + if (finalized) + pointer_set_insert (finalized_locks, lock); + } + else + /* If no new locks were added to ACQ_AFTER_SET, LOCK is also finalized. */ + pointer_set_insert (finalized_locks, lock); + + pointer_set_destroy (transitive_locks); + + return true; +} + +/* This function builds transitive acquired_after sets (i.e. transitive + closures) for locks and updates the lock_acquired_after_map. It iteratively + traverses the lock_acquired_after_map, updating the acquired_after sets + until the transitive closures converge. This function is called at most + once per compilation unit. */ + +static void +build_transitive_acquired_after_sets (void) +{ + bool updated = false; + + finalized_locks = pointer_set_create(); + + while (1) + { + pointer_map_traverse (lock_acquired_after_map, update_acquired_after, + &updated); + if (!updated) + return; + + updated = false; + } + + pointer_set_destroy (finalized_locks); +} + +/* A helper function used by pointer_map_traverse to destroy ACQ_AFTER_SET + when deleting the lock_acquired_after_map. */ + +static bool +destroy_acquired_after_set (const void * ARG_UNUSED (lock), + void **acq_after_set, void * ARG_UNUSED (data)) +{ + pointer_set_destroy ((struct pointer_set_t *) *acq_after_set); + return true; +} + +/* Function to delete the lock_expr_tab, lock_acquired_after_map, and + unbound_lock_map. This is called at the end of a compilation unit. + (See toplev.c) */ + +void +clean_up_threadsafe_analysis (void) +{ + if (lock_expr_tab) + { + htab_delete (lock_expr_tab); + lock_expr_tab = NULL; + } + + /* Free the lock acquired_after map and the sets */ + if (lock_acquired_after_map) + { + pointer_map_traverse (lock_acquired_after_map, + destroy_acquired_after_set, NULL); + pointer_map_destroy (lock_acquired_after_map); + lock_acquired_after_map = NULL; + } + + transitive_acq_after_sets_built = false; + + /* Free the unbound lock map */ + if (unbound_lock_map) + { + pointer_map_destroy (unbound_lock_map); + unbound_lock_map = NULL; + } +} + +/* Given a BASE object of a field access (i.e. base->a or base->foo()), + this function tells whether BASE is a this pointer (i.e. this->a or + this->foo()). */ + +static bool +is_base_object_this_pointer (tree base) +{ + tree this_ptr; + + if (TREE_CODE (base) != INDIRECT_REF && TREE_CODE (base) != MEM_REF) + return false; + + this_ptr = TREE_OPERAND (base, 0); + + if (TREE_CODE (this_ptr) == SSA_NAME) + this_ptr = SSA_NAME_VAR (this_ptr); + + if (TREE_CODE (this_ptr) == PARM_DECL + && DECL_NAME (this_ptr) == maybe_get_identifier ("this")) + return true; + else + return false; +} + +/* Given a CALL gimple statment, check if its function decl is annotated + with "lock_returned" attribute. If so, return the lock specified in + the attribute. Otherise, return NULL_TREE. */ + +static tree +get_lock_returned_by_call (gimple call) +{ + tree fdecl = gimple_call_fndecl (call); + tree attr = (fdecl + ? lookup_attribute ("lock_returned", DECL_ATTRIBUTES (fdecl)) + : NULL_TREE); + if (attr) + { + gcc_assert (TREE_VALUE (attr) && TREE_VALUE (TREE_VALUE (attr))); + return TREE_VALUE (TREE_VALUE (attr)); + } + else + return NULL_TREE; +} + +/* Given a lock expression (LOCKABLE), this function returns the + var/field/parm decl part of the lockable. For example, if the lockable + is a[2].foo->mu, it returns the decl tree of mu. */ + +static tree +get_lockable_decl (tree lockable) +{ + switch (TREE_CODE (lockable)) + { + case VAR_DECL: + case FIELD_DECL: + case PARM_DECL: + { + /* If the lockable is a compiler-generated temp variable that + has a debug expr specifying the original var decl (see + lookup_tmp_var() in gimplify.c), return the original var decl. */ + if (DECL_ARTIFICIAL (lockable) + && (DECL_DEBUG_EXPR_IS_FROM (lockable) + && DECL_DEBUG_EXPR (lockable))) + { + lockable = DECL_DEBUG_EXPR (lockable); + gcc_assert (DECL_P (lockable)); + } + return lockable; + } + case ADDR_EXPR: + /* Handle the case of mu.Lock(), i.e. Lock(&mu). */ + return get_lockable_decl (TREE_OPERAND (lockable, 0)); + case SSA_NAME: + { + /* If the lockable is an SSA_NAME of a temp variable (with or + without a name), we get to get the original variable decl + by back-tracing its SSA def (as shown in the following example). + D.2_1 = &this->mu; + Lock (D.2_1); + Note that the SSA name doesn't always have a def statement + (e.g. "this" pointer). */ + tree vdecl = SSA_NAME_VAR (lockable); + if (DECL_ARTIFICIAL (vdecl) + && !gimple_nop_p (SSA_NAME_DEF_STMT (lockable))) + { + gimple def_stmt = SSA_NAME_DEF_STMT (lockable); + if (is_gimple_assign (def_stmt) + && (get_gimple_rhs_class (gimple_assign_rhs_code (def_stmt)) + == GIMPLE_SINGLE_RHS)) + return get_lockable_decl (gimple_assign_rhs1 (def_stmt)); + else if (is_gimple_call (def_stmt)) + return get_lock_returned_by_call (def_stmt); + else + return get_lockable_decl (vdecl); + } + else + return get_lockable_decl (vdecl); + } + case COMPONENT_REF: + /* Handle the case of Foo.mu.Lock() or Foo->mu.Lock() */ + return get_lockable_decl (TREE_OPERAND (lockable, 1)); + case ARRAY_REF: + return get_lockable_decl (TREE_OPERAND (lockable, 0)); + default: + return NULL_TREE; + } +} + +/* Build a fully-qualified name of a lock that is a class member with the + given BASE object tree and the LOCK_FIELD tree. This helper function is + usually used when handling lock_returned, lock, and unlock attributes. + For example, given the following code + + class Bar { + public: + bool MyLock() __attributes__ ((exclusive_lock(mu1_))); + void MyUnlock() __attributes__ ((unlock(mu1_))); + int a_ __attribute__ ((guarded_by(mu1_))); + private: + Mutex mu1_; + }; + + Bar *b1, *b2; + + void func() + { + b1->MyLock(); // S1 + b1->a_ = 5; // S2 + b2->a_ = 3; // S3 + b1->MyUnlock(); // S4 + } + + When analyzing statement S1, instead of adding "mu1_" to the live lock + set, we need to build the fully-qualified name, b1->mu1, first and add + the fully-qualified name to the live lock set. The same goes for the unlock + statement in S4. Without using the fully-qualified lock names, we won't + be able to tell the lock requirement difference between S2 and S3. */ + +static tree +build_fully_qualified_lock (tree lock_field, tree base) +{ + tree lock; + tree canon_base = get_canonical_lock_expr (base, NULL_TREE, + true /* is_temp_expr */, + NULL_TREE); + + /* When the base is a pointer, i.e. b1->MyLock() (or MyLock(base) + internally), we need to create a new base that is INDIRECT_REF so that + we could form a correct fully-qualified lock expression with the + lock_field (e.g. b1->lock_field). On the other hand, if the base is an + address_taken operation (i.e. base.foo() or foo(&base)), we need to get + rid of the ADDR_EXPR operator before we form the new lock expression. */ + if (TREE_CODE (canon_base) != ADDR_EXPR) + { + gcc_assert (POINTER_TYPE_P (TREE_TYPE (canon_base))); + canon_base = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (canon_base)), + canon_base); + } + else + canon_base = TREE_OPERAND (canon_base, 0); + + lock = get_canonical_lock_expr (lock_field, canon_base, + false /* is_temp_expr */, NULL_TREE); + + return lock; +} + +/* Given a lock expression, this function returns a canonicalized + expression (for matching locks in the analysis). Basically this + function does the following to canonicalize the expression: + + - Fold temp variables. e.g. + + D.1 = &q->mu; ==> foo (&q->mu); + foo (D.1); + + - Fold SSA names. e.g. + + q.0_1 = q; ==> q->mu; + q.0_1->mu; + + - Replace function calls that return a lock with the actual lock returned + (if it is annotated with "lock_returned" attribute). + + - Subexpressions of the lock are canonicalized recursively. + + When matching two expressions, we currently only do it symbolically. + That is, if two different pointers, say p and q, point to the same + location, they will not map to the same canonical expr. + + This function can also be called to get a canonical form of an expression + which may not be a lock. For example, when trying to canonicalize the base + object expression. And in that case, IS_TEMP_EXPR is set to true so that + the expression will not be inserted into the LOCK_EXPR_TAB. + + When this function is called with a non-null NEW_LEFTMOST_BASE_VAR and the + lefmost base of LOCK is a PARM_DECL, we are trying to replace a formal + parameter with an actual argument (i.e. NEW_LEFTMOST_BASE_VAR). This + function will return an expression whose leftmost base/operands is replaced + with the given new base var. For example, if LOCK is a->b[3]->c and + NEW_LEFTMOST_BASE_VAR is x, the function will return (the canonical form + of) x->b[3]->c. */ + +tree +get_canonical_lock_expr (tree lock, tree base_obj, bool is_temp_expr, + tree new_leftmost_base_var) +{ + hashval_t hash; + tree canon_lock; + void **slot; + + switch (TREE_CODE (lock)) + { + case PARM_DECL: + if (new_leftmost_base_var) + return new_leftmost_base_var; + /* Fall through to the following case. */ + case VAR_DECL: + { + /* If the lock is a compiler-generated temp variable that + has a debug expr specifying the original var decl (see + lookup_tmp_var() in gimplify.c), return the original var decl. */ + if (DECL_ARTIFICIAL (lock) + && (DECL_DEBUG_EXPR_IS_FROM (lock) + && DECL_DEBUG_EXPR (lock))) + { + lock = DECL_DEBUG_EXPR (lock); + gcc_assert (DECL_P (lock)); + } + return lock; + } + case FIELD_DECL: + { + /* If the LOCK is a field decl and BASE_OBJ is not NULL, build a + component_ref expression for the canonical lock. */ + if (base_obj) + { + tree full_lock = build3 (COMPONENT_REF, TREE_TYPE (lock), + base_obj, lock, NULL_TREE); + lock = get_canonical_lock_expr (full_lock, NULL_TREE, + true /* is_temp_expr */, + NULL_TREE); + } + return lock; + } + case SSA_NAME: + { + /* If the lock is an SSA_NAME of a temp variable (with or + without a name), we can possibly get the original variable decl + by back-tracing its SSA def (as shown in the following example). + + D.2_1 = &this->mu; + Lock (D.2_1); + + Note that the SSA name doesn't always have a def statement + (e.g. "this" pointer). */ + tree vdecl = SSA_NAME_VAR (lock); + if (DECL_ARTIFICIAL (vdecl) + && !gimple_nop_p (SSA_NAME_DEF_STMT (lock))) + { + gimple def_stmt = SSA_NAME_DEF_STMT (lock); + if (is_gimple_assign (def_stmt) + && (get_gimple_rhs_class (gimple_assign_rhs_code (def_stmt)) + == GIMPLE_SINGLE_RHS)) + return get_canonical_lock_expr (gimple_assign_rhs1 (def_stmt), + base_obj, is_temp_expr, + NULL_TREE); + else if (is_gimple_call (def_stmt)) + { + tree fdecl = gimple_call_fndecl (def_stmt); + tree real_lock = get_lock_returned_by_call (def_stmt); + if (real_lock) + { + gcc_assert (fdecl); + if (TREE_CODE (TREE_TYPE (fdecl)) == METHOD_TYPE) + { + tree base = gimple_call_arg (def_stmt, 0); + lock = build_fully_qualified_lock (real_lock, base); + } + else + lock = real_lock; + break; + } + /* We deal with a lockable object wrapped in a smart pointer + here. For example, given the following code + + auto_ptr<Mutex> mu; + mu->Lock(); + + We would like to ignore the "operator->" (or "operator.") + and simply return mu. We also treat the "get" method of + a smart pointer the same as operator->. And we only do it + when LOCK is indeed a lock expr, not some temp expr. */ + else if (fdecl + && ((DECL_NAME (fdecl) + == maybe_get_identifier ("operator->")) + || (DECL_NAME (fdecl) + == maybe_get_identifier ("operator.")) + || (DECL_NAME (fdecl) + == maybe_get_identifier ("get"))) + && !is_temp_expr + && POINTER_TYPE_P (TREE_TYPE (lock)) + && lookup_attribute ("lockable", TYPE_ATTRIBUTES ( + TREE_TYPE (TREE_TYPE (lock))))) + { + tree arg = gimple_call_arg (def_stmt, 0); + tree canon_arg = get_canonical_lock_expr ( + arg, base_obj, false /* is_temp_expr */, NULL_TREE); + if (TREE_CODE (canon_arg) == ADDR_EXPR) + lock = TREE_OPERAND (canon_arg, 0); + break; + } + + /* For a gimple call statement not annotated with + "lock_returned" attr, try to get the canonical lhs of + the statement. */ + hash = call_gs_hash (def_stmt); + if (hash) + { + gimple canon_call = (gimple) htab_find_with_hash ( + gimple_call_tab, def_stmt, hash); + if (!canon_call) + { + slot = htab_find_slot_with_hash (gimple_call_tab, + def_stmt, hash, + INSERT); + *slot = def_stmt; + canon_call = def_stmt; + } + lock = gimple_call_lhs (canon_call); + break; + } + } + } + return get_canonical_lock_expr (vdecl, base_obj, is_temp_expr, + NULL_TREE); + } + case ADDR_EXPR: + { + tree base = TREE_OPERAND (lock, 0); + tree canon_base; + /* When the expr is a pointer to a lockable type (i.e. mu.Lock() + or Lock(&mu) internally), we don't need the address-taken + operator (&). */ + if (lookup_attribute("lockable", TYPE_ATTRIBUTES (TREE_TYPE (base)))) + return get_canonical_lock_expr (base, base_obj, + false /* is_temp_expr */, + new_leftmost_base_var); + canon_base = get_canonical_lock_expr (base, NULL_TREE, + true /* is_temp_expr */, + new_leftmost_base_var); + if (base != canon_base) + lock = build1 (ADDR_EXPR, TREE_TYPE (lock), canon_base); + break; + } + case COMPONENT_REF: + { + /* Handle the case of Foo.mu.Lock() or Foo->mu.Lock(). + If the base is "this" pointer or a base class, get the component + only. */ + tree base = TREE_OPERAND (lock, 0); + tree component = TREE_OPERAND (lock, 1); + tree canon_base; + if (is_base_object_this_pointer (base)) + return get_canonical_lock_expr (component, NULL_TREE, is_temp_expr, + NULL_TREE); + + canon_base = get_canonical_lock_expr (base, base_obj, + true /* is_temp_expr */, + new_leftmost_base_var); + + /* If either the base or the component is a compiler-generated base + object field, skip it. For example, if a lock expressions is + foo->D.2801.mu, where D.2801 is the base field in foo which is + a derived class, we want the canonical form of the lock to be + foo->mu. */ + if (lang_hooks.decl_is_base_field (canon_base)) + return get_canonical_lock_expr (component, NULL_TREE, is_temp_expr, + NULL_TREE); + + if (lang_hooks.decl_is_base_field (component)) + return canon_base; + + if (base != canon_base) + lock = build3 (COMPONENT_REF, TREE_TYPE (component), + canon_base, component, NULL_TREE); + break; + } + case ARRAY_REF: + { + tree array = TREE_OPERAND (lock, 0); + tree canon_array = get_canonical_lock_expr (array, base_obj, + true /* is_temp_expr */, + new_leftmost_base_var); + tree index = TREE_OPERAND (lock, 1); + tree canon_index = (TREE_CODE (index) == INTEGER_CST + ? index + : get_canonical_lock_expr (index, NULL_TREE, + true /* is_temp_expr */, + NULL_TREE)); + if (array != canon_array || index != canon_index) + lock = build4 (ARRAY_REF, TREE_TYPE (lock), canon_array, + canon_index, TREE_OPERAND (lock, 2), + TREE_OPERAND (lock, 3)); + break; + } + case INDIRECT_REF: + case MEM_REF: + { + tree base = TREE_OPERAND (lock, 0); + tree canon_base = get_canonical_lock_expr (base, base_obj, + true /* is_temp_expr */, + new_leftmost_base_var); + if (base != canon_base) + lock = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (canon_base)), + canon_base); + break; + } + default: + break; + } + + hash = lock_expr_hash (lock); + + /* Return the original lock expr if the lock expr is not something we can + handle now. */ + if (hash == 0) + return lock; + + /* Now that we have built a canonical lock expr, check whether it's already + in the lock_expr_tab. If so, grab and return it. Otherwise, insert the + new lock expr to the map. */ + if (lock_expr_tab == NULL) + lock_expr_tab = htab_create (10, lock_expr_hash, lock_expr_eq, NULL); + + canon_lock = (tree) htab_find_with_hash (lock_expr_tab, lock, hash); + if (canon_lock) + return canon_lock; + + /* If the lock is created temporarily (e.g. to form a full-path + lock name), don't insert it in the lock_expr_tab as the lock + tree will be manually freed later. */ + if (!is_temp_expr) + { + slot = htab_find_slot_with_hash (lock_expr_tab, lock, hash, INSERT); + *slot = lock; + } + + return lock; +} + +/* Dump the LOCK name/expr in char string to OUT_BUF. If LOCK is a + simple decl, we just use the identifier node of the lock. Otherwise, + we use the tree pretty print mechanism to do that. */ + +const char* +dump_expr_tree (tree lock, char *out_buf) +{ + if (DECL_P (lock) && DECL_NAME (lock)) + snprintf(out_buf, LOCK_NAME_LEN, "'%s'", + IDENTIFIER_POINTER (DECL_NAME (lock))); + else + { + pp_clear_output_area (&pp_buf); + dump_generic_node (&pp_buf, lock, 0, TDF_DIAGNOSTIC, false); + snprintf(out_buf, LOCK_NAME_LEN, "'%s'", + pp_base_formatted_text (&pp_buf)); + } + return out_buf; +} + +/* A helper function that checks if the left-most operand of + EXPR is a field decl, and if so, returns true. For example, if EXPR is + 'a.b->c[2]', it will check if 'a' is a field decl. */ + +static bool +leftmost_operand_is_field_decl (tree expr) +{ + if (TREE_CODE (get_leftmost_base_var (expr)) == FIELD_DECL) + return true; + else + return false; +} + +/* Check whether the given LOCK is a member of LOCK_SET and return the lock + contained in the set if so. This check is more complicated than just + calling pointer_set_contains with LOCK and LOCKSET because we need to + get the canonical form of the lock. Also the LOCK_SET may contain the + universal lock (i.e. error_mark_node). IGNORE_UNIVERSAL_LOCK indicates + whether to ignore it. In order to be conservative (not to emit false + positives), we don't want to ignore the universal lock when checking for + locks required, but should ignore it when checking for locks excluded. */ + +static tree +lock_set_contains (const struct pointer_set_t *lock_set, tree lock, + tree base_obj, bool ignore_universal_lock) +{ + /* If the universal lock is in the LOCK_SET and it is not to be ignored, + just assume the LOCK is in the LOCK_SET and returns it. */ + if (!ignore_universal_lock + && pointer_set_contains (lock_set, error_mark_node)) + return lock; + + /* If the lock is a field and the base is not 'this' pointer nor a base + class, we need to check the lock set with the fully-qualified lock name. + Otherwise, we could be confused by the same lock field of a different + object. */ + if (leftmost_operand_is_field_decl (lock) + && base_obj != NULL_TREE + && !is_base_object_this_pointer (base_obj) + && !lang_hooks.decl_is_base_field (base_obj)) + { + /* canonical lock is a fully-qualified name. */ + tree canonical_lock = get_canonical_lock_expr (lock, base_obj, + true /* is_temp_expr */, + NULL_TREE); + tree result = (pointer_set_contains (lock_set, canonical_lock) + ? canonical_lock : NULL_TREE); + return result; + } + /* Check the lock set with the given lock directly as it could already be + in canonical form. */ + else if (pointer_set_contains (lock_set, lock)) + return lock; + /* If the lock is not yet bound to a decl, try to bind it now. */ + else if (TREE_CODE (lock) == IDENTIFIER_NODE) + { + void **entry; + /* If there is any unbound lock in the attribute, the unbound lock map + must not be null. */ + gcc_assert (unbound_lock_map); + entry = pointer_map_contains (unbound_lock_map, lock); + gcc_assert (entry); + if (*entry) + { + tree lock_decl = (tree) *entry; + gcc_assert (TREE_CODE (lock_decl) == VAR_DECL + || TREE_CODE (lock_decl) == PARM_DECL + || TREE_CODE (lock_decl) == FIELD_DECL); + if (pointer_set_contains (lock_set, lock_decl)) + return lock_decl; + else + return NULL_TREE; + } + else + return NULL_TREE; + } + else + return NULL_TREE; +} + +/* This function checks whether LOCK is in the current live lock sets + (EXCL_LOCKS and SHARED_LOCKS) and emits warning message if it's not. + This function is called when analyzing the expression that either accesses + a shared variable or calls a function whose DECL is annotated with + guarded_by, point_to_guarded_by, or {exclusive|shared}_locks_required + attributes. + + IS_INDIRECT_REF indicates whether the (variable) access is indirect or not. + + LOCUS specifies the source location of the expression that accesses the + shared variable or calls the guarded function. + + MODE indicates whether the access is a read or a write. */ + +static void +check_lock_required (tree lock, tree decl, tree base_obj, bool is_indirect_ref, + const struct pointer_set_t *excl_locks, + const struct pointer_set_t *shared_locks, + const location_t *locus, enum access_mode mode) +{ + const char *msg; + char dname[LOCK_NAME_LEN], lname[LOCK_NAME_LEN]; + + if (TREE_CODE (decl) == FUNCTION_DECL) + { + gcc_assert (!is_indirect_ref); + msg = G_("Calling function"); + /* When the base obj tree is not an ADDR_EXPR, which means it is a + pointer (i.e. base->foo(), or foo(base) internally), we will need + to create a new base that is INDIRECT_REF so that we would be able + to form a correct full expression for a lock later. On the other hand, + if the base obj is an ADDR_EXPR (i.e. base.foo(), or foo(&base) + internally), we need to remove the address-taken operation. Note + that this is an issue only for class member functions. If DECL + is a class field, the base_obj is good. */ + if (base_obj) + { + tree canon_base = get_canonical_lock_expr (base_obj, NULL_TREE, + true /* is_temp_expr */, + NULL_TREE); + if (TREE_CODE (canon_base) != ADDR_EXPR) + { + gcc_assert (POINTER_TYPE_P (TREE_TYPE (canon_base))); + base_obj = build1 (INDIRECT_REF, + TREE_TYPE (TREE_TYPE (canon_base)), + canon_base); + } + else + base_obj = TREE_OPERAND (canon_base, 0); + } + } + else + { + if (mode == TSA_READ) + msg = G_("Reading variable"); + else + msg = G_("Writing to variable"); + } + + /* We want to use fully-qualified expressions (i.e. including base_obj + if any) for DECL when emitting warning messages. */ + if (base_obj) + { + if (TREE_CODE (decl) != FUNCTION_DECL) + { + tree full_decl = build3 (COMPONENT_REF, TREE_TYPE (decl), + base_obj, decl, NULL_TREE); + decl = get_canonical_lock_expr (full_decl, NULL_TREE, + true /* is_temp_expr */, NULL_TREE); + } + } + + if (!lock) + { + /* If LOCK is NULL, either the attribute is a "guarded" attribute that + doesn't specify a particular lock, or the lock name/expression + is not supported. Just check whether there is any live lock at this + point. */ + if (pointer_set_cardinality (excl_locks) == 0) + { + if (pointer_set_cardinality (shared_locks) == 0) + { + if (is_indirect_ref) + warning_at (*locus, OPT_Wthread_safety, + G_("Access to memory location pointed to by" + " variable %s requires a lock"), + dump_expr_tree (decl, dname)); + else + warning_at (*locus, + OPT_Wthread_safety, G_("%s %s requires a lock"), + msg, dump_expr_tree (decl, dname)); + } + else + { + if (mode == TSA_WRITE) + { + if (is_indirect_ref) + warning_at (*locus, OPT_Wthread_safety, + G_("Writing to memory location pointed to by" + " variable %s requires an exclusive lock"), + dump_expr_tree (decl, dname)); + else + warning_at (*locus, OPT_Wthread_safety, + G_("%s %s requires an exclusive lock"), + msg, dump_expr_tree (decl, dname)); + } + } + } + return; + } + + if (!DECL_P (lock)) + lock = get_canonical_lock_expr (lock, NULL_TREE, false /* is_temp_expr */, + NULL_TREE); + + if (!lock_set_contains(excl_locks, lock, base_obj, false)) + { + if (!lock_set_contains(shared_locks, lock, base_obj, false)) + { + /* We want to use fully-qualified expressions (i.e. including + base_obj if any) for LOCK when emitting warning + messages. */ + if (base_obj) + { + if (TREE_CODE (lock) == FIELD_DECL) + { + tree full_lock = build3 (COMPONENT_REF, TREE_TYPE (lock), + base_obj, lock, NULL_TREE); + /* Get the canonical lock tree */ + lock = get_canonical_lock_expr (full_lock, NULL_TREE, + true /* is_temp_expr */, + NULL_TREE); + } + } + if (is_indirect_ref) + warning_at (*locus, OPT_Wthread_safety, + G_("Access to memory location pointed to by" + " variable %s requires lock %s"), + dump_expr_tree (decl, dname), + dump_expr_tree (lock, lname)); + else + warning_at (*locus, OPT_Wthread_safety, + G_("%s %s requires lock %s"), + msg, dump_expr_tree (decl, dname), + dump_expr_tree (lock, lname)); + } + else + { + if (mode == TSA_WRITE) + { + if (base_obj) + { + /* We want to use fully-qualified expressions (i.e. + including base_obj if any) for LOCK when + emitting warning messages. */ + if (TREE_CODE (lock) == FIELD_DECL) + { + tree full_lock = build3 (COMPONENT_REF, TREE_TYPE (lock), + base_obj, lock, NULL_TREE); + /* Get the canonical lock tree */ + lock = get_canonical_lock_expr (full_lock, NULL_TREE, + true /* is_temp_expr */, + NULL_TREE); + } + } + if (is_indirect_ref) + warning_at (*locus, OPT_Wthread_safety, + G_("Writing to memory location pointed to by" + " variable %s requires exclusive lock %s"), + dump_expr_tree (decl, dname), + dump_expr_tree (lock, lname)); + else + warning_at (*locus, OPT_Wthread_safety, + G_("%s %s requires exclusive lock %s"), + msg, dump_expr_tree (decl, dname), + dump_expr_tree (lock, lname)); + } + } + } +} + +/* This data structure is created to overcome the limitation of the + pointer_set_traverse routine which only takes one data pointer argument. + Unfortunately when we are trying to decide whether a lock (with an optional + base object) is in a set or not, we will need 2 input parameters and 1 + output parameter. Therefore we use the following data structure. */ + +struct lock_match_info +{ + /* The lock which we want to check if it is in the acquired_after set. */ + tree lock; + + /* The base object of the lock if lock is a class member. */ + tree base; + + /* Whether we find a match or not. */ + bool match; +}; + +/* This is a helper function passed in (as a parameter) to the + pointer_set_traverse routine we invoke to traverse the acquired_after + set to find a match for the lock recorded in the match info + (parameter INFO). This function should not be called by other functions. + Parameter LOCK is a member of the acquired_after set. + If LOCK is a class field, we would reconstruct the LOCK name by + combining it with the base object (recorded in INFO) and do a match. + If we find a match, record the result in INFO->match and return false + so that pointer_set_traverse would stop iterating through the rest of + the set. Also see the comments for function acquired_after_set_contains() + below. */ + +static bool +match_locks (const void *lock, void *info) +{ + struct lock_match_info *match_info = (struct lock_match_info *)info; + tree acq_after_lock = CONST_CAST_TREE ((const_tree) lock); + bool result = true; + + if (TREE_CODE (acq_after_lock) == FIELD_DECL) + { + tree tmp_lock; + gcc_assert (match_info->base); + tmp_lock = build3 (COMPONENT_REF, TREE_TYPE (acq_after_lock), + match_info->base, acq_after_lock, NULL_TREE); + if (lock_expr_eq (tmp_lock, match_info->lock)) + { + match_info->match = true; + result = false; + } + /* Since we know tmp_lock is not going to be used any more, we might + as well free it even though it's not necessary. */ + ggc_free (tmp_lock); + } + + return result; +} + +/* Check if the LOCK is in the ACQ_AFTER_SET. This check is more complicated + than simply calling pointer_set_contains to see whether ACQ_AFTER_SET + contains LOCK because the ACQ_AFTER_SET could only contains the "bare" + name of the LOCK. For example, suppose we have the following code: + + class Foo { + public: + Mutex mu1; + Mutex mu2 attribute__ ((acquired_after(mu1))); + ... + }; + + main() + { + Foo *foo = new Foo(); + ... + foo->mu1.Lock(); + ... + foo->mu2.Lock(); + ... + } + + The lock_acquired_after_map would be + + lock acquired_after set + -------------------------- + mu2 -> { mu1 } + + In our analysis, when we look at foo->mu2.Lock() and want to know whether + foo->mu1 (which was acquired earlier) is in mu2's acquired_after set + (in this case, ACQ_AFTER_SET = { mu1 }, LOCK = foo->mu1, BASE = foo), + a call to pointer_set_contains(mu2_acquired_after_set, foo->mu1) would + return false as it is "mu1", not "foo->mu1", in mu2's acquired_after set. + Therefore we will need to iterate through each member of mu2's + acquired_after set, reconstructing the lock name with the BASE (which is + foo in this example), and check again. */ + +static bool +acquired_after_set_contains (const struct pointer_set_t *acq_after_set, + tree lock, tree base) +{ + struct lock_match_info *info; + bool result; + + if (pointer_set_contains (acq_after_set, lock)) + return true; + else if (base == NULL_TREE) + return false; + + /* Now that a simple call to pointer_set_contains returns false and + the LOCK appears to be a class member (as BASE is not null), + we need to look at each element of ACQ_AFTER_SET, reconstructing + their names, and check again. */ + info = XNEW (struct lock_match_info); + info->lock = lock; + info->base = base; + info->match = false; + + pointer_set_traverse (acq_after_set, match_locks, info); + + result = info->match; + + XDELETE (info); + + return result; +} + +/* Returns the base object if EXPR is a component ref tree, + NULL_TREE otherwise. + + Note that this routine is different from get_base_address or + get_base_var in that, for example, if we have an expression x[5].a, + this routine will return x[5], while the other two routines will + return x. Also if the expr is b[3], this routine will return NULL_TREE + while the other two will return b. */ + +static tree +get_component_ref_base (tree expr) +{ + if (TREE_CODE (expr) == COMPONENT_REF) + return TREE_OPERAND (expr, 0); + else if (TREE_CODE (expr) == ARRAY_REF) + return get_component_ref_base (TREE_OPERAND (expr, 0)); + else + return NULL_TREE; +} + +/* Given an expression, EXPR, returns the leftmost operand/base of EXPR. + For example, if EXPR is 'a.b->c[2]', it will return 'a'. Unlike + get_base_var, this routine allows the leftmost var to be a field decl. */ + +tree +get_leftmost_base_var (tree expr) +{ + while (EXPR_P (expr)) + expr = TREE_OPERAND (expr, 0); + return expr; +} + +/* This is helper function passed in (as a parameter) to pointer_set_traverse + when we traverse live lock sets to check for acquired_after requirement. + This function should not be called by other functions. The parameter + LIVE_LOCK is a member of the live lock set we are traversing, and parameter + LOCK is the lock we are about to add to the live lock set. + In this function, we first check if LIVE_LOCK is in the acquired_after + set of LOCK. If so, that's great (but we will also check whether LOCK is + in LIVE_LOCK's acquired_after set to see if there is a cycle in the + after_after relationship). Otherwise, we will emit a warning. */ + +static bool +check_acquired_after (const void *live_lock, void *lock) +{ + char lname1[LOCK_NAME_LEN], lname2[LOCK_NAME_LEN]; + tree lock_decl; + tree base; + void **entry; + tree live_lock_tree = CONST_CAST_TREE ((const_tree) live_lock); + tree live_lock_decl; + bool live_lock_in_locks_acq_after_set; + bool lock_in_live_locks_acq_after_set; + + /* If lock_acquired_after_map is never created, which means the user code + doesn't contain any acquired_after attributes, then simply return. + This should be changed later if we decide to warn about unspecified + locking order for two locks held simultaneously by a thread. */ + if (!lock_acquired_after_map) + return true; + + /* Since the lock_acquired_after_map is keyed by the decl tree of + the lock variable (see handle_acquired_after_attribute() in c-common.c), + we cannot use the full expression of the lock to look up the + lock_acquired_after_map. Instead, we need to get the lock decl + component of the expression. e.g. If the lock is a[2].foo->mu, + we cannot use the whole expression tree. We have to use the decl tree + of mu. */ + lock_decl = get_lockable_decl ((tree) lock); + base = (lock_decl ? get_component_ref_base ((tree) lock) : NULL_TREE); + entry = (lock_decl + ? pointer_map_contains (lock_acquired_after_map, lock_decl) + : NULL); + /* Check if LIVE_LOCK is in LOCK's acquired_after set. */ + live_lock_in_locks_acq_after_set = (entry + && acquired_after_set_contains ( + (struct pointer_set_t *) *entry, + live_lock_tree, base)); + + live_lock_decl = get_lockable_decl (live_lock_tree); + base = (live_lock_decl ? get_component_ref_base (live_lock_tree) + : NULL_TREE); + entry = (live_lock_decl + ? pointer_map_contains (lock_acquired_after_map, live_lock) + : NULL); + /* Check if LOCK is in LIVE_LOCK's acquired_after set. */ + lock_in_live_locks_acq_after_set = (entry + && acquired_after_set_contains ( + (struct pointer_set_t *) *entry, + (tree) lock, base)); + + if (!live_lock_in_locks_acq_after_set) + { + /* When LIVE_LOCK is not in LOCK's acquired_after set, we will emit + warning messages only when LIVE_LOCK is annotated as being acquired + after LOCK. Basically what we are saying here is that if the two + locks don't have an acquired_after relationship based on the + annotations (attributes), we will not check for (and warn about) + their locking order. This is an escape hatch for locks that could + be held simultaneously but their acquisition order is not expressible + using the current attribute/annotation scheme. */ + if (lock_in_live_locks_acq_after_set) + { + void **loc_entry = pointer_map_contains (lock_locus_map, live_lock); + if (loc_entry) + warning_at (*current_loc, OPT_Wthread_safety, + G_("Lock %s is acquired after lock %s (acquired at" + " line %d) but is annotated otherwise"), + dump_expr_tree ((tree) lock, lname1), + dump_expr_tree (live_lock_tree, lname2), + LOCATION_LINE (*((location_t *) *loc_entry))); + else + warning_at (*current_loc, OPT_Wthread_safety, + G_("Lock %s is acquired after lock %s (held at function" + " entry) but is annotated otherwise"), + dump_expr_tree ((tree) lock, lname1), + dump_expr_tree (live_lock_tree, lname2)); + } + return true; + } + + if (lock_in_live_locks_acq_after_set) + warning_at (*current_loc, OPT_Wthread_safety, + G_("There is a cycle in the acquisition order between locks" + " %s and %s"), + dump_expr_tree (live_lock_tree, lname1), + dump_expr_tree ((tree) lock, lname2)); + + return true; +} + +/* Main driver to check the lock acquisition order. LOCK is the lock we are + about to add to the live lock set. LIVE_EXCL_LOCKS and LIVE_SHARED_LOCKS + are the current live lock sets. LOCUS is the source location at which LOCK + is acquired. */ + +static void +check_locking_order (tree lock, + const struct pointer_set_t *live_excl_locks, + const struct pointer_set_t *live_shared_locks, + const location_t *locus) +{ + if (!warn_thread_mismatched_lock_order) + return; + + current_loc = locus; + pointer_set_traverse (live_excl_locks, check_acquired_after, lock); + pointer_set_traverse (live_shared_locks, check_acquired_after, lock); +} + +/* Given a CALL expr and an integer constant tree POS_ARG that specifies the + argument position, returns the corresponding argument by iterating + through the call's actual parameter list. */ + +static tree +get_actual_argument_from_position (gimple call, tree pos_arg) +{ + int lock_pos; + int num_args = gimple_call_num_args (call); + + gcc_assert (TREE_CODE (pos_arg) == INTEGER_CST); + + lock_pos = TREE_INT_CST_LOW (pos_arg); + + gcc_assert (lock_pos >= 1 && lock_pos <= num_args); + + /* The lock position specified in the attributes is 1-based, so we need to + subtract 1 from it when accessing the call arguments. */ + return gimple_call_arg (call, lock_pos - 1); +} + +/* Given a call (CALL) and its function decl (FDECL), return the actual + argument that corresponds to the given formal parameter (PARAM_DECL). */ + +static tree +get_actual_argument_from_parameter (gimple call, tree fdecl, tree param_decl) +{ + tree parm; + int parm_pos; + + for (parm = DECL_ARGUMENTS (fdecl), parm_pos = 0; + parm; + parm = TREE_CHAIN (parm), ++parm_pos) + if (DECL_NAME (parm) == DECL_NAME (param_decl)) + return gimple_call_arg (call, parm_pos); + + gcc_unreachable (); +} + +/* A helper function that adds the LOCKABLE, acquired by CALL, to the + corresponding lock sets (LIVE_EXCL_LOCKS or LIVE_SHARED_LOCKS) depending + on the boolean parameter IS_EXCLUSIVE_LOCK. If the CALL is a trylock call, + create a trylock_info data structure which will be used later. */ + +static void +add_lock_to_lockset (gimple call, tree lockable, + bool is_exclusive_lock, bool is_trylock, + struct pointer_set_t *live_excl_locks, + struct pointer_set_t *live_shared_locks) +{ + void **entry; + + if (!is_trylock) + { + /* Insert the lock to either exclusive or shared live lock set. */ + if (is_exclusive_lock) + pointer_set_insert(live_excl_locks, lockable); + else + pointer_set_insert(live_shared_locks, lockable); + } + else + { + /* If the primitive is a trylock, create a trylock_info structure and + insert it to trylock_info_map, which will be used later when we + analyze the if-statement whose condition is fed by the trylock. */ + struct trylock_info *tryinfo; + entry = pointer_map_insert (trylock_info_map, call); + if (!(*entry)) + { + tryinfo = XNEW (struct trylock_info); + tryinfo->is_exclusive = is_exclusive_lock; + tryinfo->locks = pointer_set_create(); + *entry = tryinfo; + } + else + { + tryinfo = (struct trylock_info *)*entry; + gcc_assert (tryinfo->locks + && tryinfo->is_exclusive == is_exclusive_lock); + } + pointer_set_insert (tryinfo->locks, lockable); + } +} + +/* This function handles function calls that acquire or try to acquire + locks (i.e. the functions annotated with exclusive_lock, shared_lock, + exclusive_trylock, or shared_trylock attribute). Besides adding to the + live lock sets the lock(s) it acquires (except for trylock calls), this + function also does the following: + + - Checks the lock acquisition order between the lock it acquires and + existing live locks. + + - Checks if any existing live lock is being acquired again + (i.e. re-entered). + + - If the function call is a constructor of a scoped lock, adds an entry + with the acquired lock to scopedlock_to_lock_map. + + - If the function call is a trylock, creates a trylock_info structure and + inserts it to trylock_info_map. + + - Records the source location of this function call in lock_locus_map + (as this is where the lock is acquired). + + This function handles one lock at a time, so if a locking primitive + acquires multiple locks, this function is called multiple times (see + process_function_attrs() below). + + Besides the call itself (CALL), we also pass in the function decl (FDECL). + While the function decl of a call can be easily extracted by calling + gimple_call_fndecl in most cases, it becomes a bit tricky when the function + is virtual as gimple_call_fndecl will simply return NULL. We will need to + get the function decl through the reference object in this case. + Since we have already done all the things necessary to get the function + decl earlier (see handle_call_gs()), instead of doing the whole dance again + here, we might as well pass in the function decl that we extracted earlier. + + The lock to be acquired is either the base object (i.e. BASE_OBJ) + when the primitive is a member function of a lockable class (e.g. "mu" in + mu->Lock()), or specified by an attribute parameter and passed in as ARG. + If ARG is an integer constant, it specifies the position of the primitive's + argument that corresponds to the lock to be acquired. */ + +static void +handle_lock_primitive_attrs (gimple call, tree fdecl, tree arg, tree base_obj, + bool is_exclusive_lock, bool is_trylock, + struct pointer_set_t *live_excl_locks, + struct pointer_set_t *live_shared_locks, + const location_t *locus) +{ + char lname[LOCK_NAME_LEN]; + void **entry; + tree lockable; + tree lockable_type; + + /* If ARG is not NULL, it specifies the lock to acquire. Otherwise, + BASE_OBJ is the lock. */ + if (!arg) + arg = base_obj; + else if (arg == error_mark_node) + { + /* If the arg is the universal lock (represented as the error_mark_node), + we don't need to do all the checks mentioned in the comments above. + Just add it to the lock set and return. */ + add_lock_to_lockset (call, arg, is_exclusive_lock, is_trylock, + live_excl_locks, live_shared_locks); + return; + } + /* When ARG is an integer that specifies the position of the + call's argument corresponding to the lock, or if its leftmost base is + a formal parameter, we need to grab the corresponding actual argument + of the call. */ + else if (TREE_CODE (arg) == INTEGER_CST) + arg = get_actual_argument_from_position (call, arg); + else if (TREE_CODE (get_leftmost_base_var (arg)) == PARM_DECL) + { + tree new_base + = get_actual_argument_from_parameter (call, fdecl, + get_leftmost_base_var (arg)); + arg = get_canonical_lock_expr (arg, NULL_TREE, false, new_base); + } + else if (base_obj) + arg = build_fully_qualified_lock (arg, base_obj); + + gcc_assert (arg); + + lockable = get_canonical_lock_expr (arg, NULL_TREE, false /* is_temp_expr */, + NULL_TREE); + + /* If there are unbound locks when the thread safety attributes were parsed, + we should try to bind them now if we see any lock declaration that + matches the name of the unbound lock. */ + if (unbound_lock_map + && (TREE_CODE (lockable) == VAR_DECL + || TREE_CODE (lockable) == PARM_DECL + || TREE_CODE (lockable) == FIELD_DECL)) + { + tree lock_id = DECL_NAME (lockable); + void **entry = pointer_map_contains (unbound_lock_map, lock_id); + if (entry) + *entry = lockable; + } + + gcc_assert (fdecl); + lockable_type = DECL_CONTEXT (fdecl); + if (lockable_type && !TYPE_P (lockable_type)) + lockable_type = NULL_TREE; + + /* Check if the lock primitive is actually a constructor of a scoped lock. + If so, insert to scopedlock_to_lock_map the scoped lock object along + with the lock it acquires. */ + if (!is_trylock + && lockable_type + && lookup_attribute("scoped_lockable", TYPE_ATTRIBUTES (lockable_type))) + { + if (TREE_CODE (base_obj) == ADDR_EXPR) + { + tree scoped_lock = TREE_OPERAND (base_obj, 0); + void **entry; + if (TREE_CODE (scoped_lock) == SSA_NAME) + scoped_lock = SSA_NAME_VAR (scoped_lock); + gcc_assert(TREE_CODE (scoped_lock) == VAR_DECL); + entry = pointer_map_insert (scopedlock_to_lock_map, scoped_lock); + *entry = lockable; + } + } + + /* Check if the lock is already held. */ + if (pointer_set_contains(live_excl_locks, lockable) + || pointer_set_contains(live_shared_locks, lockable)) + { + if (warn_thread_reentrant_lock) + { + void **entry = pointer_map_contains (lock_locus_map, lockable); + if (entry) + warning_at (*locus, OPT_Wthread_safety, + G_("Try to acquire lock %s that is already held" + " (previously acquired at line %d)"), + dump_expr_tree (lockable, lname), + LOCATION_LINE (*((location_t *) *entry))); + else + warning_at (*locus, OPT_Wthread_safety, + G_("Try to acquire lock %s that is already held" + " (at function entry)"), + dump_expr_tree (lockable, lname)); + } + /* Normally when we have detected a lock re-entrant issue here, we can + simply return. However, if this primitive is a trylock, we still + need to create an entry in the trylock_info_map (which will happen + later) regardless. Otherwise, the assertion that every trylock call + always has an entry in the map will fail later. */ + if (!is_trylock) + return; + } + + /* Check the lock acquisition order. */ + check_locking_order (lockable, live_excl_locks, live_shared_locks, locus); + + /* Record the source location where the lock is acquired. */ + entry = pointer_map_insert (lock_locus_map, lockable); + if (!(*entry)) + *entry = XNEW (location_t); + *((location_t *) *entry) = *locus; + + add_lock_to_lockset (call, lockable, is_exclusive_lock, is_trylock, + live_excl_locks, live_shared_locks); +} + +/* A helper function that removes the LOCKABLE from either LIVE_EXCL_LOCKS or + LIVE_SHARED_LOCKS, and returns the canonical form of LOCKABLE. If LOCKABLE + does not exist in either lock set, return NULL_TREE. */ + +static tree +remove_lock_from_lockset (tree lockable, struct pointer_set_t *live_excl_locks, + struct pointer_set_t *live_shared_locks) +{ + tree lock_contained; + + if ((lock_contained = lock_set_contains(live_excl_locks, lockable, NULL_TREE, + false)) != NULL_TREE) + pointer_set_delete (live_excl_locks, lock_contained); + else if ((lock_contained = lock_set_contains(live_shared_locks, lockable, + NULL_TREE, false)) != NULL_TREE) + pointer_set_delete (live_shared_locks, lock_contained); + + return lock_contained; +} + +/* This function handles function calls that release locks (i.e. the + functions annotated with the "unlock" attribute). Besides taking the + lock out of the live lock set, it also checks whether the user code + is trying to release a lock that's not currently held. For the + explanations on parameters FDECL, ARG, and BASE_OBJ, please see the + comments for handle_lock_primitive_attrs above. */ + +static void +handle_unlock_primitive_attr (gimple call, tree fdecl, tree arg, tree base_obj, + struct bb_threadsafe_info *bbinfo, + const location_t *locus) +{ + struct pointer_set_t *live_excl_locks = bbinfo->live_excl_locks; + struct pointer_set_t *live_shared_locks = bbinfo->live_shared_locks; + char lname[LOCK_NAME_LEN]; + tree lockable = NULL_TREE; + tree lock_released; + bool is_weak_unlock = false; + + /* Check if the unlock attribute specifies a lock or the position of the + primitive's argument corresponding to the lock. */ + if (arg) + { + /* When ARG is an integer that specifies the position of the + call's argument corresponding to the lock, or if its leftmost base is + a formal parameter, we need to grab the corresponding actual argument + of the call. */ + if (TREE_CODE (arg) == INTEGER_CST) + lockable = get_actual_argument_from_position (call, arg); + else if (TREE_CODE (get_leftmost_base_var (arg)) == PARM_DECL) + { + tree fdecl = gimple_call_fndecl (call); + tree new_base = get_actual_argument_from_parameter ( + call, fdecl, get_leftmost_base_var (arg)); + lockable = get_canonical_lock_expr (arg, NULL_TREE, false, new_base); + } + else if (base_obj) + lockable = build_fully_qualified_lock (arg, base_obj); + else + lockable = arg; + gcc_assert (lockable); + lockable = get_canonical_lock_expr (lockable, NULL_TREE, + false /* is_temp_expr */, NULL_TREE); + } + else + { + gcc_assert (base_obj); + + /* Check if the primitive is an unlock routine (e.g. the destructor or + a release function) of a scoped_lock. If so, get the lock that is + being released from scopedlock_to_lock_map. */ + if (TREE_CODE (base_obj) == ADDR_EXPR) + { + tree scoped_lock = TREE_OPERAND (base_obj, 0); + if (TREE_CODE (scoped_lock) == SSA_NAME) + scoped_lock = SSA_NAME_VAR (scoped_lock); + /* A scoped lock should be a local variable. */ + if (TREE_CODE (scoped_lock) == VAR_DECL) + { + void **entry = pointer_map_contains (scopedlock_to_lock_map, + scoped_lock); + if (entry) + { + gcc_assert (fdecl); + lockable = (tree) *entry; + /* Since this is a scoped lock, if the unlock routine is + not the destructor, we assume it is a release function + (e.g. std::unique_lock::release()). And therefore the + lock is considered weakly released and should be added + to the weak released lock set. */ + if (!lang_hooks.decl_is_destructor (fdecl)) + is_weak_unlock = true; + } + } + } + /* If the function is not a destructor of a scoped_lock, base_obj + is the lock. */ + if (!lockable) + lockable = get_canonical_lock_expr (base_obj, NULL_TREE, + false /* is_temp_expr */, + NULL_TREE); + } + + /* Remove the lock from the live lock set and, if it is not currently held, + warn about the issue. */ + if ((lock_released = remove_lock_from_lockset (lockable, live_excl_locks, + live_shared_locks)) + != NULL_TREE) + { + if (is_weak_unlock) + { + gcc_assert (bbinfo->weak_released_locks); + pointer_set_insert (bbinfo->weak_released_locks, lock_released); + } + } + else if (!is_weak_unlock + && ((lock_released = + lock_set_contains (bbinfo->weak_released_locks, lockable, + NULL_TREE, false)) != NULL_TREE)) + { + /* If the unlock function is not a weak release and the lock is currently + in the weak release set, we need to remove it from the set as it is + no longer considered weakly released after this point. */ + pointer_set_delete (bbinfo->weak_released_locks, lock_released); + } + else if (warn_thread_mismatched_lock_acq_rel) + warning_at (*locus, OPT_Wthread_safety, + G_("Try to unlock %s that was not acquired"), + dump_expr_tree (lockable, lname)); +} + +/* A helper function for handling function "locks_excluded" attribute. + Check if LOCK is in the current live lock sets and emit warnings if so. + + LOCK: the lock being examined. + FDECL: function decl of the call. + BASE_OBJ: base object if FDECL is a method (member function). + LIVE_EXCL_LOCKS: current live exclusive lock set. + LIVE_SHARED LOCKS: current live shared lock set. + LOCUS: location info of the call. */ + +static void +check_func_lock_excluded (tree lock, tree fdecl, tree base_obj, + const struct pointer_set_t *live_excl_locks, + const struct pointer_set_t *live_shared_locks, + const location_t *locus) +{ + tree lock_contained; + + /* LOCK could be NULL if the front-end/parser cannot recognize it. + Simply ignore it and return. */ + if (!lock) + return; + + /* When the base obj tree is not an ADDR_EXPR, which means it is a + pointer (i.e. base->foo() or foo(base)), we will need to create + a new base that is INDIRECT_REF so that we would be able to form + a correct full expression for a lock later. On the other hand, + if the base obj is an ADDR_EXPR (i.e. base.foo() or foo(&base)), + we need to remove the address-taken operation. */ + if (base_obj) + { + tree canon_base = get_canonical_lock_expr (base_obj, NULL_TREE, + true /* is_temp_expr */, + NULL_TREE); + if (TREE_CODE (canon_base) != ADDR_EXPR) + { + gcc_assert (POINTER_TYPE_P (TREE_TYPE (canon_base))); + base_obj = build1 (INDIRECT_REF, + TREE_TYPE (TREE_TYPE (canon_base)), + canon_base); + } + else + base_obj = TREE_OPERAND (canon_base, 0); + } + + if (!DECL_P (lock)) + lock = get_canonical_lock_expr (lock, NULL_TREE, false /* is_temp_expr */, + NULL_TREE); + + /* Check if the excluded lock is in the live lock sets when the + function is called. If so, issue a warning. */ + if ((lock_contained = lock_set_contains (live_excl_locks, lock, + base_obj, true)) + || (lock_contained = lock_set_contains (live_shared_locks, lock, + base_obj, true))) + { + char lname[LOCK_NAME_LEN]; + void **entry = pointer_map_contains (lock_locus_map, lock_contained); + if (entry) + warning_at (*locus, OPT_Wthread_safety, + G_("Cannot call function %qE with lock %s held" + " (previously acquired at line %d)"), + DECL_NAME (fdecl), + dump_expr_tree (lock_contained, lname), + LOCATION_LINE (*((location_t *) *entry))); + else + warning_at (*locus, OPT_Wthread_safety, + G_("Cannot call function %qE with lock %s held" + " (at function entry)"), + DECL_NAME (fdecl), + dump_expr_tree (lock_contained, lname)); + } +} + +/* Function lock requirement type. */ + +enum FUNC_LOCK_REQ_TYPE { + FLR_EXCL_LOCK_REQUIRED, + FLR_SHARED_LOCK_REQUIRED, + FLR_LOCK_EXCLUDED +}; + +/* Handle function lock requirement attributes ("exclusive_locks_required", + "shared_locks_required", and "locks_excluded"). + + CALL: function/method call that's currently being examined. + FDECL: function/method decl of the call. + BASE_OBJ: base object if FDECL is a method (member function). + ATTR: attribute of type FUNC_LOCK_REQ_TYPE. + REQ_TYPE: function lock requirement type. + LIVE_EXCL_LOCKS: current live exclusive lock set. + LIVE_SHARED LOCKS: current live shared lock set. + LOCUS: location info of the call. */ + +static void +handle_function_lock_requirement (gimple call, tree fdecl, tree base_obj, + tree attr, enum FUNC_LOCK_REQ_TYPE req_type, + const struct pointer_set_t *live_excl_locks, + const struct pointer_set_t *live_shared_locks, + const location_t *locus) +{ + tree arg; + tree lock; + + for (arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg)) + { + tree tmp_base_obj = base_obj; + lock = TREE_VALUE (arg); + gcc_assert (lock); + /* If lock is the error_mark_node, just set it to NULL_TREE so that + we will reduce the level of checking later. (i.e. Only check whether + there is any live lock at this point in check_lock_required and + ignore the lock in check_func_lock_excluded.) */ + if (lock == error_mark_node) + lock = NULL_TREE; + else if (TREE_CODE (lock) == INTEGER_CST) + { + lock = get_actual_argument_from_position (call, lock); + /* If the lock is a function argument, we don't want to + prepend the base object to the lock name. Set the + TMP_BASE_OBJ to NULL. */ + tmp_base_obj = NULL_TREE; + } + /* If LOCK's leftmost base is a formal parameter, we need to grab the + corresponding actual argument of the call and replace the formal + parameter with the actual argument in LOCK. */ + else if (TREE_CODE (get_leftmost_base_var (lock)) == PARM_DECL) + { + tree new_base = get_actual_argument_from_parameter ( + call, fdecl, get_leftmost_base_var (lock)); + lock = get_canonical_lock_expr (lock, NULL_TREE, false, new_base); + tmp_base_obj = NULL_TREE; + } + + if (req_type == FLR_EXCL_LOCK_REQUIRED) + check_lock_required (lock, fdecl, tmp_base_obj, + false /* is_indirect_ref */, + live_excl_locks, live_shared_locks, + locus, TSA_WRITE); + else if (req_type == FLR_SHARED_LOCK_REQUIRED) + check_lock_required (lock, fdecl, tmp_base_obj, + false /* is_indirect_ref */, + live_excl_locks, live_shared_locks, + locus, TSA_READ); + else + { + gcc_assert (req_type == FLR_LOCK_EXCLUDED); + check_func_lock_excluded (lock, fdecl, tmp_base_obj, + live_excl_locks, live_shared_locks, locus); + } + } +} + +/* The main routine that handles the thread safety attributes for + functions. CALL is the call expression being analyzed. FDECL is its + corresponding function decl tree. LIVE_EXCL_LOCKS and LIVE_SHARED_LOCKS + are the live lock sets when the control flow reaches this call expression. + LOCUS is the source location of the call expression. */ + +static void +process_function_attrs (gimple call, tree fdecl, + struct bb_threadsafe_info *current_bb_info, + const location_t *locus) +{ + struct pointer_set_t *live_excl_locks = current_bb_info->live_excl_locks; + struct pointer_set_t *live_shared_locks = current_bb_info->live_shared_locks; + tree attr = NULL_TREE; + tree base_obj = NULL_TREE; + bool is_exclusive_lock; + bool is_trylock; + + gcc_assert (is_gimple_call (call)); + + /* First check if the function call is annotated with any escape-hatch + related attributes and set/reset the corresponding flags if so. */ + if (lookup_attribute("ignore_reads_begin", DECL_ATTRIBUTES (fdecl)) + != NULL_TREE) + current_bb_info->reads_ignored = true; + if (lookup_attribute("ignore_reads_end", DECL_ATTRIBUTES (fdecl)) + != NULL_TREE) + current_bb_info->reads_ignored = false; + if (lookup_attribute("ignore_writes_begin", DECL_ATTRIBUTES (fdecl)) + != NULL_TREE) + current_bb_info->writes_ignored = true; + if (lookup_attribute("ignore_writes_end", DECL_ATTRIBUTES (fdecl)) + != NULL_TREE) + current_bb_info->writes_ignored = false; + + /* If the function is a class member, the first argument of the function + (i.e. "this" pointer) would be the base object. */ + if (TREE_CODE (TREE_TYPE (fdecl)) == METHOD_TYPE) + base_obj = gimple_call_arg (call, 0); + + /* Check whether this is a locking primitive of any kind. */ + if ((attr = lookup_attribute("exclusive_lock", + DECL_ATTRIBUTES (fdecl))) != NULL_TREE) + { + is_exclusive_lock = true; + is_trylock = false; + } + else if ((attr = lookup_attribute("exclusive_trylock", + DECL_ATTRIBUTES (fdecl))) != NULL_TREE) + { + is_exclusive_lock = true; + is_trylock = true; + } + else if ((attr = lookup_attribute("shared_lock", + DECL_ATTRIBUTES (fdecl))) != NULL_TREE) + { + is_exclusive_lock = false; + is_trylock = false; + } + else if ((attr = lookup_attribute("shared_trylock", + DECL_ATTRIBUTES (fdecl))) != NULL_TREE) + { + is_exclusive_lock = false; + is_trylock = true; + } + + /* Handle locking primitives */ + if (attr) + { + if (TREE_VALUE (attr)) + { + int succ_retval = 0; + tree arg = TREE_VALUE (attr); + /* If the locking primitive is a trylock, the first argument of + the attribute specifies the return value of a successful lock + acquisition. */ + if (is_trylock) + { + gcc_assert (TREE_CODE (TREE_VALUE (arg)) == INTEGER_CST); + succ_retval = TREE_INT_CST_LOW (TREE_VALUE (arg)); + arg = TREE_CHAIN (arg); + } + + /* If the primitive is a trylock, after we consume the first + argument of the attribute, there might not be any argument + left. So we need to check if arg is NULL again here. */ + if (arg) + { + /* If the locking primitive attribute specifies multiple locks + in its arguments, we iterate through the argument list and + handle each of the locks individually. */ + for (; arg; arg = TREE_CHAIN (arg)) + handle_lock_primitive_attrs (call, fdecl, TREE_VALUE (arg), + base_obj, is_exclusive_lock, + is_trylock, live_excl_locks, + live_shared_locks, locus); + } + else + { + /* If the attribute does not have any argument left, the lock to + be acquired is the base obj (e.g. "mu" in mu->TryLock()). */ + handle_lock_primitive_attrs (call, fdecl, NULL_TREE, base_obj, + is_exclusive_lock, is_trylock, + live_excl_locks, live_shared_locks, + locus); + } + /* If the primitive is a trylock, fill in the return value on + successful lock acquisition in the trylock_info that was + created in handle_lock_primitive_attrs. */ + if (is_trylock) + { + struct trylock_info *tryinfo; + void **entry = pointer_map_contains (trylock_info_map, call); + gcc_assert (entry); + tryinfo = (struct trylock_info *)*entry; + tryinfo->succ_retval = succ_retval; + } + } + else + { + /* If the attribute does not have any argument, the lock to be + acquired is the base obj (e.g. "mu" in mu->Lock()). */ + gcc_assert (!is_trylock); + handle_lock_primitive_attrs (call, fdecl, NULL_TREE, base_obj, + is_exclusive_lock, is_trylock, + live_excl_locks, live_shared_locks, + locus); + } + } + /* Handle unlocking primitive */ + else if ((attr = lookup_attribute ("unlock", DECL_ATTRIBUTES (fdecl))) + != NULL_TREE) + { + if (TREE_VALUE (attr)) + { + /* If the unlocking primitive attribute specifies multiple locks + in its arguments, we iterate through the argument list and + handle each of the locks individually. */ + tree arg; + for (arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg)) + { + /* If the unlock arg is an error_mark_node, which means an + unsupported lock name/expression was encountered during + parsing, the conservative approach to take is not to check + the lock acquire/release mismatch issue in the current + function by setting the flag to 0. Note that the flag will + be restored to its original value after finishing analyzing + the current function. */ + if (TREE_VALUE (arg) == error_mark_node) + { + warn_thread_mismatched_lock_acq_rel = 0; + continue; + } + handle_unlock_primitive_attr (call, fdecl, TREE_VALUE (arg), + base_obj, current_bb_info, locus); + } + } + else + /* If the attribute does not have any argument, the lock to be + released is the base obj (e.g. "mu" in mu->Unlock()). */ + handle_unlock_primitive_attr (call, fdecl, NULL_TREE, base_obj, + current_bb_info, locus); + } + + if (warn_thread_unguarded_func) + { + /* Handle the attributes specifying the lock requirements of + functions. */ + if ((attr = lookup_attribute ("exclusive_locks_required", + DECL_ATTRIBUTES (fdecl))) != NULL_TREE) + handle_function_lock_requirement (call, fdecl, base_obj, attr, + FLR_EXCL_LOCK_REQUIRED, + live_excl_locks, live_shared_locks, + locus); + + if ((attr = lookup_attribute ("shared_locks_required", + DECL_ATTRIBUTES (fdecl))) != NULL_TREE) + handle_function_lock_requirement (call, fdecl, base_obj, attr, + FLR_SHARED_LOCK_REQUIRED, + live_excl_locks, live_shared_locks, + locus); + + if ((attr = lookup_attribute ("locks_excluded", DECL_ATTRIBUTES (fdecl))) + != NULL_TREE) + handle_function_lock_requirement (call, fdecl, base_obj, attr, + FLR_LOCK_EXCLUDED, + live_excl_locks, live_shared_locks, + locus); + } +} + +/* The main routine that handles the attributes specifying variables' lock + requirements. */ + +static void +process_guarded_by_attrs (tree vdecl, tree base_obj, bool is_indirect_ref, + const struct pointer_set_t *excl_locks, + const struct pointer_set_t *shared_locks, + const location_t *locus, enum access_mode mode) +{ + tree attr; + tree lockable = NULL_TREE; + /* A flag indicating whether the attribute is {point_to_}guarded_by with + a lock specified or simply {point_to_}guarded. */ + bool lock_specified = true; + + if (!warn_thread_unguarded_var) + return; + + if (is_indirect_ref) + { + attr = lookup_attribute ("point_to_guarded_by", DECL_ATTRIBUTES (vdecl)); + if (!attr) + { + attr = lookup_attribute ("point_to_guarded", + DECL_ATTRIBUTES (vdecl)); + lock_specified = false; + } + } + else + { + attr = lookup_attribute ("guarded_by", DECL_ATTRIBUTES (vdecl)); + if (!attr) + { + attr = lookup_attribute ("guarded", DECL_ATTRIBUTES (vdecl)); + lock_specified = false; + } + } + + /* If the variable does not have an attribute specifying that it should + be protected by a lock, simply return. */ + if (!attr) + return; + + /* If the variable is a compiler-created temporary pointer, grab the + original variable's decl from the debug expr (as we don't want to + print out the temp name in the warnings. For reasons why we only + need to do this for pointers, see lookup_tmp_var() in gimplify.c. */ + if (is_indirect_ref && DECL_ARTIFICIAL (vdecl)) + { + gcc_assert (DECL_DEBUG_EXPR_IS_FROM (vdecl) && DECL_DEBUG_EXPR (vdecl)); + vdecl = DECL_DEBUG_EXPR (vdecl); + gcc_assert (DECL_P (vdecl)); + } + + if (lock_specified) + { + gcc_assert (TREE_VALUE (attr)); + lockable = TREE_VALUE (TREE_VALUE (attr)); + gcc_assert (lockable); + } + + check_lock_required (lockable, vdecl, base_obj, is_indirect_ref, + excl_locks, shared_locks, locus, mode); +} + +/* This routine is called when we see an indirect reference in our + analysis of expressions. In an indirect reference, the pointer itself + is accessed as a read. The parameter MODE indicates how the memory + location pointed to by the PTR is being accessed (either read or write). */ + +static void +handle_indirect_ref (tree ptr, struct pointer_set_t *excl_locks, + struct pointer_set_t *shared_locks, + const location_t *locus, enum access_mode mode) +{ + tree vdecl; + + /* The pointer itself is accessed as a read */ + analyze_expr (ptr, NULL_TREE, false /* is_indirect_ref */, excl_locks, + shared_locks, locus, TSA_READ); + + if (TREE_CODE (ptr) == SSA_NAME) + { + vdecl = SSA_NAME_VAR (ptr); + if (!DECL_NAME (vdecl)) + { + gimple def_stmt = SSA_NAME_DEF_STMT (ptr); + if (is_gimple_assign (def_stmt) + && (get_gimple_rhs_class (gimple_assign_rhs_code (def_stmt)) + == GIMPLE_SINGLE_RHS)) + vdecl = gimple_assign_rhs1 (def_stmt); + } + } + else + vdecl = ptr; + + if (DECL_P (vdecl)) + process_guarded_by_attrs (vdecl, NULL_TREE, true /* is_indirect_ref */, + excl_locks, shared_locks, locus, mode); + else + analyze_expr (vdecl, NULL_TREE, true /* is_indirect_ref */, + excl_locks, shared_locks, locus, mode); + + return; +} + +/* The main routine that handles gimple call statements. */ + +static void +handle_call_gs (gimple call, struct bb_threadsafe_info *current_bb_info) +{ + tree fdecl = gimple_call_fndecl (call); + int num_args = gimple_call_num_args (call); + int arg_index = 0; + tree arg_type = NULL_TREE; + tree arg; + tree lhs; + location_t locus; + + if (!gimple_has_location (call)) + locus = input_location; + else + locus = gimple_location (call); + + /* If the callee fndecl is NULL, check if it is a virtual function, + and if so, try to get its decl through the reference object. */ + if (!fdecl) + { + tree callee = gimple_call_fn (call); + if (TREE_CODE (callee) == OBJ_TYPE_REF) + { + tree objtype = TREE_TYPE (TREE_TYPE (OBJ_TYPE_REF_OBJECT (callee))); + fdecl = lang_hooks.get_virtual_function_decl (callee, objtype); + } + } + + /* The callee fndecl could be NULL, e.g., when the function is passed in + as an argument. */ + if (fdecl) + { + arg_type = TYPE_ARG_TYPES (TREE_TYPE (fdecl)); + if (TREE_CODE (TREE_TYPE (fdecl)) == METHOD_TYPE) + { + /* If an object, x, is guarded by a lock, whether or not + calling x.foo() requires an exclusive lock depends on + if foo() is const. */ + enum access_mode rw_mode = + lang_hooks.decl_is_const_member_func (fdecl) ? TSA_READ + : TSA_WRITE; + + /* A method should have at least one argument, i.e."this" pointer */ + gcc_assert (num_args); + arg = gimple_call_arg (call, 0); + + /* If the base object (i.e. "this" object) is a SSA name of a temp + variable, as shown in the following example (assuming the source + code is 'base.method_call()'): + + D.5041_2 = &this_1(D)->base; + result.0_3 = method_call (D.5041_2); + + we will need to get the rhs of the SSA def of the temp variable + in order for the analysis to work correctly. */ + if (TREE_CODE (arg) == SSA_NAME) + { + tree vdecl = SSA_NAME_VAR (arg); + if (DECL_ARTIFICIAL (vdecl) + && !gimple_nop_p (SSA_NAME_DEF_STMT (arg))) + { + gimple def_stmt = SSA_NAME_DEF_STMT (arg); + if (is_gimple_assign (def_stmt) + && (get_gimple_rhs_class (gimple_assign_rhs_code ( + def_stmt)) == GIMPLE_SINGLE_RHS)) + arg = gimple_assign_rhs1 (def_stmt); + } + } + + /* Analyze the base object ("this") if we are not instructed to + ignore it. */ + if (!(current_bb_info->reads_ignored && rw_mode == TSA_READ) + && !(current_bb_info->writes_ignored && rw_mode == TSA_WRITE)) + { + if (TREE_CODE (arg) == ADDR_EXPR) + { + /* Handle smart/scoped pointers. They are not actual + pointers but they can be annotated with + "point_to_guarded_by" attribute and have overloaded "->" + and "*" operators, so we treat them as normal pointers. */ + if ((DECL_NAME (fdecl) == maybe_get_identifier ("operator->")) + || (DECL_NAME (fdecl) + == maybe_get_identifier ("operator*"))) + handle_indirect_ref(TREE_OPERAND (arg, 0), + current_bb_info->live_excl_locks, + current_bb_info->live_shared_locks, + &locus, rw_mode); + else + /* Handle the case of x.foo() or foo(&x) */ + analyze_expr (TREE_OPERAND (arg, 0), NULL_TREE, + false /* is_indirect_ref */, + current_bb_info->live_excl_locks, + current_bb_info->live_shared_locks, &locus, + rw_mode); + } + else + { + /* Handle the case of x->foo() or foo(x) */ + gcc_assert (POINTER_TYPE_P (TREE_TYPE (arg))); + handle_indirect_ref(arg, current_bb_info->live_excl_locks, + current_bb_info->live_shared_locks, + &locus, rw_mode); + } + } + + /* Advance to the next argument */ + ++arg_index; + arg_type = TREE_CHAIN (arg_type); + } + } + + /* Analyze the call's arguments if we are not instructed to ignore the + reads. */ + if (!current_bb_info->reads_ignored + && (!fdecl + || !lookup_attribute("unprotected_read", DECL_ATTRIBUTES (fdecl)))) + { + for ( ; arg_index < num_args; ++arg_index) + { + arg = gimple_call_arg (call, arg_index); + if (!CONSTANT_CLASS_P (arg)) + { + /* In analyze_expr routine, an address-taken expr (e.g. &x) will + be skipped because the variable itself is not actually + accessed. However, if an argument which is an address-taken + expr, say &x, is passed into a function for a reference + parameter, we want to treat it as a use of x because this is + what users expect and most likely x will be used in the + callee body anyway. */ + if (arg_type + && TREE_CODE (TREE_VALUE (arg_type)) == REFERENCE_TYPE) + { + tree canon_arg = arg; + /* The argument could be an SSA_NAME. Try to get the rhs of + its SSA_DEF. */ + if (TREE_CODE (arg) == SSA_NAME) + { + tree vdecl = SSA_NAME_VAR (arg); + if (DECL_ARTIFICIAL (vdecl) + && !gimple_nop_p (SSA_NAME_DEF_STMT (arg))) + { + gimple def_stmt = SSA_NAME_DEF_STMT (arg); + if (is_gimple_assign (def_stmt) + && (get_gimple_rhs_class ( + gimple_assign_rhs_code (def_stmt)) + == GIMPLE_SINGLE_RHS)) + canon_arg = gimple_assign_rhs1 (def_stmt); + } + } + /* For an argument which is an ADDR_EXPR, say &x, that + corresponds to a reference parameter, remove the + address-taken operator and only pass 'x' to + analyze_expr. */ + if (TREE_CODE (canon_arg) == ADDR_EXPR) + arg = TREE_OPERAND (canon_arg, 0); + } + + analyze_expr (arg, NULL_TREE, false /* is_indirect_ref */, + current_bb_info->live_excl_locks, + current_bb_info->live_shared_locks, &locus, + TSA_READ); + } + + if (arg_type) + arg_type = TREE_CHAIN (arg_type); + } + } + + /* Analyze the call's lhs if it exists and we are not instructed to ignore + the writes. */ + lhs = gimple_call_lhs (call); + if (lhs != NULL_TREE && !current_bb_info->writes_ignored) + analyze_expr (lhs, NULL_TREE, false /* is_indirect_ref */, + current_bb_info->live_excl_locks, + current_bb_info->live_shared_locks, &locus, TSA_WRITE); + + /* Process the attributes associated with the callee func decl. + Note that we want to process the arguments first so that the callee + func decl attributes have no effects on the arguments. */ + if (fdecl) + process_function_attrs (call, fdecl, current_bb_info, &locus); + + return; +} + +/* The main routine that handles decls and expressions. It in turn calls + other helper routines to analyze different kinds of trees. + + EXPR is the expression/decl being analyzed. + BASE_OBJ is the base component of EXPR if EXPR is a component reference + (e.g. b.a). + EXCL_LOCKS and SHARED_LOCKS are the live lock sets when the control flow + reaches EXPR. + LOCUS is the source location of EXPR. + MODE indicates whether the access is a read or a write. */ + +static void +analyze_expr (tree expr, tree base_obj, bool is_indirect_ref, + struct pointer_set_t *excl_locks, + struct pointer_set_t *shared_locks, + const location_t *locus, enum access_mode mode) +{ + tree vdecl; + + if (EXPR_P (expr)) + { + int nops; + int i; + /* For an address-taken expression (i.e. &x), the memory location of + the operand is not actually accessed. So no thread safe check + necessary here. */ + if (TREE_CODE (expr) == ADDR_EXPR) + return; + + if (TREE_CODE (expr) == INDIRECT_REF || TREE_CODE (expr) == MEM_REF) + { + tree ptr = TREE_OPERAND (expr, 0); + handle_indirect_ref (ptr, excl_locks, shared_locks, locus, mode); + return; + } + + /* For a component reference, we need to look at both base and + component trees. */ + if (TREE_CODE (expr) == COMPONENT_REF) + { + tree base = TREE_OPERAND (expr, 0); + tree component = TREE_OPERAND (expr, 1); + analyze_expr (base, NULL_TREE, false /* is_indirect_ref */, + excl_locks, shared_locks, locus, mode); + analyze_expr (component, base, is_indirect_ref, + excl_locks, shared_locks, locus, mode); + return; + } + + /* For all other expressions, just iterate through their operands + and call analyze_expr on them recursively */ + nops = TREE_OPERAND_LENGTH (expr); + for (i = 0; i < nops; i++) + { + tree op = TREE_OPERAND (expr, i); + if (op != 0 && !CONSTANT_CLASS_P (op)) + analyze_expr (op, base_obj, false /* is_indirect_ref */, + excl_locks, shared_locks, locus, mode); + } + + return; + } + + /* If EXPR is a ssa name, grab its original variable decl. */ + if (TREE_CODE (expr) == SSA_NAME) + { + vdecl = SSA_NAME_VAR (expr); + /* If VDECL is a nameless temp variable and we are analyzing an indirect + reference, we will need to grab and analyze the RHS of its SSA def + because the RHS is the actual pointer that gets dereferenced. + For example, in the following snippet of gimple IR, when we first + analyzed S1, we only saw a direct access to foo.a_. Later, when + analyzing the RHS of S2 (i.e. *D1803_1), which is an indirect + reference, we need to look at foo.a_ again because what's really + being referenced is *foo.a_. + + S1: D.1803_1 = foo.a_; + S2: res.1_4 = *D.1803_1; */ + if (!DECL_NAME (vdecl) && is_indirect_ref) + { + gimple def_stmt = SSA_NAME_DEF_STMT (expr); + if (is_gimple_assign (def_stmt) + && (get_gimple_rhs_class (gimple_assign_rhs_code (def_stmt)) + == GIMPLE_SINGLE_RHS)) + vdecl = gimple_assign_rhs1 (def_stmt); + } + } + else if (DECL_P (expr)) + vdecl = expr; + else + return; + + if (DECL_P (vdecl)) + process_guarded_by_attrs (vdecl, base_obj, is_indirect_ref, + excl_locks, shared_locks, locus, mode); + else + analyze_expr (vdecl, base_obj, is_indirect_ref, excl_locks, shared_locks, + locus, mode); +} + +/* This is a helper function called by handle_cond_gs() to check if + GS is a trylock call (or a simple expression fed by a trylock + call that involves only logic "not" operations). And if so, grab and + return the corresponding trylock_info structure. Otherwise, return NULL. + In the process, *LOCK_ON_TRUE_PATH is set to indicate whether the true + (control flow) path should be taken when the lock is successfully + acquired. */ + +static struct trylock_info * +get_trylock_info(gimple gs, bool *lock_on_true_path) +{ + while (1) + { + if (is_gimple_assign (gs)) + { + enum tree_code subcode = gimple_assign_rhs_code (gs); + if (subcode == SSA_NAME) + { + gs = SSA_NAME_DEF_STMT (gimple_assign_rhs1 (gs)); + continue; + } + else if (subcode == TRUTH_NOT_EXPR) + { + /* If the expr is a logic "not" operation, negate the value + pointed to by lock_on_true_apth and continue trace back + the expr's operand. */ + *lock_on_true_path = !*lock_on_true_path; + gs = SSA_NAME_DEF_STMT (gimple_assign_rhs1 (gs)); + continue; + } + else + return NULL; + } + else if (is_gimple_call (gs)) + { + tree fdecl = gimple_call_fndecl (gs); + /* The function decl could be null in some cases, e.g. + a function pointer passed in as a parameter. */ + if (fdecl + && (lookup_attribute ("exclusive_trylock", + DECL_ATTRIBUTES (fdecl)) + || lookup_attribute ("shared_trylock", + DECL_ATTRIBUTES (fdecl)))) + { + void **entry = pointer_map_contains (trylock_info_map, gs); + gcc_assert (entry); + return (struct trylock_info *)*entry; + } + else + return NULL; + } + else + return NULL; + } + + gcc_unreachable (); + + return NULL; +} + +/* This routine determines whether the given condition statment (COND_STMT) is + fed by a trylock call through expressions involving only "not", "equal" + "not-equal" operations. Here are several examples where the condition + statements are fed by trylock calls: + + (1) if (mu.Trylock()) { + ... + } + + (2) bool a = mu.Trylock(); + bool b = !a; + if (b) { + ... + } + + (3) int a = pthread_mutex_trylock(mu); + bool b = (a == 1); + if (!b) { + ... + } + + (4) int a = pthread_mutex_trylock(mu); + bool b = (a != 0); + bool c = b; + if (c == true) { + ... + } + + If COND_STMT is determined to be fed by a trylock call, this routine + populates the data structure pointed to by CURRENT_BB_INFO, and + sets *LOCK_ON_TRUE_PATH to indicate whether the true (control flow) path + should be taken when the lock is successfully acquired. */ + +static void +handle_cond_gs (gimple cond_stmt, struct bb_threadsafe_info *current_bb_info) +{ + gimple gs = NULL; + bool lock_on_true_path = true; + bool is_cond_stmt = true; + edge true_edge, false_edge; + basic_block bb = gimple_bb (cond_stmt); + tree op0 = gimple_cond_lhs (cond_stmt); + tree op1 = gimple_cond_rhs (cond_stmt); + enum tree_code subcode = gimple_cond_code (cond_stmt); + + /* We only handle condition expressions with "equal" or "not-equal" + operations. */ + if (subcode != EQ_EXPR && subcode != NE_EXPR) + return; + + /* In the new gimple tuple IR, a single operand if-condition such as + + if (a) { + } + + would be represented as + + GIMPLE_COND <NE_EXPR, a, 0, TRUE_LABEL, FALSE_LABEL> + + Here we are trying this case and grab the SSA definition of a. */ + + if (TREE_CODE (op0) == SSA_NAME + && subcode == NE_EXPR + && TREE_CODE (op1) == INTEGER_CST + && TREE_INT_CST_LOW (op1) == 0) + { + gs = SSA_NAME_DEF_STMT (op0); + is_cond_stmt = false; + } + + /* Iteratively back-tracing the SSA definitions to determine if the + condition expression is fed by a trylock call. If so, record the + edge that indicates successful lock acquisition. */ + while (1) + { + if (is_cond_stmt || is_gimple_assign (gs)) + { + if (!is_cond_stmt) + { + gcc_assert (gs); + subcode = gimple_assign_rhs_code (gs); + } + + if (subcode == SSA_NAME) + { + gcc_assert (!is_cond_stmt); + gs = SSA_NAME_DEF_STMT (gimple_assign_rhs1 (gs)); + continue; + } + else if (subcode == TRUTH_NOT_EXPR) + { + gcc_assert (!is_cond_stmt); + lock_on_true_path = !lock_on_true_path; + gs = SSA_NAME_DEF_STMT (gimple_assign_rhs1 (gs)); + continue; + } + else if (subcode == EQ_EXPR || subcode == NE_EXPR) + { + struct trylock_info *tryinfo; + int const_op; + if (!is_cond_stmt) + { + op0 = gimple_assign_rhs1 (gs); + op1 = gimple_assign_rhs2 (gs); + } + if (TREE_CODE (op0) == INTEGER_CST + && TREE_CODE (op1) == SSA_NAME) + { + const_op = TREE_INT_CST_LOW (op0); + tryinfo = get_trylock_info (SSA_NAME_DEF_STMT (op1), + &lock_on_true_path); + } + else if (TREE_CODE (op1) == INTEGER_CST + && TREE_CODE (op0) == SSA_NAME) + { + const_op = TREE_INT_CST_LOW (op1); + tryinfo = get_trylock_info (SSA_NAME_DEF_STMT (op0), + &lock_on_true_path); + } + else + return; + + if (tryinfo) + { + struct pointer_set_t *edge_locks; + /* Depending on the operation (eq or neq) and whether the + succ_retval of the trylock is the same as the constant + integer operand, we might need to toggle the value + pointed to bylock_on_true_path. For example, if the + succ_retval of TryLock() is 0 and the cond expression is + (mu.TryLock() != 0), we need to negate the + lock_on_true_path value. */ + if ((tryinfo->succ_retval == const_op + && subcode == NE_EXPR) + || (tryinfo->succ_retval != const_op + && subcode == EQ_EXPR)) + lock_on_true_path = !lock_on_true_path; + + edge_locks = pointer_set_copy (tryinfo->locks); + if (tryinfo->is_exclusive) + current_bb_info->edge_exclusive_locks = edge_locks; + else + current_bb_info->edge_shared_locks = edge_locks; + break; + } + else + return; + } + else + return; + } + else if (is_gimple_call (gs)) + { + struct trylock_info *tryinfo; + tryinfo = get_trylock_info (gs, &lock_on_true_path); + if (tryinfo) + { + struct pointer_set_t *edge_locks; + /* If the succ_retval of the trylock is 0 (or boolean + "false"), we need to negate the value pointed to by + lock_on_true_path. */ + if (tryinfo->succ_retval == 0) + lock_on_true_path = !lock_on_true_path; + edge_locks = pointer_set_copy (tryinfo->locks); + if (tryinfo->is_exclusive) + current_bb_info->edge_exclusive_locks = edge_locks; + else + current_bb_info->edge_shared_locks = edge_locks; + break; + } + else + return; + } + else + return; + } + + gcc_assert (current_bb_info->edge_exclusive_locks + || current_bb_info->edge_shared_locks); + extract_true_false_edges_from_block (bb, &true_edge, &false_edge); + if (lock_on_true_path) + current_bb_info->trylock_live_edge = true_edge; + else + current_bb_info->trylock_live_edge = false_edge; +} + +/* This is a helper function to populate the LOCK_SET with the locks + specified in ATTR's arguments. */ + +static void +populate_lock_set_with_attr (struct pointer_set_t *lock_set, tree attr) +{ + tree arg; + + for (arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg)) + { + tree lock = TREE_VALUE (arg); + gcc_assert (lock); + /* If the lock is an integer specifying the argument position, grab + the corresponding formal parameter. */ + if (TREE_CODE (lock) == INTEGER_CST) + { + int lock_pos = TREE_INT_CST_LOW (lock); + int i; + tree parm; + for (parm = DECL_ARGUMENTS (current_function_decl), i = 1; + parm; + parm = TREE_CHAIN (parm), ++i) + if (i == lock_pos) + break; + gcc_assert (parm); + lock = parm; + } + + /* Canonicalize the lock before we add it to the lock set. */ + if (!DECL_P (lock)) + lock = get_canonical_lock_expr (lock, NULL_TREE, + false /* is_temp_expr */, NULL_TREE); + + /* Add the lock to the lock set. */ + pointer_set_insert (lock_set, lock); + + /* If there are unbound locks when the thread safety attributes were + parsed, we should try to bind them now if we see any lock declaration + that matches the name of the unbound lock. */ + if (unbound_lock_map + && (TREE_CODE (lock) == VAR_DECL + || TREE_CODE (lock) == PARM_DECL + || TREE_CODE (lock) == FIELD_DECL)) + { + tree lock_id = DECL_NAME (lock); + void **entry = pointer_map_contains (unbound_lock_map, lock_id); + if (entry) + *entry = lock; + } + } +} + +/* This is a helper function passed in (as a parameter) to pointer_set_traverse + when we traverse the set containing locks that are not properly released + and emit a warning message for each of them. By improper release, we meant + the places these locks are released are not control equivalent to where + they are acquired. The improperly-released lock set was calculated when we + reach a joint point during the data flow analysis. Any lock that is not + in all of the preceding basic blocks' live-out sets is considered not + released locally. REPORTED set contains the locks for which we have + already printed out a warning message. We use this set to avoid emitting + duplicate warnings for a lock. Here is an example why duplicate warnings + could be emitted if we don't keep a REPORTED set. + + B1: + mu.Lock() + + / \ \ + / \ \ + B2: B3: B4: + mu.Unlock() + \ / / + \ / / + + B5: + + When we reach B5, "mu" would be in the live out sets of B3 and B4, but + not that of B2. If we do a live out set intersection between B2 and B3 + first, and then intersect the resulting set with B4's live out set, we + could've emitted the warning message for "mu" twice if we had not kept + a reported set. */ + +static bool +warn_locally_unreleased_locks (const void *lock, void *reported) +{ + char lname[LOCK_NAME_LEN]; + void **entry; + tree lock_tree = CONST_CAST_TREE ((const_tree) lock); + location_t *loc; + struct pointer_set_t *reported_unreleased_locks; + + reported_unreleased_locks = (struct pointer_set_t *) reported; + + /* If this unreleased lock has been reported or is a universal lock (i.e. + error_mark_node), don't emit a warning message for it again. */ + if (lock != error_mark_node + && !pointer_set_contains (reported_unreleased_locks, lock)) + { + entry = pointer_map_contains (lock_locus_map, lock); + if (entry) + { + loc = (location_t *) *entry; + warning_at (*loc, OPT_Wthread_safety, + G_("Lock %s (acquired at line %d) is not released at" + " the end of its scope in function %qE"), + dump_expr_tree (lock_tree, lname), + LOCATION_LINE (*loc), + DECL_NAME (current_function_decl)); + } + else + warning_at (DECL_SOURCE_LOCATION (current_function_decl), + OPT_Wthread_safety, + G_("Lock %s (held at entry) is released on some but not all" + " control flow paths in function %qE"), + dump_expr_tree (lock_tree, lname), + DECL_NAME (current_function_decl)); + + pointer_set_insert (reported_unreleased_locks, lock); + } + + return true; +} + +/* This is a helper function passed in (as a parameter) to traverse_pointer_set + when we iterate through the set of locks that are not released at the end + of a function. A warning message is emitted for each of them unless they + were not acquired in the current function (i.e. acquired before calling + the current function). */ + +static bool +warn_unreleased_locks (const void *lock, void *locks_at_entry) +{ + /* If the unreleased lock was actually acquired before calling the current + function, we don't emit a warning for it as the lock is not expected to + be released in the current function anyway. Also if the lock is a + universal lock (i.e. error_mark_node), don't emit a warning either. */ + if (lock != error_mark_node + && !pointer_set_contains ((struct pointer_set_t *) locks_at_entry, lock)) + { + char lname[LOCK_NAME_LEN]; + void **entry = pointer_map_contains (lock_locus_map, lock); + tree lock_tree = CONST_CAST_TREE ((const_tree) lock); + location_t *loc; + gcc_assert (entry); + loc = (location_t *) *entry; + warning_at (*loc, OPT_Wthread_safety, + G_("Lock %s (acquired at line %d) is not released at the end" + " of function %qE"), + dump_expr_tree (lock_tree, lname), + LOCATION_LINE (*loc), + DECL_NAME (current_function_decl)); + } + + return true; +} + +/* This is a helper function passed in (as a parameter) to + pointer_map_traverse when we delete lock_locus_map. */ + +static bool +delete_lock_locations (const void * ARG_UNUSED (lock), + void **entry, void * ARG_UNUSED (data)) +{ + XDELETE (*entry); + return true; +} + +/* This is a helper function passed in (as a parameter) to + pointer_map_traverse when we delete trylock_info_map. */ + +static bool +delete_trylock_info (const void * ARG_UNUSED (call), + void **entry, void * ARG_UNUSED (data)) +{ + struct trylock_info *tryinfo = (struct trylock_info *)*entry; + gcc_assert (tryinfo); + pointer_set_destroy (tryinfo->locks); + XDELETE (tryinfo); + return true; +} + +/* Helper function for walk_gimple_stmt() that is called on each gimple + statement. Except for call statements and SSA definitions of namesless + temp variables, the operands of the statements will be analyzed by + analyze_op_r(). */ + +static tree +analyze_stmt_r (gimple_stmt_iterator *gsi, bool *handled_ops, + struct walk_stmt_info *wi) +{ + gimple stmt = gsi_stmt (*gsi); + struct bb_threadsafe_info *current_bb_info = + (struct bb_threadsafe_info *) wi->info; + + if (is_gimple_call (stmt)) + { + handle_call_gs (stmt, current_bb_info); + /* The arguments of the call is already analyzed in handle_call_gs. + Set *handled_ops to true to skip calling analyze_op_r later. */ + *handled_ops = true; + } + else if (gimple_code (stmt) == GIMPLE_COND) + handle_cond_gs (stmt, current_bb_info); + + return NULL_TREE; +} + +/* Helper function for walk_gimple_stmt() that is called on each operand of + a visited gimple statement. */ + +static tree +analyze_op_r (tree *tp, int *walk_subtrees, void *data) +{ + struct walk_stmt_info *wi = (struct walk_stmt_info *) data; + struct bb_threadsafe_info *current_bb_info = + (struct bb_threadsafe_info *) wi->info; + gimple stmt = gsi_stmt (wi->gsi); + enum access_mode mode = wi->is_lhs ? TSA_WRITE : TSA_READ; + location_t locus; + + /* Analyze the statement operand if we are not instructed to ignore the + reads or writes and if it is not a constant. */ + if (!(current_bb_info->reads_ignored && mode == TSA_READ) + && !(current_bb_info->writes_ignored && mode == TSA_WRITE) + && !CONSTANT_CLASS_P (*tp)) + { + if (!gimple_has_location (stmt)) + locus = input_location; + else + locus = gimple_location (stmt); + + analyze_expr(*tp, NULL_TREE, false /* is_indirect_ref */, + current_bb_info->live_excl_locks, + current_bb_info->live_shared_locks, &locus, mode); + } + + *walk_subtrees = 0; + + return NULL_TREE; +} + +/* Perform thread safety analysis using the attributes mentioned above + (see comments at the beginning of the file). The analysis pass uses + a single-pass (or single iteration) data-flow analysis to calculate + live lock sets at each program point, using the attributes to decide + when to add locks to the live sets and when to remove them from the + sets. With the live lock sets and the attributes attached to shared + variables and functions, we are able to check whether the variables + and functions are well protected. Note that the reason why we don't + need iterative data flow analysis is because critical sections across + back edges are considered a bad practice. + + The single-iteration data flow analysis is performed by visiting + each basic block only once in a topological order. The topological + traversal is achieved by maintaining a work list (or ready list) which + is seeded with the successors of the function's entry block. A basic + block is added to the work list when all of its predecessors have been + visited. During the traversal, back edges are ignored. */ + +static unsigned int +execute_threadsafe_analyze (void) +{ + size_t append_ptr = 0, visit_ptr = 0; + basic_block bb; + edge e; + edge_iterator ei; + tree fdecl_attrs; + struct bb_threadsafe_info *threadsafe_info; + struct pointer_set_t *live_excl_locks_at_entry; + struct pointer_set_t *live_shared_locks_at_entry; + tree attr; + basic_block *worklist; + int i; + int old_mismatched_lock_acq_rel = warn_thread_mismatched_lock_acq_rel; + + /* Skip the compiler-generated functions. */ + if (DECL_ARTIFICIAL (current_function_decl)) + return 0; + + /* Constructors and destructors should only be accessed by a single + thread and therefore are ignored here. */ + if (lang_hooks.decl_is_constructor (current_function_decl) + || lang_hooks.decl_is_destructor (current_function_decl)) + return 0; + + /* If the current function is annotated as a locking or unlocking primitive, + or if it is marked to be skipped (with no_thread_safety_analysis + attribute), ignore it. */ + fdecl_attrs = DECL_ATTRIBUTES (current_function_decl); + if (lookup_attribute("exclusive_lock", fdecl_attrs) + || lookup_attribute("shared_lock", fdecl_attrs) + || lookup_attribute("exclusive_trylock", fdecl_attrs) + || lookup_attribute("shared_trylock", fdecl_attrs) + || lookup_attribute("unlock", fdecl_attrs) + || lookup_attribute("no_thread_safety_analysis", fdecl_attrs)) + return 0; + + /* If this is the first function of the current compilation unit, we need + to build the transitive acquired_after sets for the locks. */ + if (lock_acquired_after_map && !transitive_acq_after_sets_built) + { + build_transitive_acquired_after_sets(); + transitive_acq_after_sets_built = true; + } + + /* Mark the back edges in the cfg so that we can skip them later + in our (single-iteration) data-flow analysis. */ + mark_dfs_back_edges (); + + /* Allocate lock-related global maps. */ + scopedlock_to_lock_map = pointer_map_create (); + lock_locus_map = pointer_map_create (); + trylock_info_map = pointer_map_create (); + gimple_call_tab = htab_create (10, call_gs_hash, call_gs_eq, NULL); + + /* Initialize the pretty printer buffer for warning emitting. */ + pp_construct (&pp_buf, /* prefix */ NULL, /* line-width */ 0); + + /* Allocate the threadsafe info array. + Use XCNEWVEC to clear out the info. */ + threadsafe_info = XCNEWVEC(struct bb_threadsafe_info, last_basic_block); + + /* Since the back/complex edges are not traversed in the analysis, + mark them as visited. */ + FOR_EACH_BB (bb) + { + FOR_EACH_EDGE (e, ei, bb->preds) + { + if (e->flags & (EDGE_DFS_BACK | EDGE_COMPLEX)) + ++threadsafe_info[bb->index].n_preds_visited; + } + } + + /* Populate ENTRY_BLOCK's live out sets with "exclusive_locks_required" + and "shared_locks_required" attributes. */ + live_excl_locks_at_entry = pointer_set_create(); + live_shared_locks_at_entry = pointer_set_create(); + + attr = lookup_attribute("exclusive_locks_required", + DECL_ATTRIBUTES (current_function_decl)); + if (attr) + populate_lock_set_with_attr(live_excl_locks_at_entry, attr); + + attr = lookup_attribute("shared_locks_required", + DECL_ATTRIBUTES (current_function_decl)); + if (attr) + populate_lock_set_with_attr(live_shared_locks_at_entry, attr); + + threadsafe_info[ENTRY_BLOCK_PTR->index].liveout_exclusive_locks = + live_excl_locks_at_entry; + threadsafe_info[ENTRY_BLOCK_PTR->index].liveout_shared_locks = + live_shared_locks_at_entry; + + threadsafe_info[ENTRY_BLOCK_PTR->index].weak_released_locks = + pointer_set_create (); + + /* Allocate the worklist of BBs for topological traversal, which is + basically an array of pointers to basic blocks. */ + worklist = XNEWVEC (basic_block, n_basic_blocks); + + /* Seed the worklist by adding the successors of the entry block + to the worklist. */ + FOR_EACH_EDGE (e, ei, ENTRY_BLOCK_PTR->succs) + { + worklist[append_ptr++] = e->dest; + } + + /* The basic blocks in the current function are traversed in a topological + order. Both "visit_ptr" and "append_ptr" are indices to the worklist + array and initialized to zero. "append_ptr" is incremented whenever a BB + is added to the work list, while "visit_ptr" is incremented when we + visit a BB. When "visit_ptr" catches up with "append_ptr", the traversal + is done. */ + while (visit_ptr != append_ptr) + { + struct pointer_set_t *reported_unreleased_locks = pointer_set_create(); + struct bb_threadsafe_info *current_bb_info; + gimple_stmt_iterator gsi; + + bb = worklist[visit_ptr++]; + current_bb_info = &threadsafe_info[bb->index]; + current_bb_info->visited = true; + + /* First create the live-in lock sets for bb by intersecting all of its + predecessors' live-out sets */ + FOR_EACH_EDGE (e, ei, bb->preds) + { + basic_block pred_bb = e->src; + struct pointer_set_t *unreleased_locks; + struct pointer_set_t *pred_liveout_excl_locks; + struct pointer_set_t *pred_liveout_shared_locks; + + /* Skip the back/complex edge. */ + if (e->flags & (EDGE_DFS_BACK | EDGE_COMPLEX)) + continue; + + /* If this is the first predecessor of bb's, simply copy the + predecessor's live-out sets and reads/writes_ignored flags + to bb's live (working) sets and corresponding flags. */ + if (current_bb_info->live_excl_locks == NULL) + { + current_bb_info->reads_ignored = + threadsafe_info[pred_bb->index].reads_ignored; + current_bb_info->writes_ignored = + threadsafe_info[pred_bb->index].writes_ignored; + current_bb_info->live_excl_locks = pointer_set_copy ( + threadsafe_info[pred_bb->index].liveout_exclusive_locks); + current_bb_info->live_shared_locks = pointer_set_copy ( + threadsafe_info[pred_bb->index].liveout_shared_locks); + current_bb_info->weak_released_locks = pointer_set_copy ( + threadsafe_info[pred_bb->index].weak_released_locks); + /* If the pred bb has a trylock call and its edge to the current + bb is the one for successful lock acquisition, add the + trylock live sets to the bb's live working sets. */ + if (threadsafe_info[pred_bb->index].trylock_live_edge == e) + { + gcc_assert ( + threadsafe_info[pred_bb->index].edge_exclusive_locks + || threadsafe_info[pred_bb->index].edge_shared_locks); + if (threadsafe_info[pred_bb->index].edge_exclusive_locks) + pointer_set_union_inplace ( + current_bb_info->live_excl_locks, + threadsafe_info[pred_bb->index].edge_exclusive_locks); + if (threadsafe_info[pred_bb->index].edge_shared_locks) + pointer_set_union_inplace ( + current_bb_info->live_shared_locks, + threadsafe_info[pred_bb->index].edge_shared_locks); + } + continue; + } + + unreleased_locks = pointer_set_create(); + pred_liveout_excl_locks = + threadsafe_info[pred_bb->index].liveout_exclusive_locks; + pred_liveout_shared_locks = + threadsafe_info[pred_bb->index].liveout_shared_locks; + + /* If the pred bb has a trylock call and its edge to the current + bb is the one for successful lock acquisition, add the + trylock live sets to the pred bb's live-out sets. */ + if (threadsafe_info[pred_bb->index].trylock_live_edge == e) + { + gcc_assert(threadsafe_info[pred_bb->index].edge_exclusive_locks + || threadsafe_info[pred_bb->index].edge_shared_locks); + /* The following code will clobber the original contents of + edge_exclusive_locks set and/or edge_shared_locks set of + the pred bb, but that is fine because they will not be + used in the future (as this edge is visited only once in + our single-iteration data-flow analysis). */ + if (threadsafe_info[pred_bb->index].edge_exclusive_locks) + { + pred_liveout_excl_locks = + threadsafe_info[pred_bb->index].edge_exclusive_locks; + pointer_set_union_inplace (pred_liveout_excl_locks, + threadsafe_info[pred_bb->index].liveout_exclusive_locks); + } + + if (threadsafe_info[pred_bb->index].edge_shared_locks) + { + pred_liveout_shared_locks = + threadsafe_info[pred_bb->index].edge_shared_locks; + pointer_set_union_inplace (pred_liveout_shared_locks, + threadsafe_info[pred_bb->index].liveout_shared_locks); + } + } + + /* Logical-and'ing the current BB's reads/writes_ignored flags with + predecessor's flags. These flags will be true at the beginning + of a BB only when they are true at the end of all the + precedecessors. */ + current_bb_info->reads_ignored &= + threadsafe_info[pred_bb->index].reads_ignored; + current_bb_info->writes_ignored &= + threadsafe_info[pred_bb->index].writes_ignored; + + /* Intersect the current (working) live set with the predecessor's + live-out set. Locks that are not in the intersection (i.e. + complement set) should be reported as improperly released. */ + pointer_set_intersection_complement ( + current_bb_info->live_excl_locks, + pred_liveout_excl_locks, + unreleased_locks); + pointer_set_intersection_complement ( + current_bb_info->live_shared_locks, + pred_liveout_shared_locks, + unreleased_locks); + + /* Take the union of the weak released lock sets of the + predecessors. */ + pointer_set_union_inplace ( + current_bb_info->weak_released_locks, + threadsafe_info[pred_bb->index].weak_released_locks); + + /* If a lock is released by a Release function of a scoped lock on + some control-flow paths (but not all), the lock would still be + live on other paths, which is OK as the destructor of the scoped + lock will eventually release the lock. We don't want to emit + bogus warnings about the release inconsistency at the + control-flow join point. To avoid that, we simply add those + weakly-released locks in the REPORTED_UNRELEASED_LOCKS set. */ + pointer_set_union_inplace ( + reported_unreleased_locks, + current_bb_info->weak_released_locks); + + /* Emit warnings for the locks that are not properly released. + That is, the places they are released are not control + equivalent to where they are acquired. */ + if (warn_thread_mismatched_lock_acq_rel) + pointer_set_traverse (unreleased_locks, + warn_locally_unreleased_locks, + reported_unreleased_locks); + + pointer_set_destroy (unreleased_locks); + } + + pointer_set_destroy (reported_unreleased_locks); + gcc_assert (current_bb_info->live_excl_locks != NULL); + + /* Now iterate through each statement of the bb and analyze its + operands. */ + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + struct walk_stmt_info wi; + memset (&wi, 0, sizeof (wi)); + wi.info = (void *) current_bb_info; + walk_gimple_stmt (&gsi, analyze_stmt_r, analyze_op_r, &wi); + } + + /* Now that we have visited the current bb, check if any of its + successors can be added to the work list. */ + FOR_EACH_EDGE (e, ei, bb->succs) + { + basic_block succ_bb; + if (e->flags & (EDGE_DFS_BACK | EDGE_COMPLEX)) + continue; + succ_bb = e->dest; + /* Since we skip the back edges, we shouldn't see a visited basic + block again here. */ + gcc_assert (!threadsafe_info[succ_bb->index].visited); + if ((++threadsafe_info[succ_bb->index].n_preds_visited) == + EDGE_COUNT(succ_bb->preds)) + worklist[append_ptr++] = succ_bb; + } + + current_bb_info->liveout_exclusive_locks = + current_bb_info->live_excl_locks; + current_bb_info->liveout_shared_locks = + current_bb_info->live_shared_locks; + } + + /* If there are still live locks at the end of the function that are held + at the entry of the function (i.e. not in the function's locks_required + sets), emit warning messages for them. + Note that the exit block may not be reachable from the entry (e.g. when + there are abort() or exit() calls that collectively dominate the exit + block). We need to check whether its liveout_exclusive_locks and + liveout_shared_locks are empty before trying to traverse them. + TODO: Besides the exit block, we also need to check the basic blocks + that don't have any successors as they are practically "exit" blocks + as well. */ + if (warn_thread_mismatched_lock_acq_rel) + { + if (threadsafe_info[EXIT_BLOCK_PTR->index].liveout_exclusive_locks) + pointer_set_traverse( + threadsafe_info[EXIT_BLOCK_PTR->index].liveout_exclusive_locks, + warn_unreleased_locks, live_excl_locks_at_entry); + if (threadsafe_info[EXIT_BLOCK_PTR->index].liveout_shared_locks) + pointer_set_traverse( + threadsafe_info[EXIT_BLOCK_PTR->index].liveout_shared_locks, + warn_unreleased_locks, live_shared_locks_at_entry); + } + + /* Free the allocated data structures. */ + for (i = 0; i < last_basic_block; ++i) + { + if (threadsafe_info[i].liveout_exclusive_locks != NULL) + { + pointer_set_destroy(threadsafe_info[i].liveout_exclusive_locks); + pointer_set_destroy(threadsafe_info[i].liveout_shared_locks); + } + if (threadsafe_info[i].weak_released_locks != NULL) + pointer_set_destroy (threadsafe_info[i].weak_released_locks); + if (threadsafe_info[i].edge_exclusive_locks != NULL) + pointer_set_destroy (threadsafe_info[i].edge_exclusive_locks); + if (threadsafe_info[i].edge_shared_locks != NULL) + pointer_set_destroy (threadsafe_info[i].edge_shared_locks); + } + + XDELETEVEC (threadsafe_info); + XDELETEVEC (worklist); + + pp_clear_output_area (&pp_buf); + pointer_map_destroy (scopedlock_to_lock_map); + pointer_map_traverse (lock_locus_map, delete_lock_locations, NULL); + pointer_map_destroy (lock_locus_map); + pointer_map_traverse (trylock_info_map, delete_trylock_info, NULL); + pointer_map_destroy (trylock_info_map); + htab_delete (gimple_call_tab); + + /* The flag that controls the warning of mismatched lock acquire/release + could be turned off when we see an unlock primitive with an unsupported + lock name/expression (see process_function_attrs). We need to restore + the original value of the flag after we finish analyzing the current + function. */ + if (old_mismatched_lock_acq_rel != warn_thread_mismatched_lock_acq_rel) + warn_thread_mismatched_lock_acq_rel = old_mismatched_lock_acq_rel; + + return 0; +} + +static bool +gate_threadsafe_analyze (void) +{ + return warn_thread_safety != 0; +} + +struct gimple_opt_pass pass_threadsafe_analyze = +{ + { + GIMPLE_PASS, + "threadsafe_analyze", /* name */ + gate_threadsafe_analyze, /* gate */ + execute_threadsafe_analyze, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_TREE_THREADSAFE, /* tv_id */ + PROP_cfg | PROP_ssa, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_func /* todo_flags_finish */ + } +}; diff --git a/gcc/tree-threadsafe-analyze.h b/gcc/tree-threadsafe-analyze.h new file mode 100644 --- /dev/null +++ b/gcc/tree-threadsafe-analyze.h @@ -0,0 +1,39 @@ +/* Header file for thread safety analysis. + Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. + Contributed by Le-Chun Wu <lcwu@google.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + + +#ifndef TREE_THREADSAFE_ANALYZE_H +#define TREE_THREADSAFE_ANALYZE_H + +/* Maximum length (in bytes) of a lock name string */ +#define LOCK_NAME_LEN 64 + +/* Defined in tree-threadsafe-analyze.c */ +extern tree get_canonical_lock_expr (tree, tree, bool, tree); +extern void clean_up_threadsafe_analysis (void); +extern struct pointer_map_t *lock_acquired_after_map; +extern struct pointer_map_t *unbound_lock_map; +extern const char* dump_expr_tree (tree lock, char *out_buf); +extern tree get_leftmost_base_var (tree); +/* True if the parser is currently parsing a lock attribute. */ +extern bool parsing_lock_attribute; + + +#endif /* TREE_THREADSAFE_ANALYZE_H */ diff --git a/gcc/tree.h b/gcc/tree.h --- a/gcc/tree.h +++ b/gcc/tree.h @@ -5360,6 +5360,12 @@ extern const struct attribute_spec *lookup_attribute_spec (const_tree); a decl attribute to the declaration rather than to its type). */ extern tree decl_attributes (tree *, tree, int); +/* Return true if the given identifier tree is the name of a lock attribute + that takes arguments. */ +extern bool is_lock_attribute_with_args (const_tree); +/* Extract and return all lock attributes from the given attribute list. */ +extern tree extract_lock_attributes (tree); + /* In integrate.c */ extern void set_decl_abstract_flags (tree, int); extern void set_decl_origin_self (tree); -- This patch is available for review at http://codereview.appspot.com/4275075
Sign in to reply to this message.
Looks OK with some fixes below. Could you also update http://gcc.gnu.org/wiki/ThreadSafetyAnnotation? It still points to the old branch and seems to have stale content. Any plans for mainline merge? Diego. http://codereview.appspot.com/4275075/diff/2001/gcc/common.opt File gcc/common.opt (right): http://codereview.appspot.com/4275075/diff/2001/gcc/common.opt#newcode682 gcc/common.opt:682: Make the thread safety analysis try to bind the function parameters used in the attributes Hmm, you've added these warnings twice now. I had added the flags to fix our builds. The warnings I added are just above these. If there's anything new, you can just overwrite it. http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c File gcc/cp/parser.c (left): http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#oldcode5766 gcc/cp/parser.c:5766: is_attribute_list = non_attr; Why are you taking this out? http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#oldcode5802 gcc/cp/parser.c:5802: VEC_safe_insert (tree, gc, expression_list, 0, identifier); Likewise. http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c File gcc/cp/parser.c (right): http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#newcode1742 gcc/cp/parser.c:1742: tree current_declarator_scope; You're going to find some amusing merge conflicts the next time google/main merges from trunk ;) All this has been factored out of cp/parser.c http://codereview.appspot.com/4275075/diff/2001/gcc/tree.h File gcc/tree.h (right): http://codereview.appspot.com/4275075/diff/2001/gcc/tree.h#newcode5366 gcc/tree.h:5366: /* Extract and return all lock attributes from the given attribute list. */ Blank line above comment.
Sign in to reply to this message.
On Thu, Mar 24, 2011 at 15:30, Le-Chun Wu <lcwu@google.com> wrote: > 2011-03-24 Le-Chun Wu <lcwu@google.com> > > * Makefile.in: Add new source file and headers in dependencies. > * attribs.c (decl_attributes): Handle lock attributes. > (is_lock_attribute_with_args): New function. > (is_lock_attribute_p): Likewise. > (extract_lock_attributes): Likewise. > (merge_lock_attr_args): Likewise. > * c-decl.c (undeclared_variable): Suppress errors for lock attributes. > * c-parser.c (c_parser_declaration_or_fndef): Allow lock attributes on > function definitions. Add support for suppressing errors for lock > attributes. > (c_parser_attributes): Replace the original code that handles the > argument list with a call to c_parser_attr_arg_list. > (c_parser_attr_arg_list): New function. > * common.opt: Add new flags for lock annotations and analysis. > * doc/invoke.texi: Add documentation for new flags for lock annotations > and analysis. > * gimplify.c (lookup_tmp_var): Copy thread safety attributes to tmp > variable and save the original variable name. > * langhooks-def.h: Define new language hooks. > * langhooks.c (lhd_do_nothing_t_return_int): New function. > (lhd_do_nothing_t_return_bool): Likewise. > (lhd_do_nothing_t_t_return_null_tree): Likewise. > * langhooks.h: Add new hook functions in the lang_hooks struct. > * passes.c (init_optimization_passes): Add a new pass. > * pointer-set.c (pointer_set_copy): New function. > (pointer_set_delete): Likewise. > (pointer_set_intersection_complement): Likewise. > (pointer_set_union_inplace): Likewise. > (pointer_set_cardinality): Likewise. > * pointer-set.h: Add declarations of new functions. > * timevar.def: Add a new time var for thread safety analysis pass. > * toplev.c (compile_file): Clean up the global data structures > used by the thread safety analysis. > * tree-pass.h: Add a new pass declaration. > * tree-threadsafe-analyze.c: New file. > * tree-threadsafe-analyze.h: New file. > * tree.h: Declaration for new functions. > > c-family/ > * c-common.c (c_common_attribute_table): Add new functions to process > lock attributes. > (attribute_takes_identifier_p): Handle lock attributes. > (handle_lockable_attribute): New Handler. > (handle_guarded_by_attribute): Likewise. > (handle_point_to_guarded_by_attribute): Likewise. > (handle_guarded_attribute): Likewise. > (handle_point_to_guarded_attribute): Likewise. > (handle_acquired_order_attribute): Likewise. > (handle_lock_attribute): Likewise. > (handle_unlock_attribute): Likewise. > (handle_locks_required_excluded_attribute): Likewise. > (handle_lock_returned_attribute): Likewise. > (handle_no_thread_safety_analysis_attribute): Likewise. > (supported_lock_expression): New helper function. > (get_lock_decl): Likewise. > (populate_acquired_after_map): Likewise. > (is_lock_formal_parameter): Likewise. > (check_lock_unlock_attr_args): Likewise. > * c-cppbuiltin.c (c_cpp_builtins): Define annotalysis-related macros. > * c-pretty-print.c (pp_c_expression): Handle SSA_NAME. > > cp/ > * Make-lang.in: Add new includes. > * call.c (build_new_op): Support for non-const non-modifying methods. > (find_const_memfunc_with_identical_prototype): New function. > (build_new_method_call): Suppress errors for calls in lock attributes. > Support for non-const non-modifying methods. > * class.c (cp_get_virtual_function_decl): New function. > (cp_fold_obj_type_ref): Refactored to call cp_get_virtual_function_decl. > (cp_decl_is_base_field): New function. > (cp_decl_is_constructor): Likewise. > (cp_decl_is_destructor): Likewise. > (cp_decl_is_const_member_func): Likewise. > * cp-lang.c: New language hooks. > * cp-tree.h: New function declarations. > * decl2.c (is_late_template_attribute): Handle delay parsing of lock > attribute arguments. > * error.c (dump_expr): Handle SSA_NAME. > * lex.c (unqualified_name_lookup_error): Suppress errors for lock > attributes. > * name-lookup.c (lookup_name_in_func_params): New function. > * name-lookup.h: New function declaration. > * parser.c (cp_parser): New fields. > (cp_parser_name_lookup_error): Suppress errors for lock attributes. > (cp_parser_new): Initialize unparsed_attribute_args_queue. > (cp_parser_postfix_expression): Add function parameter lookup support. > (cp_parser_parenthesized_expression_list): Fix a problem in parsing > identifier arguments and skip folding for decl arguments. > (cp_parser_lambda_declarator_opt): Add a new argument to > cp_parser_attributes_opt. > (cp_parser_label_for_labeled_statement): Likewise. > (cp_parser_condition): Likewise. > (cp_parser_decl_specifier_seq): Likewise. > (cp_parser_conversion_type_id): Likewise. > (cp_parser_elaborated_type_specifier): Likewise. > (cp_parser_enum_specifier): Likewise. > (cp_parser_namespace_definition): Likewise. > (cp_parser_using_directive): Likewise. > (cp_parser_init_declarator): Allow lock attributes on function > definitions. Support function parameter lookup. Also add a new > argument to cp_parser_attributes_opt. > (cp_parser_declarator): Add a new argument to calls to > cp_parser_attributes_opt. > (cp_parser_type_specifier_seq): Likewise. > (cp_parser_parameter_declaration): Likewise. > (cp_parser_class_specifier): Late-parse the lock attribute arguments. > Also add a new argument to cp_parser_attributes_opt. > (cp_parser_class_head): Add a new argument to cp_parser_attributes_opt. > (cp_parser_member_declaration): Likewise. > (cp_parser_asm_label_list): Likewise. > (cp_parser_attributes_opt): Add a new parameter 'member_p' and call > cp_parser_attribute_list with it. > (cp_parser_save_attribute_arg_list): New function. > (cp_parser_attribute_list): Add a new parameter 'member_p'. Also delay > parsing of lock attribute arguments by saving the tokens. > (cp_parser_late_parsing_attribute_arg_lists): New function. > (cp_parser_function_definition_from_specifiers_and_declarator): Parse > unbound lock attribute arguments. > (cp_parser_objc_method_keyword_params): Add a new argument to > cp_parser_attributes_opt. > (cp_parser_objc_method_tail_params_opt): Likewise. > (cp_parser_objc_method_maybe_bad_prefix_attributes): Likewise. > (cp_parser_objc_class_ivars): Likewise. > (cp_parser_objc_struct_declaration): Likewise. > (cp_parser_omp_for_loop): Likewise. > * pt.c (find_parameter_packs_r): Skip walking the subtrees if the tree > list node is created for delay parsing. > (apply_late_template_attributes): Defer instantiation of lock > attributes. > (pa_reverse): New function. > (instantiate_class_template): Instantiate the deferred lock attributes > and apply them to the corresponding declarations. > (tsubst_copy): Suppress errors for lock attributes. > (tsubst_copy_and_build): Support function parameter lookup. > * semantics.c (finish_non_static_data_member): Suppress errors for lock > attributes. > * typeck.c (finish_class_member_access_expr): Suppress errors for lock > attributes. > * typeck2.c (cxx_incomplete_type_diagnostic): Suppress errors for lock > attributes. > > testsuite/ > * g++.dg/README: Add an entry for the new thread-ann sub-directory. > * g++.dg/thread-ann: New test sub-directory. > * g++.dg/thread-ann/thread_annot_common.h: New test header file. > * g++.dg/thread-ann/thread_annot_lock-1.C: New test. > * g++.dg/thread-ann/thread_annot_lock-2.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-3.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-4.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-5.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-6.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-7.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-8.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-9.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-10.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-11.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-12.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-13.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-14.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-15.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-16.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-17.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-18.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-19.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-20.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-21.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-22.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-23.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-24.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-25.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-26.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-27.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-28.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-29.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-30.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-31.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-32.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-33.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-34.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-35.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-36.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-37.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-38.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-39.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-40.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-41.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-42.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-43.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-44.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-45.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-46.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-47.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-48.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-49.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-50.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-51.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-52.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-53.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-54.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-55.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-56.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-57.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-58.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-59.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-60.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-61.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-62.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-63.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-64.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-65.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-66.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-67.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-68.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-69.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-70.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-71.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-72.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-73.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-74.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-75.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-76.C: Likewise. > * g++.dg/thread-ann/thread_annot_lock-77.C: Likewise. > * gcc.dg/thread_annot_common_c.h: New test header file for C. > * gcc.dg/thread_annot_lock-23.c: New test. > * gcc.dg/thread_annot_lock-23.c: New test. > * gcc.dg/thread_annot_lock-24.c: Likewise. > * gcc.dg/thread_annot_lock-25.c: Likewise. > * gcc.dg/thread_annot_lock-26.c: Likewise. > * gcc.dg/thread_annot_lock-27.c: Likewise. > * gcc.dg/thread_annot_lock-42.c: Likewise. Looks OK with some fixes below. Could you also update http://gcc.gnu.org/wiki/ThreadSafetyAnnotation? It still points to the old branch and seems to have stale content. Any plans for mainline merge? Diego. http://codereview.appspot.com/4275075/diff/2001/gcc/common.opt File gcc/common.opt (right): http://codereview.appspot.com/4275075/diff/2001/gcc/common.opt#newcode682 gcc/common.opt:682: Make the thread safety analysis try to bind the function parameters used in the attributes Hmm, you've added these warnings twice now. I had added the flags to fix our builds. The warnings I added are just above these. If there's anything new, you can just overwrite it. http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c File gcc/cp/parser.c (left): http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#oldcode5766 gcc/cp/parser.c:5766: is_attribute_list = non_attr; Why are you taking this out? http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#oldcode5802 gcc/cp/parser.c:5802: VEC_safe_insert (tree, gc, expression_list, 0, identifier); Likewise. http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c File gcc/cp/parser.c (right): http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#newcode1742 gcc/cp/parser.c:1742: tree current_declarator_scope; You're going to find some amusing merge conflicts the next time google/main merges from trunk ;) All this has been factored out of cp/parser.c http://codereview.appspot.com/4275075/diff/2001/gcc/tree.h File gcc/tree.h (right): http://codereview.appspot.com/4275075/diff/2001/gcc/tree.h#newcode5366 gcc/tree.h:5366: /* Extract and return all lock attributes from the given attribute list. */ Blank line above comment. Diego.
Sign in to reply to this message.
> Could you also update http://gcc.gnu.org/wiki/ThreadSafetyAnnotation? > It still points to the old branch and seems to have stale content. Will do. > > Any plans for mainline merge? I don't actually have a time frame for that, but that is the ultimate goal. http://codereview.appspot.com/4275075/diff/2001/gcc/common.opt File gcc/common.opt (right): http://codereview.appspot.com/4275075/diff/2001/gcc/common.opt#newcode682 gcc/common.opt:682: Make the thread safety analysis try to bind the function parameters used in the attributes On 2011/03/24 21:42:18, Diego Novillo wrote: > Hmm, you've added these warnings twice now. I had added the flags to fix our > builds. The warnings I added are just above these. If there's anything new, > you can just overwrite it. Ah, I didn't notice that, and the build went OK. Duplicate entries were removed. http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c File gcc/cp/parser.c (left): http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#oldcode5766 gcc/cp/parser.c:5766: is_attribute_list = non_attr; On 2011/03/24 21:42:18, Diego Novillo wrote: > Why are you taking this out? Without doing, the parser would start evaluating/binding the identifier nodes after processing the first argument, which would limit our ability to allow users to use names not in scope. The detailed explanation is in the comments above (line 5778 to 5801 in the patched file). http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#oldcode5802 gcc/cp/parser.c:5802: VEC_safe_insert (tree, gc, expression_list, 0, identifier); On 2011/03/24 21:42:18, Diego Novillo wrote: > Likewise. In the original implementation, the first identifier argument was not pushed into the expression_list vector, so we had to do that here. With my patch, the first argument is pushed to the vector in line 5802 (in patched file). http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c File gcc/cp/parser.c (right): http://codereview.appspot.com/4275075/diff/2001/gcc/cp/parser.c#newcode1742 gcc/cp/parser.c:1742: tree current_declarator_scope; On 2011/03/24 21:42:18, Diego Novillo wrote: > You're going to find some amusing merge conflicts the next time google/main > merges from trunk ;) All this has been factored out of cp/parser.c Thanks for the heads-up. We will deal with them then. :-) http://codereview.appspot.com/4275075/diff/2001/gcc/tree.h File gcc/tree.h (right): http://codereview.appspot.com/4275075/diff/2001/gcc/tree.h#newcode5366 gcc/tree.h:5366: /* Extract and return all lock attributes from the given attribute list. */ On 2011/03/24 21:42:18, Diego Novillo wrote: > Blank line above comment. Done.
Sign in to reply to this message.
|