aboutsummaryrefslogtreecommitdiffstats
path: root/gcc-4.4.3/gcc/tree-threadsafe-analyze.c
diff options
context:
space:
mode:
authorJing Yu <jingyu@google.com>2010-07-22 14:03:48 -0700
committerJing Yu <jingyu@google.com>2010-07-22 14:03:48 -0700
commitb094d6c4bf572654a031ecc4afe675154c886dc5 (patch)
tree89394c56b05e13a5413ee60237d65b0214fd98e2 /gcc-4.4.3/gcc/tree-threadsafe-analyze.c
parentdc34721ac3bf7e3c406fba8cfe9d139393345ec5 (diff)
downloadtoolchain_gcc-b094d6c4bf572654a031ecc4afe675154c886dc5.tar.gz
toolchain_gcc-b094d6c4bf572654a031ecc4afe675154c886dc5.tar.bz2
toolchain_gcc-b094d6c4bf572654a031ecc4afe675154c886dc5.zip
commit gcc-4.4.3 which is used to build gcc-4.4.3 Android toolchain in master.
The source is based on fsf gcc-4.4.3 and contains local patches which are recorded in gcc-4.4.3/README.google. Change-Id: Id8c6d6927df274ae9749196a1cc24dbd9abc9887
Diffstat (limited to 'gcc-4.4.3/gcc/tree-threadsafe-analyze.c')
-rw-r--r--gcc-4.4.3/gcc/tree-threadsafe-analyze.c3277
1 files changed, 3277 insertions, 0 deletions
diff --git a/gcc-4.4.3/gcc/tree-threadsafe-analyze.c b/gcc-4.4.3/gcc/tree-threadsafe-analyze.c
new file mode 100644
index 000000000..eb49db529
--- /dev/null
+++ b/gcc-4.4.3/gcc/tree-threadsafe-analyze.c
@@ -0,0 +1,3277 @@
+/* Thread Safety Annotations and Analysis.
+ Copyright (C) 2007, 2008 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))
+
+ 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-common.h"
+#include "toplev.h"
+#include "input.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "basic-block.h"
+#include "tree-flow.h"
+#include "tree-pass.h"
+#include "tree-dump.h"
+#include "langhooks.h"
+#include "pointer-set.h"
+#include "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;
+
+ /* 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
+};
+
+/* 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));
+ }
+}
+
+/* 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);
+ tree fn_ptr2 = get_canonical_lock_expr (gimple_call_fn (call_gs2),
+ NULL_TREE, true);
+ 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);
+ tree arg2 = get_canonical_lock_expr (gimple_call_arg (call_gs2, i),
+ NULL_TREE, true);
+ 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)
+ 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 */);
+
+ /* 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 */);
+
+ 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. */
+
+tree
+get_canonical_lock_expr (tree lock, tree base_obj, bool is_temp_expr)
+{
+ hashval_t hash;
+ tree canon_lock;
+ void **slot;
+
+ switch (TREE_CODE (lock))
+ {
+ case VAR_DECL:
+ case PARM_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 */);
+ }
+ 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);
+ 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->. */
+ else if (fdecl
+ && ((DECL_NAME (fdecl)
+ == maybe_get_identifier ("operator->"))
+ || (DECL_NAME (fdecl)
+ == maybe_get_identifier ("operator."))
+ || (DECL_NAME (fdecl)
+ == maybe_get_identifier ("get")))
+ && 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 */);
+ 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);
+ }
+ 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 */);
+ canon_base = get_canonical_lock_expr (base, NULL_TREE,
+ true /* is_temp_expr */);
+ 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);
+
+ canon_base = get_canonical_lock_expr (base, base_obj,
+ true /* is_temp_expr */);
+
+ /* 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);
+
+ 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 */);
+ 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 */));
+ 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:
+ {
+ tree base = TREE_OPERAND (lock, 0);
+ tree canon_base = get_canonical_lock_expr (base, base_obj,
+ true /* is_temp_expr */);
+ 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 (recursively) 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)
+{
+ while (1)
+ {
+ if (TREE_CODE (expr) == FIELD_DECL)
+ return true;
+ else if (EXPR_P (expr))
+ {
+ expr = TREE_OPERAND (expr, 0);
+ continue;
+ }
+ 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 */);
+ 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 */);
+ 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 */);
+ }
+ }
+
+ 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 (OPT_Wthread_safety,
+ G_("%HAccess to memory location pointed to by"
+ " variable %s requires a lock"),
+ locus, dump_expr_tree (decl, dname));
+ else
+ warning (OPT_Wthread_safety, G_("%H%s %s requires a lock"),
+ locus, msg, dump_expr_tree (decl, dname));
+ }
+ else
+ {
+ if (mode == TSA_WRITE)
+ {
+ if (is_indirect_ref)
+ warning (OPT_Wthread_safety,
+ G_("%HWriting to memory location pointed to by"
+ " variable %s requires an exclusive lock"),
+ locus, dump_expr_tree (decl, dname));
+ else
+ warning (OPT_Wthread_safety,
+ G_("%H%s %s requires an exclusive lock"),
+ locus, msg, dump_expr_tree (decl, dname));
+ }
+ }
+ }
+ return;
+ }
+
+ if (!DECL_P (lock))
+ lock = get_canonical_lock_expr (lock, NULL_TREE, false /* is_temp_expr */);
+
+ 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 */);
+ }
+ }
+ if (is_indirect_ref)
+ warning (OPT_Wthread_safety,
+ G_("%HAccess to memory location pointed to by"
+ " variable %s requires lock %s"),
+ locus, dump_expr_tree (decl, dname),
+ dump_expr_tree (lock, lname));
+ else
+ warning (OPT_Wthread_safety, G_("%H%s %s requires lock %s"),
+ locus, 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 */);
+ }
+ }
+ if (is_indirect_ref)
+ warning (OPT_Wthread_safety,
+ G_("%HWriting to memory location pointed to by"
+ " variable %s requires exclusive lock %s"),
+ locus, dump_expr_tree (decl, dname),
+ dump_expr_tree (lock, lname));
+ else
+ warning (OPT_Wthread_safety,
+ G_("%H%s %s requires exclusive lock %s"),
+ locus, 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;
+}
+
+/* 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 (OPT_Wthread_safety,
+ G_("%HLock %s is acquired after lock %s (acquired at"
+ " line %d) but is annotated otherwise"),
+ current_loc, dump_expr_tree ((tree) lock, lname1),
+ dump_expr_tree (live_lock_tree, lname2),
+ LOCATION_LINE (*((location_t *) *loc_entry)));
+ else
+ warning (OPT_Wthread_safety,
+ G_("%HLock %s is acquired after lock %s (held at function"
+ " entry) but is annotated otherwise"),
+ current_loc, dump_expr_tree ((tree) lock, lname1),
+ dump_expr_tree (live_lock_tree, lname2));
+ }
+ return true;
+ }
+
+ if (lock_in_live_locks_acq_after_set)
+ warning (OPT_Wthread_safety,
+ G_("%HThere is a cycle in the acquisition order between locks"
+ " %s and %s"),
+ current_loc, 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);
+}
+
+/* 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).
+
+ 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 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)
+{
+ tree fdecl = gimple_call_fndecl (call);
+ 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, we need to grab
+ the corresponding actual parameter of the call. */
+ else if (TREE_CODE (arg) == INTEGER_CST)
+ arg = get_actual_argument_from_position (call, arg);
+ 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 */);
+
+ /* 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 (OPT_Wthread_safety,
+ G_("%HTry to acquire lock %s that is already held"
+ " (previously acquired at line %d)"),
+ locus, dump_expr_tree (lockable, lname),
+ LOCATION_LINE (*((location_t *) *entry)));
+ else
+ warning (OPT_Wthread_safety,
+ G_("%HTry to acquire lock %s that is already held"
+ " (at function entry)"),
+ locus, 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 ARG and BASE_OBJ, please see the comments for
+ handle_lock_primitive_attrs above. */
+
+static void
+handle_unlock_primitive_attr (gimple call, 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)
+ {
+ if (TREE_CODE (arg) == INTEGER_CST)
+ lockable = get_actual_argument_from_position (call, arg);
+ 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 */);
+ }
+ 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)
+ {
+ tree fdecl = gimple_call_fndecl (call);
+ 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 */);
+ }
+
+ /* 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 (OPT_Wthread_safety,
+ G_("%HTry to unlock %s that was not acquired"),
+ locus, dump_expr_tree (lockable, lname));
+}
+
+/* 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;
+ char lname[LOCK_NAME_LEN];
+ tree base_obj = NULL_TREE;
+ bool is_exclusive_lock;
+ bool is_trylock;
+
+ gcc_assert (is_gimple_call (call));
+
+ /* 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;
+ 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, 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, 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, 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, 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, NULL_TREE, base_obj,
+ current_bb_info, locus);
+ }
+
+ if (warn_thread_unguarded_func)
+ {
+ tree arg;
+ tree lock;
+
+ /* Handle the attributes specifying the lock requirements of
+ functions. */
+ if ((attr = lookup_attribute("exclusive_locks_required",
+ DECL_ATTRIBUTES (fdecl))) != NULL_TREE)
+ {
+ for (arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg))
+ {
+ lock = TREE_VALUE (arg);
+ gcc_assert (lock);
+ /* If lock is the error_mark_node, just set it to NULL_TREE
+ so that check_lock_required will reduce the level of checking
+ (i.e. only checks whether there is any live lock at this
+ point. */
+ if (lock == error_mark_node)
+ lock = NULL_TREE;
+ else if (TREE_CODE (lock) == INTEGER_CST)
+ lock = get_actual_argument_from_position (call, lock);
+ check_lock_required (lock, fdecl, base_obj,
+ false /* is_indirect_ref */,
+ live_excl_locks, live_shared_locks,
+ locus, TSA_WRITE);
+ }
+ }
+
+ if ((attr = lookup_attribute("shared_locks_required",
+ DECL_ATTRIBUTES (fdecl))) != NULL_TREE)
+ {
+ for (arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg))
+ {
+ lock = TREE_VALUE (arg);
+ gcc_assert (lock);
+ /* If lock is the error_mark_node, just set it to NULL_TREE
+ so that check_lock_required will reduce the level of checking
+ (i.e. only checks whether there is any live lock at this
+ point. */
+ if (lock == error_mark_node)
+ lock = NULL_TREE;
+ else if (TREE_CODE (lock) == INTEGER_CST)
+ lock = get_actual_argument_from_position (call, lock);
+ check_lock_required (lock, fdecl, base_obj,
+ false /* is_indirect_ref */,
+ live_excl_locks, live_shared_locks,
+ locus, TSA_READ);
+ }
+ }
+
+ if ((attr = lookup_attribute("locks_excluded", DECL_ATTRIBUTES (fdecl)))
+ != NULL_TREE)
+ {
+ /* 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 */);
+ 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);
+ }
+
+ for (arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg))
+ {
+ tree lock_contained;
+ lock = TREE_VALUE (arg);
+ gcc_assert (lock);
+ /* If lock is the error_mark_node, just ignore it. */
+ if (lock == error_mark_node)
+ continue;
+ if (TREE_CODE (lock) == INTEGER_CST)
+ lock = get_actual_argument_from_position (call, lock);
+ lock = get_canonical_lock_expr (lock, NULL_TREE,
+ false /* is_temp_expr */);
+ gcc_assert (lock);
+ /* 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)))
+ {
+ void **entry = pointer_map_contains (lock_locus_map,
+ lock_contained);
+ if (entry)
+ warning (OPT_Wthread_safety,
+ G_("%HCannot call function %qE with lock %s held"
+ " (previously acquired at line %d)"),
+ locus, DECL_NAME (fdecl),
+ dump_expr_tree (lock_contained, lname),
+ LOCATION_LINE (*((location_t *) *entry)));
+ else
+ warning (OPT_Wthread_safety,
+ G_("%HCannot call function %qE with lock %s held"
+ " (at function entry)"),
+ locus, DECL_NAME (fdecl),
+ dump_expr_tree (lock_contained, lname));
+ }
+ }
+ }
+ }
+}
+
+/* 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;
+ 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 && 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.
+ TODO: Note that to determine if a class member function is const, we
+ would've liked to use DECL_CONST_MEMFUNC_P instead of the following
+ long sequence of macro calls. However, we got an undefined reference
+ to `cp_type_quals' at link time when using DECL_CONST_MEMFUNC_P. */
+ bool is_const_memfun = TYPE_READONLY
+ (TREE_TYPE (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (fdecl)))));
+ enum access_mode rw_mode = is_const_memfun ? 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 (TREE_CODE (arg) == ADDR_EXPR)
+ {
+ /* 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;
+ }
+
+ /* Analyze the call's arguments */
+ for ( ; arg_index < num_args; ++arg_index)
+ {
+ arg = gimple_call_arg (call, arg_index);
+ if (!CONSTANT_CLASS_P (arg))
+ analyze_expr (arg, NULL_TREE, false /* is_indirect_ref */,
+ current_bb_info->live_excl_locks,
+ current_bb_info->live_shared_locks, &locus, TSA_READ);
+ }
+
+ /* Analyze the call's lhs if existing. */
+ lhs = gimple_call_lhs (call);
+ if (lhs != NULL_TREE)
+ 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 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 */);
+
+ /* 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 (OPT_Wthread_safety,
+ G_("%HLock %s (acquired at line %d) is not released at"
+ " the end of its scope in function %qE"),
+ loc, dump_expr_tree (lock_tree, lname),
+ LOCATION_LINE (*loc),
+ DECL_NAME (current_function_decl));
+ }
+ else
+ warning (OPT_Wthread_safety,
+ G_("%HLock %s (held at entry) is released on some but not all"
+ " control flow paths in function %qE"),
+ &DECL_SOURCE_LOCATION (current_function_decl),
+ 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 (OPT_Wthread_safety,
+ G_("%HLock %s (acquired at line %d) is not released at the end"
+ " of function %qE"),
+ loc, 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;
+
+ if (!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 to bb's live (working) sets. */
+ if (current_bb_info->live_excl_locks == NULL)
+ {
+ 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);
+ }
+ }
+
+ /* 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 */
+ 0, /* tv_id */
+ PROP_cfg | PROP_ssa, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ TODO_dump_func /* todo_flags_finish */
+ }
+};