GithubHelp home page GithubHelp logo

evincarofautumn / ward Goto Github PK

View Code? Open in Web Editor NEW
25.0 6.0 2.0 233 KB

A static analysis tool for C.

License: Other

Haskell 88.88% C 2.36% Shell 8.76%
static-analysis static-code-analysis c haskell locking signals

ward's Introduction

Ward

A static analysis tool for C.

Ward accepts C99 source files, annotated with permissions, and verifies that permissions are correct for all top-level functions. A permission is a constraint on the context in which some code is allowed to run, and can be used to verify things like locking, signal-safety, program phases (e.g., initialization and shutdown), and memory allocation (e.g., disallowing allocation in performance- or safety-critical code).

C programs tend to have a fair amount of global state, which can produce subtle errors if used incorrectly. Ward is designed to make it easier to use this sort of state correctly. It’s a fairly liberal analysis—the goal is to call attention to suspicious code, not verify everything precisely.

Building

Build Status

Ward is written in Haskell using the language-c package for parsing C99. You need Stack to build it.

> git clone [email protected]:evincarofautumn/Ward.git
> cd Ward
> stack setup  # if this is your first time using Stack
> stack build
> stack exec ward -- --help

Ward - A static analysis tool for C.

Usage: ward CPP [-M|--mode html|compiler|graph] [-C|--config FILE] [-q|--quiet]
            PATH... [-P|--preprocess FLAG]
  Verify Ward permissions for all top-level functions.

Available options:
  CPP                      Name of preprocessor.
  -M,--mode html|compiler|graph
                           Output mode style (default 'compiler'). The 'graph'
                           mode does not run analyses, just parses the source
                           files and emits inferred call graph.
  -C,--config FILE         Read permission information from configuration FILE.
  -q,--quiet               Suppress output except for errors.
  PATH...                  Paths to C source files.
  -P,--preprocess FLAG     Pass FLAG to preprocessor.
  -h,--help                Show this help text

stack install will place a copy of the executable in ~/.local/bin (Mac & Linux) or %APPDATA%\local\bin (Windows).

Running

Ward accepts a path to a C preprocessor (e.g., gcc), a list of paths to C sources, one or more config files, and additional flags to pass to the preprocessor. On OS X, you’ll want to pass -fno-blocks or -U__BLOCKS__ because the C parsing library used by Ward does not yet support Apple’s block extension. A typical invocation looks like this:

ward gcc foo.c bar.c --config=my.config -- -I/some/include/path -U__BLOCKS__

Ward should build and run on OS X and Linux; it may work on Windows with a suitable preprocessor installed, but this hasn’t been tested.

Writing a Config File

Ward determines how to carry out its analysis based on config files with the following format. You can check out the grammar of config files for more details.

Comments

// This is a comment. It begins with two slashes and continues to the end of the line.

Declarations and Restrictions

In order to use a permission, you must declare it in the config by specifying its name.

my_permission;

A permission may be annotated with a description string for documentation purposes and to help produce better error messages.

my_permission "permission to do the thing";

After the permission name and before the description (if present), you can specify modifiers for the permission. Currently the only modifier is implicit:

alloc implicit "can allocate";

With the implicit modifier, the alloc permission will be implicitly granted to all functions unless they explicitly waive it (see “Annotating Your Code”).

Permissions may have any number of additional restrictions, which consist of an ASCII rightwards arrow -> followed by a Boolean expression using the operators & (and), | (or), and ! (not). Restrictions specify that if the permission to the left of the arrow is used, then the context must have or lack some additional permissions specified on the right. Like permissions, restrictions can also have description strings.

lock_held       "assume the lock is held";

take_lock       "take the lock"
  -> !lock_held "cannot take the lock recursively";

Enforcements

Ward will only enforce annotations for functions specified in the config, so you can add Ward annotations incrementally to an existing codebase. Enforcement directives begin with .enforce and accept:

  • A file name, to enforce annotations for all functions declared or defined in that file;
  • A function name, to enforce annotations for a particular function; or
  • Both a file name and function name, to enforce annotations for a single function if defined in the given file—this is useful for static functions.
.enforce "locks.h";                    // All functions declared in a path ending in "locks.h"
.enforce foo_bar;                      // All functions named "foo_bar"
.enforce "locks-internal.c" do_stuff;  // The function "do_stuff" in "locks-internal.c"

