I was playing a bit around to see if it is possible to figure out if a
variable is accessed without holding the corresponding lock. This never
got anywhere and that's why I abandon it. In case someone wants to
use parts of it or whatever, please be my guest.

Example output:

./test-locks validation/caps.c
validation/caps.c:49:13: warning: function 'set_str' not holding 'anon'
validation/caps.c:25:13: warning: leaks lock 'anon'
  frame: ep 0x7f0af437e010 a
  frame: ep 0x7f0af437e0a0 okay1 (call        a)
  frame: ep 0x7f0af437e0d0 bad2 (call        okay1)

validation/caps.c:70:8: warning: leaks lock 'global_lock'
  frame: ep 0x7f0af437e100 wtf_lock
  frame: ep 0x7f0af437e1f0 bad4 (call        wtf_lock, global_lock)

Signed-off-by: Daniel Wagner <[email protected]>
---
 Makefile          |    3 +-
 lib.c             |    2 +
 lib.h             |    7 +
 test-locks.c      | 1020 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 validation/caps.c |   80 +++++
 5 files changed, 1111 insertions(+), 1 deletion(-)
 create mode 100644 test-locks.c
 create mode 100644 validation/caps.c

diff --git a/Makefile b/Makefile
index c7031af..db0c343 100644
--- a/Makefile
+++ b/Makefile
@@ -54,7 +54,8 @@ INCLUDEDIR=$(PREFIX)/include
 PKGCONFIGDIR=$(LIBDIR)/pkgconfig
 
 PROGRAMS=test-lexing test-parsing obfuscate compile graph sparse \
-        test-linearize example test-unssa test-dissect ctags
+        test-linearize example test-unssa test-dissect ctags \
+        test-locks
 INST_PROGRAMS=sparse cgcc
 INST_MAN1=sparse.1 cgcc.1
 
diff --git a/lib.c b/lib.c
index 8dc5bcf..7b49e2f 100644
--- a/lib.c
+++ b/lib.c
@@ -244,6 +244,7 @@ int Wvla = 1;
 
 int dbg_entry = 0;
 int dbg_dead = 0;
+int dbg_caps = 0;
 
 int preprocess_only;
 
@@ -518,6 +519,7 @@ static char **handle_switch_W(char *arg, char **next)
 static struct warning debugs[] = {
        { "entry", &dbg_entry},
        { "dead", &dbg_dead},
+       { "caps", &dbg_caps},
 };
 
 
diff --git a/lib.h b/lib.h
index 15b69fa..87d1948 100644
--- a/lib.h
+++ b/lib.h
@@ -130,6 +130,7 @@ extern int Wvla;
 
 extern int dbg_entry;
 extern int dbg_dead;
+extern int dbg_caps;
 
 extern int arch_m64;
 
