[FAIL 5/5] test-locks: Add lock tester

From: Daniel Wagner
Date: Wed Feb 17 2016 - 04:53:03 EST


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 <daniel.wagner@xxxxxxxxxxxx>
---
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