Annotating Your Code

Ward accepts annotations in the form of function-level attributes, which specify permissions and the actions Ward should take with them.

__attribute__((ward(action(perm, perm, …), action(perm, perm, …), …)))

An action may be any of the following:

Action Effect
need(p) When this function is called, p must be in the context.
use(p) Same as need, but expresses the intent that p is used directly, and should be subject to restrictions specified in the config.
deny(p) When this function is called, p must not be in the context.
waive(p) If p was declared implicit, this function is not granted p.
grant(p) After this function is called, p is added to the context.
revoke(p) After this function is called, p is removed from the context.

Here’s a typical example, where alloc is declared implicit:

#define PERM(...) __attribute__((ward(__VA_ARGS__)))

void must_not_allocate(void)
  PERM(waive(alloc));

void take_lock(void)
  PERM(need(lock), revoke(lock), grant(locked));

void must_have_lock_held(void)
  PERM(need(locked));

void release_lock(void)
  PERM(need(locked), revoke(locked), grant(lock));

These annotations enforce the following properties:

  • must_not_allocate:

    • waive(alloc): this function cannot call functions that implicitly need(alloc)
  • take_lock:

    • need(lock): if a function takes the lock, it must also be annotated with need(lock)
    • revoke(lock): when the lock is held, it can’t be recursively locked
    • grant(locked): a function annotated with need(locked) can only be called when the lock is held
  • must_have_lock_held:

    • need(locked): the lock must have been taken already before entering this function
  • release_lock:

    • need(locked): the lock must be held in order to release it
    • revoke(locked): when the lock is released, a function annotated with need(locked) can no longer be called
    • grant(lock): the lock can be taken again after it is released

By implicitly granting alloc, you only need to annotate those functions that are explicitly not allowed to allocate. You can do a similar thing with e.g. signal_unsafe implicit;—functions are not required to be signal-safe by default, but you want to make sure that no signal-safe function calls a signal-unsafe function!

In this way, Ward is pay-as-you-go: you only need to annotate and verify the specific source files and permissions you’re interested in, and you can define whatever meanings you like for a permission, because it’s just a label.

Known Deficiencies

Poor Performance

On a project of nontrivial size (dozens of kloc), Ward consumes a large amount of memory and operates slowly due to retaining a large amount of language-c AST information. The preferred method for checking a large project is to build a call map for each translation unit separately using --mode=graph, and then load & check all the call maps together. You can see an example of this in check-mono.sh.

No Indirect Calls

Ward currently does not handle indirect calls. In practice this is not a significant limitation, as the vast majority of calls in typical C code are direct, but it may be addressed in the future.

Limited Testing

It seems to be usable and effective in practice, but it’s not very well tested. We’re working on adding a comprehensive test suite.

No Complex Control Flow

It also doesn’t handle non-trivial control flow very well, so you may need to restructure some code in order to make it checkable. For example, Ward cannot verify this, because the locked permission is conditional:

void foo(bool lock) {
  if (lock) {
    take_lock();
  }
  …
  if (lock) {
    release_lock();
  }
}

Ward doesn’t know whether the if branch will be taken, so it conservatively assumes that it requires the permission to lock. This may be addressed in the future to support more coding patterns. However, as a workaround, currently it can verify this alternative formulation:

void foo_locked(void) {
  take_lock();
  foo_unlocked();
  release_lock();
}

void foo_unlocked(void) {
  …
}

(And this is arguably better coding practice, anyway.)

ward's People

Contributors

bgamari avatar evincarofautumn avatar lambdageek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ward's Issues

Config file for permission relationships

Re. mono/mono#4529 (comment) and mono/mono#4529 (comment), we want a way to specify how permissions are related. I propose adding a --config=<path> / -C<path> option, which reads a config file consisting of a series of declarations, each of which defines a permission or a relationship between permissions.