@@ -189,6 +190,12 @@ static inline struct basic_block *first_basic_block(struct 
basic_block_list *hea
 {
        return first_ptr_list((struct ptr_list *)head);
 }
+
+static inline struct basic_block *last_basic_block(struct basic_block_list 
*head)
+{
+       return last_ptr_list((struct ptr_list *)head);
+}
+
 static inline struct instruction *last_instruction(struct instruction_list 
*head)
 {
        return last_ptr_list((struct ptr_list *)head);
diff --git a/test-locks.c b/test-locks.c
new file mode 100644
index 0000000..f311f4c
--- /dev/null
+++ b/test-locks.c
@@ -0,0 +1,1020 @@
+/*
+ * This is an miserable attempt in static code analysis. The test
+ * executes the program and maintains a current set of locks. At every
+ * function entry and exit point all the current lock set is attached
+ * to the function and the current calling stack is also added to the lock.
+ *
+ * In the second phase the check_leaking() function then looks at all
+ * locks and the calling stacks and reports all those function which
+ * have an lock in the exit lock set and no other function is calling them.
+ * Obvioysly reallity is much more complex and this simple testing
+ * will not catch a lot of wrong cases.
+ *
+ * There is also the attempt to figure out if a variable has been accessed
+ * without holding the corresponding lock.
+ *
+ * If you want to understand what is doing run it with
+ *
+ *   $ test-locks validation/caps.c -vcaps -v -v -v
+ *
+ * which print a lot of details.
+ *
+ * Copyright (C) 2016  BMW Car IT GmbH.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "lib.h"
+#include "allocate.h"
+#include "token.h"
+#include "parse.h"
+#include "symbol.h"
+#include "expression.h"
+#include "linearize.h"
+#include "flow.h"
+#include "scope.h"
+
+#define dbg_level0 (dbg_caps)
+#define dbg_level1 (dbg_caps && verbose > 0)
+#define dbg_level2 (dbg_caps && verbose > 1)
+#define dbg_level3 (dbg_caps && verbose > 2)
+
+struct frame {
+       struct instruction *insn;
+       struct entrypoint *ep;
+};
+
+ALLOCATOR(frame, "call stack");
+DECLARE_PTR_LIST(frame_list, struct frame);
+
+struct frame *alloc_frame(struct entrypoint *ep, struct instruction *insn)
+{
+       struct frame *frame = __alloc_frame(0);
+
+       frame->ep = ep;
+       frame->insn = insn;
+       return frame;
+}
+
+static inline struct frame *last_frame(struct frame_list *head)
+{
+       return last_ptr_list((struct ptr_list *)head);
+}
+
+static inline struct frame *first_frame(struct frame_list *head)
+{
+       return first_ptr_list((struct ptr_list *)head);
+}
+
+static inline struct frame *delete_last_frame(struct frame_list **head)
+{
+       return delete_ptr_list_last((struct ptr_list **)head);
+}
+
+static inline void add_frame(struct frame_list **list, struct frame *frame)
+{
+       add_ptr_list(list, frame);
+}
+
+static inline struct frame *lookup_frame(struct frame_list *list,
+                                       struct  entrypoint *ep)
+{
+       struct frame *c;
+
+       FOR_EACH_PTR(list, c) {
+               if (c->ep == ep)
+                       return c;
+       } END_FOR_EACH_PTR(c);
+
+       return NULL;
+}
+
+static inline int frame_list_size(struct frame_list *head)
+{
+       return ptr_list_size((struct ptr_list *)head);
+}
+
+static inline int frame_cmp(struct frame *f1, struct frame *f2)
+{
+       return !(f1->ep == f2->ep && f1->insn == f2->insn);
+}
+
+static int stack_cmp(struct frame_list *s1, struct frame_list *s2)
+{
+       struct frame *f1, *f2;
+
+       PREPARE_PTR_LIST(s1, f1);
+       PREPARE_PTR_LIST(s2, f2);
+       for (;;) {
+               if (!f1 || !f2)
+                       return 1;
+
+               if (frame_cmp(f1, f2))
+                       return 1;
+
+               NEXT_PTR_LIST(f1);
+               NEXT_PTR_LIST(f2);
+       }
+       FINISH_PTR_LIST(f2);
+       FINISH_PTR_LIST(f1);
+
+       return 0;
+}
+
+static int stack_frame_position(struct frame_list *stack, struct entrypoint 
*ep)
+{
+       struct frame *f;
+       int pos = 0;
+
+       FOR_EACH_PTR(stack, f) {
+               if (f->ep == ep)
+                       return pos;
+               pos++;
+       } END_FOR_EACH_PTR(f);
+
+       return -1;
+}
+
+static void copy_stack(struct frame_list *from, struct frame_list **to)
+{
+       struct frame *c;
+
+       FOR_EACH_PTR(from, c) {
+               add_frame(to, alloc_frame(c->ep, c->insn));
+       } END_FOR_EACH_PTR(c);
+}
+
+/* Better naming wouldn't hurt. we could also do a frame_list** in
+ * struct lock but that is really hard to read. Let's wrap it here and
+ * use it as 2 dimensional list.
+ */
+struct stack {
+       struct frame_list *stack;
+};
+ALLOCATOR(stack, "stack list");
+DECLARE_PTR_LIST(stack_list, struct stack);
+
+static inline void add_stack(struct stack_list **list, struct stack *stack)
+{
+       add_ptr_list(list, stack);
+}
+
+struct lock {
+       unsigned int ref;
+       struct symbol *sym;
+       struct capability_list *caps;
+       struct stack_list *stacks;
+};
+
+ALLOCATOR(lock, "lock list");
+DECLARE_PTR_LIST(lock_list, struct lock);
+
+static struct lock *alloc_lock(struct symbol *sym)
+{
+       struct lock *lock = __alloc_lock(0);
+
+       lock->ref = 1;
+       lock->sym = sym;
+
+       return lock;
+};
+
+static inline void ref_lock(struct lock *lock)
+{
+       lock->ref++;
+}
+
+static inline unsigned int unref_lock(struct lock *lock)
+{
+       return --lock->ref;
+}
+
+static inline void add_lock(struct lock_list **list, struct lock *lock)
+{
+       add_ptr_list(list, lock);
+}
+
+static inline void remove_lock(struct lock_list **list, struct lock *lock)
+{
+       delete_ptr_list_entry((struct ptr_list **)list, lock, 1);
+}
+
+static struct lock *lookup_lock(struct lock_list *list, struct symbol *sym)
+{
+       struct lock *l;
+
+       FOR_EACH_PTR(list, l) {
+               if (sym == l->sym)
+                       return l;
+       } END_FOR_EACH_PTR(l);
+
+       return NULL;
+}
+
+static struct lock_list *all_locks;
+
+static void add_stack_trace(struct frame_list *stack, struct lock *lock)
+{
+       struct stack *st;
+
+       /* Filter out duplicates */
+       FOR_EACH_PTR(lock->stacks, st) {
+               if (!stack_cmp(stack, st->stack))
+                       return;
+       } END_FOR_EACH_PTR(st);
+
+       st = __alloc_stack(0);
+       copy_stack(stack, &st->stack);
+       add_stack(&lock->stacks, st);
+}
+
+/* Copy the current lock set to either the entry or exit list of the
+ * function. Remember also the calling stack. This will be handy
+ * in the second step of the analysis when we check for leaking
+ * locks etc.
+ */
+static void copy_lock_list(struct frame_list *stack,
+                               struct lock_list *from, struct lock_list **to)
+{
+       struct lock *lock, *l;
+
+       FOR_EACH_PTR(from, lock) {
+               /* let's colapse symbols with the same addresse to one
+                * lock and remove duplicates by this.
+                */
+               l = lookup_lock(all_locks, lock->sym);
+               if (!l) {
+                       l = alloc_lock(lock->sym);
+                       add_lock(&all_locks, l);
+               } else
+                       ref_lock(l);
+
+               if (!lookup_lock(*to, lock->sym))
+                       add_lock(to, l);
+
+               add_stack_trace(stack, l);
+       } END_FOR_EACH_PTR(lock);
+}
+
+static inline int lock_list_size(struct lock_list *list)
+{
+       return ptr_list_size((struct ptr_list *)list);
+}
+
+struct bb_info {
+       struct lock_list *entry;
+       struct lock_list *exit;
+};
+ALLOCATOR(bb_info, "bb info");
+
+/* debug printfs helpers */
+
+static void show_call_stack(struct frame_list *list, int level)
+{
+       struct frame *c;
+
+       FOR_EACH_PTR_REVERSE(list, c) {
+               printf("%*sframe: ep %p %s", level * 2, "", c->ep,
+                       show_ident(c->ep->name->ident));
+               if (c->insn)
+                       printf(" (%s)", show_instruction(c->insn));
+               printf("\n");
+       } END_FOR_EACH_PTR_REVERSE(c);
+}
+
+static void show_short_symbol_list(struct symbol_list *list, const char *name)
+{
+       struct symbol *sym;
+       const char *prepend = "";
+
+       printf("%s:\t", name);
+       FOR_EACH_PTR(list, sym) {
+               printf("%s", prepend);
+               prepend = ", ";
+               printf("%s",  show_ident(sym->ident));
+       } END_FOR_EACH_PTR(sym);
+       puts("");
+}
+
+static void show_pseudo_user_list(struct pseudo_user_list *list,
+                                 const char *name)
+{
+       struct pseudo_user *pu;
+
+       if (ptr_list_empty(list))
+               return;
+       printf("%s:\n", name);
+       FOR_EACH_PTR(list, pu) {
+               printf("\t%s used by %p, %s\n",
+                       show_pseudo(*pu->userp), pu->insn,
+                       show_instruction(pu->insn));
+       } END_FOR_EACH_PTR(pu);
+}
+
+static void show_pseudo_list(struct pseudo_list *list, const char *name)
+{
+       struct pseudo *p;
+       struct symbol *s;
+
+       printf("%s:\n", name);
+       FOR_EACH_PTR(list, p) {
+               printf("\t%s",  show_pseudo(p));
+               switch (p->type) {
+               case PSEUDO_SYM:
+                       printf(" (symbol %p scope %p ", p->sym, p->sym->scope);
+                       s = p->sym->next_id;
+                       while (s) {
+                               printf("next_id %p ", s);
+                               s = s->next_id;
+                       }
+                       show_type(p->sym);
+                       printf(")\n");
+                       break;
+               case PSEUDO_ARG:
+                       printf(" (");
+                       show_pseudo_user_list(p->users, "users");
+                       printf(" )\n");
+                       break;
+               default:
+                       break;
+               }
+       } END_FOR_EACH_PTR(p);
+}
+
+static void show_basic_block_list(struct basic_block_list *list,
+                                 const char *name)
+{
+       struct basic_block *bb;
+       const char *prepend = "";
+
+       printf("%s:\t", name);
+       FOR_EACH_PTR(list, bb) {
+               printf("%s", prepend);
+               prepend = ", ";
+               printf("%p",  bb);
+       } END_FOR_EACH_PTR(bb);
+       puts("");
+}
+
+static const char * const cap_ops[] = {
+       [CAP_ACQUIRE] = "acquire",
+       [CAP_RELEASE] = "release",
+       [CAP_REQUIRES] = "requires",
+       [CAP_GUARDED_BY] = "guarded_by",
+};
+
+static void show_cap(struct capability *cap)
+{
+       printf("cap %p %s '%s'\n", cap,
+               cap_ops[cap->op],
+               cap->expr ? show_ident(cap->expr->symbol_name) : "anon");
+}
+
+static void show_lock(struct lock *lock, const char *prepend,
+               const char *postfix)
+{
+       printf("%ssym %p '%s' %s",
+               prepend, lock->sym,
+               lock->sym ? show_ident(lock->sym->ident) : "anon",
+               postfix);
+}
+
+static void show_lock_list(struct lock_list *locks,
+                       const char *prepend, const char *postfix)
+{
+       struct lock *lock;
+
+       FOR_EACH_PTR(locks, lock) {
+               show_lock(lock, prepend, postfix);
+       } END_FOR_EACH_PTR(lock);
+}
+
+static void show_cap_list(struct capability_list *list, const char *name)
+{
+       struct capability *cap;
+       const char *prepend = "";
+
+       printf("%s:\t", name);
+       FOR_EACH_PTR(list, cap) {
+               printf("%s", prepend);
+               prepend = ", ";
+               show_cap(cap);
+       } END_FOR_EACH_PTR(cap);
+       puts("");
+}
+
+static void show_bb_lock_list(struct basic_block_list *list, const char *name)
+{
+       struct basic_block *bb;
+
+       printf("%s:\n", name);
+       FOR_EACH_PTR(list, bb) {
+               struct bb_info *info = bb->priv;
+
+               printf("\tbb %p\n", bb);
+               printf("\t\tlocks entry:\n");
+               show_lock_list(info->entry, "\t\t\t", "\n");
+               printf("\t\tlocks exit:\n");
+               show_lock_list(info->exit, "\t\t\t", "\n");
+       } END_FOR_EACH_PTR(bb);
+}
+
+static void show_entry_lock_list(struct entrypoint *ep, int level)
+{
+       struct basic_block *bb = ep->entry->bb;
+       struct bb_info *info = bb->priv;
+
+       printf("%*slocks entry:", level * 2, "");
+       show_lock_list(info->entry, " ", ",");
+       printf("\n");
+}
+
+static void show_exit_lock_list(struct entrypoint *ep, int level)
+{
+       struct basic_block *bb = last_basic_block(ep->bbs);
+       struct bb_info *info = bb->priv;
+
+       printf("%*slocks exit:", level * 2, "");
+       show_lock_list(info->exit, " ", ",");
+       printf("\n");
+}
+
+static void show_entrypoint(struct entrypoint *ep)
+{
+       printf("ep:\t%s\n", ep->name->ident->name);
+       show_short_symbol_list(ep->name->ctype.base_type->arguments, "args");
+       show_short_symbol_list(ep->syms, "syms");
+       show_pseudo_list(ep->accesses, "accesses");
+       show_basic_block_list(ep->bbs, "bbs");
+       show_pseudo_list(ep->entry->arg_list, "arg_list");
+       show_cap_list(ep->name->ctype.capabilities, "caps");
+       show_bb_lock_list(ep->bbs, "locks");
+}
+
+static struct pseudo *lookup_accesses(struct entrypoint *ep,
+                                       struct ident *ident)
+{
+       struct pseudo *p;
+
+       if (!ident)
+               return NULL;
+
+       FOR_EACH_PTR(ep->accesses, p) {
+               if (p->ident && !strcmp(p->ident->name, ident->name))
+                       return p;
+       } END_FOR_EACH_PTR(p);
+
+       return NULL;
+}
+
+static struct pseudo *get_argument_pseudo(struct pseudo_list *list, int nr)
+{
+       struct pseudo *p;
+       int i = 1;
+
+       FOR_EACH_PTR(list, p) {
+               if (i == nr)
+                       return p;
+               i++;
+       } END_FOR_EACH_PTR(p);
+
+       return NULL;
+}
+
+static int get_argument_nr(struct symbol_list *list, struct pseudo *p)
+{
+       int i = 0;
+       struct symbol *sym;
+
+       FOR_EACH_PTR(list, sym) {
+               i++;
+               if (strcmp(sym->ident->name, p->ident->name))
+                       continue;
+               return i;
+       } END_FOR_EACH_PTR(sym);
+
+       return -1;
+}
+
+static struct symbol *figure_access(struct pseudo *p, struct frame_list *stack)
+{
+       struct symbol *sym, *type;
+       struct frame *f;
+       int argc = -1;
+
+       FOR_EACH_PTR_REVERSE(stack, f) {
+               if (argc >= 0) {
+                       p = get_argument_pseudo(f->insn->arguments, argc);
+                       if (!p) {
+                               printf("bug bug bug bug for YOUUUUU!!\n");
+                               return NULL;
+                       }
+
+                       argc = -1;
+               }
+
+               if (p->type == PSEUDO_ARG) {
+                       /* this is an argument so we can just go on
+                        * frame up and look at the caller.
+                        */
+                       argc = p->nr;
+                       continue;
+               }
+
+               if (p->type == PSEUDO_SYM) {
+                       sym = get_base_type(p->sym);
+                       if (is_ptr_type(sym)) {
+                               /* so we have a pointer. was it passed
+                                * in as argument?
+                                */
+                               type = f->ep->name->ctype.base_type;
+                               argc = get_argument_nr(type->arguments, p);
+                               continue;
+                       } else
+                               return p->sym;
+               }
+
+               /* p is not ARG nor SYM, that's the end of the story */
+               break;
+       } END_FOR_EACH_PTR_REVERSE(f);
+
+       return NULL;
+}
+
+static struct symbol *figure_capability(struct capability *cap,
+                                       struct entrypoint *ep,
+                                       struct frame_list *stack)
+{
+       struct symbol *sym;
+       struct pseudo *p;
+
+       if (!cap->expr)
+               return NULL;
+
+       if (cap->expr->symbol) {
+               /* so from the linearization step we have a proper symbol,
+                * just get the pseudo.
+                */
+               p = lookup_accesses(ep, cap->expr->symbol->ident);
+       } else {
+               /* no luck, we only have the name so let's use that one
+                * instead.
+                */
+               p = lookup_accesses(ep, cap->expr->symbol_name);
+       }
+
+       if (p) {
+               /* we found a pseudo in this function, let's try to
+                * defer it, that is where is defined. note the final
+                * call frame also handles the global symbols because
+                * they are also listed in the accesses list.
+                */
+               sym = figure_access(p, stack);
+       } else {
+               /* the context attributes didn't get the linearize
+                * treatment, that means the access has not been
+                * added to the accesses list. first we tried
+                * to lookup in the accesses list but it is not there
+                * so we just try to see if it is a global symbol.
+                */
+               sym = lookup_symbol(cap->expr->symbol_name, NS_SYMBOL);
+       }
+
+       return sym;
+}
+
+static void acquire_lock(struct lock_list **lset, struct entrypoint *ep,
+                       struct frame_list *stack, struct capability *cap)
+{
+       struct symbol *sym;
+       struct lock *lock;
+
+       sym = figure_capability(cap, ep, stack);
+       lock = lookup_lock(*lset, sym);
+
+       if (!lock) {
+               lock = alloc_lock(sym);
+               add_lock(lset, lock);
+       } else
+               ref_lock(lock);
+}
+
+static void release_lock(struct lock_list **lset, struct entrypoint *ep,
+                       struct frame_list *stack, struct capability *cap)
+{
+       struct symbol *sym;
+       struct lock *lock;
+
+       sym = figure_capability(cap, ep, stack);
+       lock = lookup_lock(*lset, sym);
+
+       if (!lock)
+               return;
+
+       if (!unref_lock(lock)) {
+               remove_lock(lset, lock);
+               __free_lock(lock);
+       }
+}
+
+static void check_cap_requires(struct lock_list **lset,
+                               struct entrypoint *ep, struct frame_list *stack)
+{
+       struct capability *cap;
+
+       if (frame_list_size(stack) == 1) {
+               /* there is little point in checking if the caller holds
+                * the required locks if there is no caller.
+                */
+               return;
+       }
+
+       /* First step of validation. Let's check if we see
+        * the required lock in the working set.
+        */
+       FOR_EACH_PTR(ep->name->ctype.capabilities, cap) {
+               struct symbol *s = figure_capability(cap, ep, stack);
+               struct lock *l;
+
+               switch (cap->op) {
+               case CAP_REQUIRES:
+                       l = lookup_lock(*lset, s);
+                       if (!l) {
+                               warning(ep->name->pos,
+                                       "function '%s' not holding '%s'",
+                                       show_ident(ep->name->ident),
+                                       s ? s->ident->name : "anon");
+                       }
+                       break;
+               default:
+                       break;
+               }
+       } END_FOR_EACH_PTR(cap);
+}
+
+static void update_locks_on_entry(struct lock_list **lset,
+                                 struct entrypoint *ep,
+                                 struct frame_list *stack,
+                                 int level)
+{
+       struct basic_block *bb = ep->entry->bb;
+       struct bb_info *info = bb->priv;
+
+       copy_lock_list(stack, *lset, &info->entry);
+
+       check_cap_requires(lset, ep, stack);
+
+       if (dbg_level0)
+               show_entry_lock_list(ep, level);
+}
+
+static void update_locks_on_exit(struct lock_list **lset,
+                                struct entrypoint *ep,
+                                struct frame_list *stack,
+                                int level)
+{
+       struct basic_block *bb = last_basic_block(ep->bbs);
+       struct bb_info *info = bb->priv;
+       struct capability *cap;
+
+       FOR_EACH_PTR(ep->name->ctype.capabilities, cap) {
+               struct symbol *s = figure_capability(cap, ep, stack);
+               struct lock *l;
+
+               switch (cap->op) {
+               case CAP_ACQUIRE:
+                       acquire_lock(lset, ep, stack, cap);
+                       break;
+               case CAP_RELEASE:
+                       release_lock(lset, ep, stack, cap);
+                       break;
+               case CAP_REQUIRES:
+                       l = lookup_lock(*lset, s);
+                       if (!l) {
+                               warning(ep->name->pos,
+                                       "function '%s' not holding '%s'",
+                                       show_ident(ep->name->ident),
+                                       s ? s->ident->name : "anon");
+                       }
+                       break;
+               default:
+                       break;
+               }
+       } END_FOR_EACH_PTR(cap);
+
+       copy_lock_list(stack, *lset, &info->exit);
+
+       if (dbg_level0)
+               show_exit_lock_list(ep, level);
+}
+
+static int function_acquires_lock(struct entrypoint *ep)
+{
+       struct capability *cap;
+
+       FOR_EACH_PTR(ep->name->ctype.capabilities, cap) {
+               if (cap->op == CAP_ACQUIRE)
+                       return 1;
+       } END_FOR_EACH_PTR(cap);
+
+       return 0;
+}
+
+static void check_leaking(struct entrypoint *ep)
+{
+       struct basic_block *bb = last_basic_block(ep->bbs);
+       struct bb_info *info = bb->priv;
+       struct lock *lock;
+
+       /* Let's ignore function which acquire locks for the time
+        * beeing.
+        */
+       if (function_acquires_lock(ep))
+               return;
+
+       FOR_EACH_PTR(info->exit, lock) {
+               struct stack *st;
+               struct frame_list *leak = NULL;
+               int pos;
+
+               FOR_EACH_PTR(lock->stacks, st) {
+                       /* Find a call stack where this ep is the first
+                        * one.
+                        */
+                       pos = stack_frame_position(st->stack, ep);
+                       if (pos == 0) {
+                               if (function_acquires_lock(
+                                               last_frame(st->stack)->ep)) {
+                                       leak = st->stack;
+                               }
+                       } else if (pos > 0) {
+                               leak = NULL;
+                       }
+               } END_FOR_EACH_PTR(st);
+
+               if (!leak)
+                       continue;
+
+               warning(ep->name->pos, "leaks lock '%s'",
+                       lock->sym ? show_ident(lock->sym->ident) : "anon");
+               show_call_stack(leak, 1);
+               printf("\n");
+       } END_FOR_EACH_PTR(lock);
+}
+
+static void check_sym_access(struct instruction *insn,
+                       struct symbol *sym, struct basic_block *bb,
+                       struct lock_list *lset,
+                       struct frame_list *stack)
+{
+       struct capability *c;
+       struct lock *l;
+
+       FOR_EACH_PTR(sym->ctype.capabilities, c) {
+               struct symbol *s = figure_capability(c, bb->ep, stack);
+
+               l = lookup_lock(lset, s);
+               if (!l) {
+                       warning(insn->pos,
+                               "accessing '%s' without holding '%s'",
+                               sym->ident->name,
+                               s->ident->name);
+               }
+       } END_FOR_EACH_PTR(c);
+}
+
+static void check_bb(struct basic_block *bb, struct lock_list **lset,
+               struct frame_list *stack, unsigned long generation,
+               int level);
+
+static void handle_call(struct instruction *insn, struct lock_list **lset,
+                       struct frame_list *stack, int level)
+{
+       struct entrypoint *ep;
+       struct symbol *sym;
+       struct frame *frame;
+
+       if (!insn->func->ident)
+               return;
+
+       sym = lookup_symbol(insn->func->ident, NS_SYMBOL);
+       if (!sym)
+               return;
+       ep = sym->ep;
+
+       /* no code/entrypoint for this symbol, decleration only */
+       if (!ep)
+               return;
+
+       /* prevent recursions */
+       frame = lookup_frame(stack, ep);
+       if (frame)
+               return;
+
+       frame = last_frame(stack);
+       frame->insn = insn;
+       frame = alloc_frame(ep, NULL);
+       add_frame(&stack, frame);
+
+       if (dbg_level0)
+               printf("%*scall %s\n", level * 2, "",
+                       show_ident(ep->name->ident));
+       level++;
+       if (dbg_level2)
+               show_call_stack(stack, level);
+
+       update_locks_on_entry(lset, ep, stack, level);
+       check_bb(ep->entry->bb, lset, stack, ++bb_generation, level);
+       update_locks_on_exit(lset, ep, stack, level);
+       level--;
+
+       if (dbg_level0)
+               printf("%*sret %s\n", level * 2, "",
+                       show_ident(ep->name->ident));
+
+       frame = delete_last_frame(&stack);
+       __free_frame(frame);
+}
+
+static void check_bb(struct basic_block *bb,
+               struct lock_list **lset, struct frame_list *stack,
+               unsigned long generation, int level)
+{
+       struct instruction *insn;
+       struct multijmp *mj;
+       struct symbol *sym;
+       struct bb_info *info = bb->priv;
+
+       if (!info) {
+               info = __alloc_bb_info(0);
+               bb->priv = info;
+       }
+
+       if (generation == bb->generation)
+               return;
+       bb->generation = generation;
+
+       if (dbg_level2)
+               printf("%*sentry bb %p\n", level * 2, "", bb);
+
+       FOR_EACH_PTR(bb->insns, insn) {
+               switch (insn->opcode) {
+               case OP_CALL:
+                       handle_call(insn, lset, stack, level);
+                       break;
+               case OP_BR:
+                       if (dbg_level2)
+                               level++;
+
+                       if (insn->bb_true)
+                               check_bb(insn->bb_true, lset, stack,
+                                        generation, level);
+                       if (insn->bb_false)
+                               check_bb(insn->bb_false, lset, stack,
+                                        generation, level);
+                       if (dbg_level2)
+                               level--;
+                       break;
+               case OP_SWITCH:
+               case OP_COMPUTEDGOTO:
+                       /* Note this one might jump backwards, which means
+                        * we need to be careful and not endup in an endless
+                        * loop.
+                        */
+                       if (dbg_level2)
+                               level++;
+                       FOR_EACH_PTR(insn->multijmp_list, mj) {
+                               check_bb(mj->target, lset, stack,
+                                        generation, level);
+                       } END_FOR_EACH_PTR(mj);
+                       if (dbg_level2)
+                               level--;
+                       break;
+
+               case OP_LOAD:
+               case OP_STORE:
+                       sym = figure_access(insn->src, stack);
+                       if (dbg_level1)
+                               printf("%*sld/st: %p %s\n", level * 2, "",
+                                       sym, sym ? show_ident(sym->ident) : "");
+                       if (sym)
+                               check_sym_access(insn, sym, bb, *lset, stack);
+                       break;
+               }
+       } END_FOR_EACH_PTR(insn);
+
+       if (dbg_level2)
+               printf("%*sexit  bb %p\n", level * 2, "", bb);
+}
+
+static void check_entrypoint(struct entrypoint *ep)
+{
+       struct frame_list *stack;
+       struct frame *frame;
+       struct lock_list *lset = NULL;
+       struct bb_info *info = ep->entry->bb->priv;
+       int level = 0;
+
+       if (!info) {
+               info = __alloc_bb_info(0);
+               ep->entry->bb->priv = info;
+       }
+
+       stack = NULL;
+       frame = alloc_frame(ep, NULL);
+       add_frame(&stack, frame);
+
+       update_locks_on_entry(&lset, ep, stack, level);
+       check_bb(ep->entry->bb, &lset, stack, ++bb_generation, level);
+       update_locks_on_exit(&lset, ep, stack, level);
+
+       frame = delete_last_frame(&stack);
+       __free_frame(frame);
+}
+
+DECLARE_PTR_LIST(entrypoint_list, struct entrypoint);
+
+static inline void add_entrypoint(struct entrypoint_list **list,
+                                 struct entrypoint *ep)
+{
+       add_ptr_list(list, ep);
+}
+
+static void check_symbols(struct symbol_list *list)
+{
+       struct entrypoint_list *eps = NULL;
+       struct entrypoint *ep;
+       struct symbol *sym;
+
+       /* First pass collect all locks */
+       FOR_EACH_PTR(list, sym) {
+               expand_symbol(sym);
+               ep = linearize_symbol(sym);
+               if (ep) {
+                       if (dbg_level0)
+                               printf("\n## %s()\n",
+                                       show_ident(ep->name->ident));
+
+                       /* Call each function and collect all locks
+                        * taken at entry and exit point of function.
+                        */
+                       check_entrypoint(ep);
+
+                       add_entrypoint(&eps, ep);
+               }
+       } END_FOR_EACH_PTR(sym);
+
+       FOR_EACH_PTR(eps, ep) {
+               if (dbg_level3) {
+                       printf("\n## %s()\n",
+                               show_ident(ep->name->ident));
+                       show_entrypoint(ep);
+                       printf("\n");
+               }
+
+               check_leaking(ep);
+       } END_FOR_EACH_PTR(ep);
+}
+
+int main(int argc, char **argv)
+{
+       struct string_list *filelist = NULL;
+       char *file;
+
+       add_pre_buffer("#define __TESTLOCK__ 1\n");
+
+       check_symbols(sparse_initialize(argc, argv, &filelist));
+       FOR_EACH_PTR_NOTAG(filelist, file) {
+               check_symbols(sparse(file));
+       } END_FOR_EACH_PTR_NOTAG(file);
+
+#if 0
+       show_frame_alloc();
+       show_lock_alloc();
+#endif
+       return 0;
+}
diff --git a/validation/caps.c b/validation/caps.c
new file mode 100644
index 0000000..0ffa407
--- /dev/null
+++ b/validation/caps.c
@@ -0,0 +1,80 @@
+#define __must_hold(x) __attribute__((requires_capability(x)))
+#define __acquires(x)  __attribute__((acquire_capability(x)))
+#define __releases(x)  __attribute__((release_capability(x)))
+#define __guarded_by(x)        __attribute__((guarded_by(x)))
+
+static void a(void) __acquires()
+{
+}
+
+static void r(void) __releases()
+{
+}
+
+static void good1(void)
+{
+       a();
+       r();
+}
+
+static void okay1(void)
+{
+       a();
+}
+
+static void bad2(void)
+{
+       okay1();
+}
+
+struct hello {
+       char *str;
+       int lock;
+};
+
+static struct hello info __guarded_by(info.lock);
+
+static void wtf_lock(int *lock)
+       __acquires(lock)
+{
+       *lock = 1;
+}
+
+static void wtf_unlock(int *lock)
+       __releases(lock)
+{
+       *lock = 0;
+}
+
+static void set_str(struct hello *h)
+       __must_hold(h->lock)
+{
+       h->str = "hello";
+}
+
+static good2(void)
+{
+       wtf_lock(&info.lock);
+       set_str(&info);
+       wtf_unlock(&info.lock);
+}
+
+static int global_lock;
+
+static good3(void)
+{
+       wtf_lock(&global_lock);
+       wtf_unlock(&global_lock);
+}
+
+static bad4(void)
+{
+       wtf_lock(&global_lock);
+}
+
+/*
+ * check-name: Check capabilities
+ *
+ * check-error-start
+ * check-error-end
+ */
-- 
2.5.0

Reply via email to