<config> ::= <decl>*
<decl> ::= <name> ("->" <expr>)? <desc>? ";"
<expr> ::= <or-expr>
<or-expr> ::= <and-expr> ("|" <and-expr>)*
<and-expr> ::= <term> ("&" <term>)*
<term> ::= <name> | "!" <term> | "(" <expr> ")"
<name> ::= /^[A-Za-z_][0-9A-Za-z_]*$/
<desc> ::= /^"([^"\\]|\\[\\"])*"$/

For example, suppose the foo lock can only be taken when the bar lock is held and the baz lock is not held.

lock_foo; foo_locked;
lock_bar; bar_locked;
lock_baz; baz_locked;

lock_foo -> bar_locked & !baz_locked;

Now checking need(lock_foo) also implies checking need(bar_locked) and deny(baz_locked).

When a config file is specified, use of a permission not declared in the config is an error, rather than implicitly creating the permission.

  • Parse config file (e95fa7e)
  • Check restrictions when checking permissions
  • Disallow undeclared permissions when using a config

Fix SGen issues

This issue is to track functions with (seemingly) legitimate permission errors that need to be fixed in order to use Ward with Mono.

  • Failure to lock or possible deadlock in mono-profiler-log.c: heap_walk assumes the GC lock is held and the world is stopped by calling mono_gc_walk_heap, but also assumes it can take the GC lock and stop the world in the EXIT_LOG_EXPLICIT macro, which may call process_requests, which may call mono_gc_collect. gc_event shares this problem by calling heap_walk.

  • Possible deadlock in sgen-gc.c: major_copy_or_mark_from_roots assumes the GC lock is held and the world is stopped, but calls sgen_nursery_allocator_prepare_for_pinning, which may indirectly take the GC lock via sgen_clear_allocator_fragmentssgen_clear_rangesgen_client_array_fill_rangeget_array_fill_vtablemono_gc_make_descr_for_array.

  • sgen-gc.c: collect_nursery assumes the world is stopped, but calls sgen_debug_verify_nursery, which may call sgen_nursery_allocator_prepare_for_pinning.

Error disappears when a module is added

Using 05b02cf I am seeing some rather concerning behavior when processing callmaps. I am using this configuration file:

sm_lock_held      "assume the storage manager lock is held";                
take_sm_lock      "the ability to take the storage manager lock"            
  -> ! sm_lock_held;                                                        
                                                                            
.enforce "sm/NonMovingMark.c";                                              
.enforce "sm/NonMovingMark.h";                                              
.enforce "sm/BlockAlloc.c";                                                 
.enforce "rts/sm/BlockAlloc.h";                                             
.enforce "Block.h";                                                         

I have two callmap files: rts/sm/BlockAlloc.c.ward.graph and rts/Capability.c.ward.graph.

Checking the Capability callmap alone correctly reports no errors:

$ ward gcc --mode=compiler --config=rts/config.ward    rts/Capability.c.ward.graph   
Loading config files...
Preprocessing and parsing...
Checking...
Warnings: 0, Errors: 0

Checking the BlockAlloc callmap alone correctly reports several errors:

$ ward gcc --mode=compiler --config=rts/config.ward rts/sm/BlockAlloc.c.ward.graph                                                                                                                                                       
Loading config files...
Preprocessing and parsing...
Checking...
rts/sm/BlockAlloc.h:13: error: missing required annotation on 'allocLargeChunk'; annotation [] is missing: [revoke(take_sm_lock),need(take_sm_lock),grant(sm_lock_held)]
includes/rts/storage/Block.h:320: error: missing required annotation on 'allocGroupOnNode_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:315: error: missing required annotation on 'allocGroup_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:317: error: missing required annotation on 'allocBlock_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:322: error: missing required annotation on 'allocBlockOnNode_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:333: error: missing required annotation on 'freeGroup_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:335: error: missing required annotation on 'freeChain_lock'; annotation [] is missing: [need(take_sm_lock)]
Warnings: 0, Errors: 7

However, when I check the Capability and BlockAlloc callmaps together the error vanishes:

$ ward gcc --mode=compiler --config=rts/config.ward rts/Capability.c.ward.graph rts/sm/BlockAlloc.c.ward.graph
Loading config files...
Preprocessing and parsing...
Checking...
Warnings: 0, Errors: 0

Even stranger, when I flip the order of the two the errors are again correctly reported,

$ ward gcc --mode=compiler --config=rts/config.ward rts/sm/BlockAlloc.c.ward.graph rts/Capability.c.ward.graph
Loading config files...
Preprocessing and parsing...
Checking...
rts/sm/BlockAlloc.h:13: error: missing required annotation on 'allocLargeChunk'; annotation [] is missing: [revoke(take_sm_lock),need(take_sm_lock),grant(sm_lock_held)]
includes/rts/storage/Block.h:320: error: missing required annotation on 'allocGroupOnNode_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:315: error: missing required annotation on 'allocGroup_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:317: error: missing required annotation on 'allocBlock_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:322: error: missing required annotation on 'allocBlockOnNode_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:333: error: missing required annotation on 'freeGroup_lock'; annotation [] is missing: [need(take_sm_lock)]
includes/rts/storage/Block.h:335: error: missing required annotation on 'freeChain_lock'; annotation [] is missing: [need(take_sm_lock)]
Warnings: 0, Errors: 7

Make annotations mandatory in some cases

E.g., in Mono, runtime entry- and exit-points should require annotations. (“things like icalls, the runtime API and when calling user supplied callbacks” — Kumpera)

Ward should define a preprocessor symbol

We should define __WARD__ or something similar so that non-Ward runs can ignore our attributes.

ie so we can do:

#if defined(__WARD__))
#  define WARD_PERMISSIONS(...) __attribute__((ward (__VA_ARGS__)))
#else
#  define WARD_PERMISSIONS(...) /*empty*/
#endif

Errors are reported on a single line

In compiler mode I have noticed that errors are reported on a single line, greatly compromising legibility. For instance:

$ ward gcc --mode=compiler --config=rts/config.ward    rts/sm/BlockAlloc.c.ward.graph rts/Capability.c.ward.graph
Loading config files...
Preprocessing and parsing...
Checking...
rts/sm/BlockAlloc.h:13: error: missing required annotation on 'allocLargeChunk'; annotation [] is missing: [revoke(take_sm_lock),need(take_sm_lock),grant(sm_lock_held)]includes/rts/storage/Block.h:320: error: missing required annotation on 'allocGroupOnNode_lock'; annotation [] is missing: [need(take_sm_lock)]includes/rts/storage/Block.h:315: error: missing required annotation on 'allocGroup_lock'; annotation [] is missing: [need(take_sm_lock)]includes/rts/storage/Block.h:317: error: missing required annotation on 'allocBlock_lock'; annotation [] is missing: [need(take_sm_lock)]includes/rts/storage/Block.h:322: error: missing required annotation on 'allocBlockOnNode_lock'; annotation [] is missing: [need(take_sm_lock)]includes/rts/storage/Block.h:333: error: missing required annotation on 'freeGroup_lock'; annotation [] is missing: [need(take_sm_lock)]includes/rts/storage/Block.h:335: error: missing required annotation on 'freeChain_lock'; annotation [] is missing: [need(take_sm_lock)]Warnings: 0, Errors: 7

Feature proposal: Parametric permissions

Currently permissions are merely names. However, Ward could be made significantly more powerful by extending the permission language with some notion of parameterisation on source program identifiers. To make this concrete, let's say we have some datastructure (call it Capability, drawing on the concept from GHC) which embeds a lock:

struct Capability {
  Mutex lock;
  int some_state;
};

We want to expose a function which can change some_state of a Capability yet only if the caller holds its lock. To do this we might define the following:

#define WARD(...) __attribute__((__VA_ARGS__))

WARD(need(may_take_capability_lock(cap), grant(holds_capablity_lock(cap)))
void acquire_capability_lock(Capability *cap) {
  ACQUIRE_MUTEX(&cap->lock);
}

WARD(need(holds_capability_lock(cap), revoke(holds_capablity_lock(cap)))
void release_capability_lock(Capability *cap) {
  RELEASE_MUTEX(&cap->lock);
}

WARD(need(holds_capability_lock(cap))
void set_state(Capability *cap, int new_state) {
  cap->state = new_state;
}

Here we have extended the permission language as follows:

permission_name := string
permission := permission_name ['(' arg_list ')']
arg_list := argument [',' arg_list]
argument := c_identifier

Where argument must be either a global variable or an identifier bound in the function's argument list. For simplicity, call sites of functions with such permissions would be restricted to only "trivial" parameters (e.g. just identifiers). This would allow permissions to be easily propagated from the call-site to the definition site. For instance,

WARD(need(may_take_capability_lock(cap))
void set_state_to_zero(Capability *the_cap) {
  acquire_capability_lock(the_cap);
  // We now have holds_capability_lock(the_cap) in our context
  set_state(the_cap, 0);
  // By the definition given above, the above call requires that we
  // have holds_capability_lock(the_cap), which we indeed have.
  release_capability_lock(the_cap);
}

The arity of a capability is fixed in the configuration. For instance the Ward configuration for the above might look like:

holds_capability_lock(cap) "The given capability's lock is held";
may_take_capability_lock(cap) "The may take the given capability's lock";

Note that under this proposal arguments are untyped. That is, one is free to write seemingly nonsensical things like,

WARD(grant(may_take_capability_lock(n)))
void do_something(int n) {  /* ... */ }

Additionally, the fact that permissions may take multiple arguments allows to represent relations between runtime values. For instance in GHC Tasks can "own" Capability's, allowing us to do some things in a lock-free manner,

__thread struct Task my_task; // the current thread's task

WARD(need(task_owns_capability(my_task, cap)))
void do_something(Capability *cap) { /* ... */ }

Limitations

There are, of course, many cases that this minimal proposal is not able to cover. For instance,

void set_all_to_zero(int n, Capabilities *caps[n]) {
  for (int i=0; i < n; i++)
    set_state_to_zero(caps[i]);
}

We sadly have no way to express the permission requirements of this function. To handle this we need the ability to embed permission actions in function bodies:

void set_all_to_zero(int n, Capability *caps[n]) {
  for (int i=0; i < n; i++) {
    Capability *cap = caps[i];
    WARD(grant(may_take_capability_lock(cap)));
    set_state_to_zero(cap);
  }
}

This of course makes set_all_to_zero part of the trusted codebase.

Extensions

Return terms

We could do slightly better on the above case by extending our permission syntax with:

argument := c_identifier
          | 'return'

Where the return keyword denotes the return value of the function. This allows us to write:

WARD(need(may_take_capability_lock(caps), grant(may_take_capability_lock(return)))
Capability *get_capability(int i, Capability *caps[i]) {
  return caps[i];
}

Allowing indexing

One could imagine further extending the permission syntax with:

argument := term [ '[' integer_literal ']' ]
term := c_identifier
      | 'return'

which would allow a few more patterns to be captured. For instance, output parameters:

WARD(grant(may_take_capability_lock(cap[0])))
void get_a_capability(Capability **cap) {  /* cap is an output */
  *cap = ...;
}

Implementation

My suspicion is that nothing of the above should be particularly difficult to implement. The only non-trivial aspect the proposal is the argument renaming necessary at call-sites but this is a simple mechanical rewrite.

Poor performance on real-world codebase

I was looking into using Ward to lint GHC's runtime system, starting with simple lock checking. Unfortunately even with only no privileges defined and enabling enforcement for a single file the check runs for more than 10 minutes before sending my laptop with 32GB of RAM into swap-death. This seems a bit high for a 50kLoC codebase.

Checking each source file individually typically takes around 30 seconds per file. Is this the recommended strategy for non-small projects?

Flesh out warnings and errors

  • Inconsistent annotations
  • Annotation on definition of non-static function
  • Added permission not granted
  • Dropped permission not revoked
  • Locally revoking permission not in context
  • Name collision between static definitions
  • Calling function with unknown permissions
  • Granting permission already in context
  • Aliasing variable with local permissions

Verbose mode with boring statistics?

At least in the HTML output, it would be nice to output some boring statistics (# of iterations until quiescence, total number of functions scanned, something like that). So that we can tell that the analysis is running even when the final report says:

0 Warnings, 0 Errors

Differentiate “need” from “use”

Currently there is no distinction between permissions needed directly and those needed indirectly. mono/mono#4529 (comment) suggests adding a “use” annotation to the body of a function that actually uses a permission, e.g., __WARD_USE__ (foo_locked). Ward should raise a warning when you need a permission without using it (directly or indirectly), in order to help prevent stale/redundant annotations.

Generate HTML report

We currently produce compiler-style output:

/path/file.c:line:column: (note|warning|error): Message

It would be nice to have a --mode=html flag that produces a formatted report, e.g., for viewing on CI.

Conflicts and failed restrictions beneath 'Choice' nodes aren't reported

Take a look at https://github.com/evincarofautumn/Ward/blob/minimal-annotations/src/Check/Permissions.hs#L655

      let conflicts = HashSet.filter conflicting $ mconcat sites
      unless (HashSet.null conflicts) $ do
        record True $ Error pos $ Text.concat $
          [ "conflicting information for permissions "
          , Text.pack $ show $ sort $ map presencePermission
            $ HashSet.toList conflicts
          , " in '"
          , name
          , "'"
          ]

this will find all conflicts in sites :: Vector [Site] and report on them. the problem is that sites comes from https://github.com/evincarofautumn/Ward/blob/minimal-annotations/src/Check/Permissions.hs#L587

      do
        sites <- liftIO $ fmap Vector.toList $ Vector.freeze $ nodeSites node
        reportCallSites restrictions (sites, nodeCalls node, name, pos)

And the nodeSites record field in Node only contains one element for each toplevel element of nodeCalls :: CallTree (Function). It does not have any sites for calls beneath conditionals.

Now I don't think this means we won't report those conflicts at all. Rather - I think we'll just report a conflict at the position of the if statement. This is... not great.

The same problem, but worse, will happen with restrictions https://github.com/evincarofautumn/Ward/blob/minimal-annotations/src/Check/Permissions.hs#L667

      for_ (zip [0 :: Int ..] sites) $ \ (index, s) -> do
        let
          position = case index of
            0 -> ["before first call"]
            _ ->
              [ "at "
              , Text.pack $ show $ callTreeIndex (index - 1) callees
              ]
        for_ restrictions $ \ restriction -> do
          unless (evalRestriction s restriction) $ do
            record True $ Error pos $ Text.concat $
              [ "restriction "
              , Text.pack $ show restriction
              , " violated in '"
              , name
              {-
              , "' with permissions '"
              , Text.pack $ show $ HashSet.toList s
              -}
              , "' "
              ]
              <> position

The issue here is worse, I think, because a restriction could be true at a Choice node, but false at a call in one of its branches.

Anyway, I think I can probably make this all go away by recomputing the site info under 'Choice' when reporting. (1. I think we can get the info in one pass no need to iterate again since the 'Call's will have the best info we have gathered. 2. This should be pretty cheap to do - I think the propagation algorithm is fairly inexpensive)

Something isn't right with static functions in a CallMap

Consider the following example file "foo.c":

int foo (int a, int b) __attribute__((ward(deny(coop_can_checkpoint))));
static int bar (int a, int b);

int 
foo (int a, int b)  {
	if (a < 0)
		return foo (b, a - b);
	else
		return bar (a, b);
}

static int
bar (int a, int b) {
	int x = foo (1, a);
	int y = foo (1, b);
	return foo (x, y);
}

I expect that this defines 2 functions foo and bar where bar is static and foo is not.

Here's the callgraph I get out, however:

(callmap
  (function "foo" (node (span (source "foo.c" 128 2 1) 1 (source "foo.c" 199 2 72))(name 19))
    (actions (deny coop_can_checkpoint))
    (calltree
      (choice
        (call "foo")
        (call "foo.c`bar"))))
  (function "bar" (node (span (source "foo.c" 202 4 1) 1 (source "foo.c" 231 4 30))(name 33))
    (actions )
    (calltree
    ))
  (function "foo.c`bar" (node (span (source "foo.c" 327 15 1) 1 (source "foo.c" 421 20 1))(name 116))
    (actions )
    (calltree
      (call "foo")
      (call "foo")
      (call "foo")))

Note that there are evidently 3 functions. It looks like the calls end up being to the right functions, but I'm still surprised to see "bar" defined at all.

Join points at return?

Consider

void foo () {
  f1 ();
  if (cond) {
    f2 ();
    return;
  }
  f3 ();
}

I haven't looked too closely, but does CallTree represent the join point between the return after f2 and the fallthrough after f3()? Does it need to? (the usual representation for functions is a graph - not tree - of single-entry-single-exit basic blocks)

Formally specify type system

It would be helpful to have a document (e.g. in ott) formally specifying the judgments that define the type system. Currently the only reference is prose

Is simplifyCallTree is wrong on Choice Nop t ?

simplifyCallTree (Choice a b) = case (simplifyCallTree a, simplifyCallTree b) of
  (a', Nop) -> a'
  (Nop, b') -> b'
  (a', b') -> Choice a' b'

This seems wrong to me. That's saying that fnA and fnB below are equivalent. Is that really what we want?

void f1 (void);
void f2 (void);

void fnA (int i) {
  if (i)
    f1 ();
  f2 ();
}

void fnB (int i) {
  f1 ();
  f2 ();
}

conditionally granting or revoking or needing or using a permissions shouldn't be the same as always having it, should it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.