int source();
void sink(...); void indirect_sink(...);

void intraprocedural_with_local_flow() {
  int t2;
  int t1 = source();
  sink(t1); // $ ast,ir
  t2 = t1;
  sink(t1); // $ ast,ir
  sink(t2); // $ ast,ir
  if (t1) {
    t2 = 0;
    sink(t2); // clean
  }
  sink(t2); // $ ast,ir

  t1 = 0;
  while (false) {
    t1 = t2;
  }
  sink(t1); // clean (because loop condition is `false`)

  for (int i = 0; i < t1; i++) {
    t1 = t2;
  }
  sink(t1); // $ ast,ir
}

static void callee(int t, int c) {
  sink(t); // $ ast,ir // (from first call in caller() function)
  sink(c); // $ ast,ir // (from second call in caller() function)
}

void caller() {
  callee(source(), 0);
  callee(1, source());
}

void branching(bool b) {
  {
    int t1 = 1, t2 = 2;
    int t = source();
    sink(t ? t1 : t2); // clean

    t = b ? t1 : t2;
    sink(t); // clean
  }

  {
    int t1 = source(), t = 0;
    if (b) {
      t = t1;
    } else {
      t = 1;
    }


    sink(t); // $ ast,ir
  }
}

namespace std {
  template<class T> T&& move(T& t) noexcept; // simplified signature
}

void identityOperations(int* source1) { // $ ast-def=source1 ir-def=*source1
  const int *x1 = std::move(source1);
  int* x2 = const_cast<int*>(x1);
  int* x3 = (x2);
  const int* x4 = (const int *)x3;
  sink(x4);  // $ ast,ir
}

void trackUninitialized() {
  int u1;
  sink(u1); // $ ast,ir
  u1 = 2;
  sink(u1); // clean

  int i1 = 1;
  sink(i1); // clean

  int u2;
  sink(i1 ? u2 : 1); // $ ast,ir
  i1 = u2;
  sink(i1); // $ ast,ir
}

void local_references(int &source1, int clean1) { // $ ast-def=source1 ir-def=*source1
  sink(source1); // $ ast,ir
  source1 = clean1;
  sink(source1); // clean

  // The next two test cases show that the analysis does not understand the "&"
  // on the type at all. It does not understand that the initialization creates
  // an alias, so it does not understand when two variables change on one
  // assignment. This leads to both overapproximation and underapproximation of
  // flows.
  {
    int t = source();
    int &ref = t;
    t = clean1;
    sink(ref); // $ SPURIOUS: ast,ir
  }

  {
    int t = clean1;
    int &ref = t;
    t = source();
    sink(ref); // $ MISSING: ast,ir
  }
}

int alwaysAssignSource(int *out) { // $ ast-def=out ir-def=*out 
  *out = source();
  return 0;
}

int alwaysAssign0(int *out) { // $ ast-def=out ir-def=*out
  *out = 0;
  return 0;
}

int alwaysAssignInput(int *out, int in) { // $ ast-def=out ir-def=*out
  *out = in;
  return 0;
}
// TODO: call the above

// Tests for flow through functions that return a parameter, or a value obtained from a parameter
// These also test some cases for tracking non-parameter sources returned to a function call

int returnParameter(int p) {
  return p; // considered clean unless the caller passes taint into p, which the analysis will handle separately
}

void callReturnParameter() {
  int x = returnParameter(source());
  int y = x;
  sink(y); // $ ast,ir
}

int returnSourceParameter(int s) {
  sink(s); // $ ast,ir // from line 151
  return s; // considered clean unless the caller passes taint into the parameter source
}

void callReturnSourceParameter() {
  int x = returnSourceParameter(0);
  sink(x); // clean
  int y = returnSourceParameter(source());
  sink(y); // $ ast,ir
}

int returnSourceParameter2(int s) {
  int x = s;
  sink(x); // $ ast,ir // from line 164
  return x; // considered clean unless the caller passes taint into the parameter source
}

void callReturnSourceParameter2() {
  int x = returnSourceParameter2(0);
  sink(x); // clean
  int y = returnSourceParameter2(source());
  sink(y); // $ ast,ir
}

// Tests for non-parameter sources returned to a function call

int returnSource() {
  int x = source(); // taints the return value
  return x;
}

void callReturnSource() {
  int x = returnSource();
  int y = x;
  sink(y); // $ ast,ir
}

// TESTS WITH BARRIERS: none of these should have results

void barrier(...);

class BarrierTests {
  // Tests for flow through functions that return a parameter, or a value obtained from a parameter
  // These also test some cases for tracking non-parameter sources returned to a function call

  int returnParameter(int p) {
    return p; // considered clean unless the caller passes taint into p, which the analysis will handle separately
  }

  void callReturnParameter() {
    int x = returnParameter(source());
    int barrier = x;
    int y = barrier;
    sink(y); // no longer tainted
  }

  int returnSourceParameter(int source) {
    int barrier = source;
    sink(barrier); // no longer tainted
    return barrier; // clean
  }

  void callReturnSourceParameter() {
    int x = returnSourceParameter(0);
    sink(x); // clean
    int y = returnSourceParameter(source());
    sink(y); // no longer tainted
  }

  int returnSourceParameter2(int source) {
    int barrier = source;
    int x = barrier;
    sink(x); // no longer tainted
    return x; // clean
  }

  void callReturnSourceParameter2() {
    int x = returnSourceParameter2(0);
    sink(x); // clean
    int y = returnSourceParameter2(source());
    sink(y); // no longer tainted
  }

  // Tests for non-parameter sources returned to a function call

  int returnSource() {
    int x = source();
    int barrier = x;
    return barrier;
  }

  void callReturnSource() {
    int x = returnSource();
    int y = x;
    sink(y); // no longer tainted
  }
};
// Tests for interprocedural flow (as above) involving nested function calls
namespace NestedTests {
  class FlowIntoParameter {
    void level0() {
      level1(source());
      safelevel1(source());
    }

    void level1(int x) {
      int y = x;
      level2(y);
    }

    void safelevel1(int x) {
      int barrier = x;
      level2(barrier);
      }

    void level2(int x) {
      sink(x); // $ ast,ir // tainted from call to level1() but not from call to safelevel1()
    }
  };
  class FlowThroughFunctionReturn {
    void level0() {
      int x = level1(source());
      sink(x); // $ ast,ir
      x = safelevel1(source());
      sink(x); // no longer tainted
    }

    int level1(int x) {
      int y = x;
      return level2(y);
    }

    int safelevel1(int x) {
      int barrier = x;
      return level2(barrier);
    }

    int level2(int x) {
      int y = x;
      return y;
    }
  };
  class FlowOutOfFunction {
      void level0() {
        int x = level1();
        sink(x); // $ ast,ir
        x = safelevel1();
        sink(x); // no longer tainted
      }

      int level1() {
        int y = level2();
        return y;
      }

      int safelevel1() {
        int barrier = level2();
        return barrier;
      }

      int level2() {
        int y = source();
        return y;
      }

      // the next three functions describe a case that should not be picked up
      // by the flow-out-of-function case, but only by the flow-through-function case
      // a poor heuristic to prevent that will lead to the clean sink being flagged

      void f() {
	g(source());
      }
      void g(int p) {
	int x = h(p);
	sink(x); // $ ast,ir  // tainted from f
	int y = h(0);
	sink(y); // clean
	f();
      }
      int h(int p) {
	return p;
      }
    };
}

namespace FlowThroughGlobals {
  int globalVar;

  void taintGlobal() {
    globalVar = source();
  }

  void f() {
    sink(globalVar); // $ ir=333:17 ir=347:17 // tainted or clean? Not sure.
    taintGlobal();
    sink(globalVar); // $ ir=333:17 ir=347:17 MISSING: ast
  }

  void calledAfterTaint() {
    sink(globalVar); // $ ir=333:17 ir=347:17 MISSING: ast
  }

  void taintAndCall() {
    globalVar = source();
    calledAfterTaint();
    sink(globalVar); // $ ast ir
  }
}

// This is similar to FlowThroughGlobals, only with a non-static data member
// instead of a global.
class FlowThroughFields {
  int field = 0;

  void taintField() {
    field = source();
  }

  void f() {
    sink(field); // tainted or clean? Not sure.
    taintField();
    sink(field); // $ ast,ir
  }

  void calledAfterTaint() {
    sink(field); // $ ast,ir
  }

  void taintAndCall() {
    field = source();
    calledAfterTaint();
    sink(field); // $ ast,ir
  }
};

typedef unsigned long size_t;
void *memcpy(void *dest, const void *src, size_t count);

void flowThroughMemcpy_ssa_with_local_flow(int source1) {
  int tmp = 0;
  memcpy(&tmp, &source1, sizeof tmp);
  sink(tmp); // $ ast,ir
}

void flowThroughMemcpy_blockvar_with_local_flow(int source1, int b) {
  int tmp = 0;
  int *capture = &tmp;
  memcpy(&tmp, &source1, sizeof tmp);
  sink(tmp); // $ ast,ir
  if (b) {
    sink(tmp); // $ ast,ir
  }
}

void cleanedByMemcpy_ssa(int clean1) { // currently modeled with BlockVar, not SSA
  int tmp;
  memcpy(&tmp, &clean1, sizeof tmp);
  sink(tmp); // $ SPURIOUS: ast
}

void cleanedByMemcpy_blockvar(int clean1) {
  int tmp;
  int *capture = &tmp;
  memcpy(&tmp, &clean1, sizeof tmp);
  sink(tmp); // $ SPURIOUS: ast
}

void intRefSource(int &ref_source);
void intPointerSource(int *ref_source);
void intArraySource(int ref_source[], size_t len);

void intRefSourceCaller() {
  int local;
  intRefSource(local);
  sink(local); // $ ast,ir=416:7 ast,ir=417:16
}

void intPointerSourceCaller() {
  int local;
  intPointerSource(&local);
  sink(local); // $ ast,ir=422:7 ast,ir=423:20
}






void intPointerSourceCaller2() {
  int local[1];
  intPointerSource(local);
  sink(local); // $ ast=434:20 ir SPURIOUS: ast=433:7
  sink(*local); // $ ast=434:20 ir SPURIOUS: ast=433:7
}

void intArraySourceCaller() {
  int local;
  intArraySource(&local, 1);
  sink(local); // $ ast,ir=440:7 ast,ir=441:18
}

// The IR results for this test _are_ equivalent to the AST ones.
// See the comment on `intPointerSourceCaller2` for an explanation.
void intArraySourceCaller2() {
  int local[2];
  intArraySource(local, 2);
  sink(local); // $ ast=449:18 ir SPURIOUS: ast=448:7
  sink(*local); // $ ast=449:18 ir SPURIOUS: ast=448:7
}

///////////////////////////////////////////////////////////////////////////////

void throughStmtExpr(int source1, int clean1) {
  sink( ({ source1; }) ); // $ ast,ir
  sink( ({ clean1; }) ); // clean

  int local = ({
    int tmp;
    if (clean1)
      tmp = source1;
    else
      tmp = clean1;
    tmp;
  });
  sink(local); // $ ast,ir
}

void intOutparamSource(int *p) { // $ ast-def=p ir-def=*p
  *p = source();
}

void viaOutparam() {
  int x = 0;
  intOutparamSource(&x);
  sink(x); // $ ast,ir
}

void writes_to_content(void*);

struct MyStruct {
  int* content; 
};

void local_field_flow_def_by_ref_steps_with_local_flow(MyStruct * s) { // $ ast-def=s ir-def=*s
  writes_to_content(s->content);
  int* p_content = s->content;
  sink(*p_content);
}

bool unknown();

void regression_with_phi_flow(int clean1) {
  int x = 0;
  while (unknown()) {
    x = clean1;
    if (unknown()) { }
    sink(x); // clean
    x = source();
  }
}

int intOutparamSourceMissingReturn(int *p) { // $ ast-def=p ir-def=*p
  *p = source();
  // return deliberately omitted to test IR dataflow behavior
}

void viaOutparamMissingReturn() {
  int x = 0;
  intOutparamSourceMissingReturn(&x);
  sink(x); // $ ast,ir
}

void uncertain_definition() {
  int stackArray[2];
  int clean = 0;
  stackArray[0] = source();
  stackArray[1] = clean;
  sink(stackArray[0]); // $ ast=519:19 ir SPURIOUS: ast=517:7
}

void set_through_const_pointer(int x, const int **e) // $ ast-def=e ir-def=*e ir-def=**e
{
  *e = &x;
}

void test_set_through_const_pointer(int *e) // $ ast-def=e ir-def=*e
{
  set_through_const_pointer(source(), &e);
  sink(*e); // $ ir MISSING: ast
}

void sink_then_source_1(int* p) { // $ ast-def=p ir-def=*p
    sink(*p); // $ ir // Flow from the unitialized x to the dereference.
    *p = source();
}

void sink_then_source_2(int* p, int y) { // $ ast-def=p ir-def=*p
    sink(y); // $ SPURIOUS: ast ir
    *p = source();
}

void test_sink_then_source() {
  {
    int x;
    sink_then_source_1(&x);
  }
  {
    int y;
    sink_then_source_2(&y, y);
  }
}

int* indirect_source();

namespace IndirectFlowThroughGlobals {
  int* globalInt;

  void taintGlobal() {
    globalInt = indirect_source();
  }

  void f() {
    sink(*globalInt); // $ ir=562:17 ir=576:17 MISSING: ast=562:17 ast=576:17
    taintGlobal();
    sink(*globalInt); // $ ir=562:17 ir=576:17 MISSING: ast=562:17 ast=576:17
  }

  void calledAfterTaint() {
    sink(*globalInt); // $ ir=562:17 ir=576:17 MISSING: ast=562:17 ast=576:17
  }

  void taintAndCall() {
    globalInt = indirect_source();
    calledAfterTaint();
    sink(*globalInt); // $ ir MISSING: ast=562:17 ast=576:17
  }
}

void write_to_param(int* x) { // $ ast-def=x ir-def=*x
  int s = source();
  x = &s;
}

void test_write_to_param() {
  int x = 0;
  write_to_param(&x);
  sink(x); // $ SPURIOUS: ast,ir
}

void test_indirect_flow_to_array() {
  int* p = indirect_source();
  int* xs[2];
  xs[0] = p;
  sink(*xs[0]); // $ ir MISSING: ast // the IR source is the indirection of `indirect_source()`.
}

void test_def_by_ref_followed_by_uncertain_write_array(int* p) { // $ ast-def=p ir-def=*p
  intPointerSource(p);
  p[10] = 0;
  sink(*p); // $ ir MISSING: ast
}

void test_def_by_ref_followed_by_uncertain_write_pointer(int* p) { // $ ast-def=p ir-def=*p
  intPointerSource(p);
  *p = 0;
  sink(*p); // $ ir MISSING: ast
}

void test_flow_through_void_double_pointer(int *p) // $ ast-def=p ir-def=*p
{
  intPointerSource(p);
  void* q = (void*)&p;
  sink(**(int**)q); // $ ir MISSING: ast
}

void use(int *);

void test_def_via_phi_read(bool b)
{
  static int buffer[10]; // This is missing an initialisation in IR dataflow
  if (b)
  {
    use(buffer);
  }
  intPointerSource(buffer);
  indirect_sink(buffer); // $ ast,ir
}

void test_static_local_1() {
  static int x = source();
  sink(x); // $ ast,ir
}

void test_static_local_2() {
  static int x = source();
  x = 0;
  sink(x); // clean
}

void test_static_local_3() {
  static int x = 0;
  sink(x); // $ ir MISSING: ast
  x = source();
}

void test_static_local_4() {
  static int x = 0;
  sink(x); // clean
  x = source();
  x = 0;
}

void test_static_local_5() {
  static int x = 0;
  sink(x); // $ ir MISSING: ast
  x = 0;
  x = source();
}

void test_static_local_6() {
  static int s = source();
  static int* ptr_to_s = &s;
  sink(*ptr_to_s); // $ ir MISSING: ast
}

void test_static_local_7() {
  static int s = source();
  s = 0;
  static int* ptr_to_s = &s;
  sink(*ptr_to_s); // clean
}

void test_static_local_8() {
  static int s;
  static int* ptr_to_s = &s;
  sink(*ptr_to_s); // $ ir MISSING: ast

  s = source();
}

void test_static_local_9() {
  static int s;
  static int* ptr_to_s = &s;
  sink(*ptr_to_s); // clean

  s = source();
  s = 0;
}

void increment_buf(int** buf) { // $ ast-def=buf ir-def=*buf ir-def=**buf
  *buf += 10;
  sink(buf); // $ SPURIOUS: ast
}

void call_increment_buf(int** buf) { // $ ast-def=buf ir-def=*buf ir-def=**buf
  increment_buf(buf);
}

void test_conflation_regression(int* source) { // $ ast-def=source ir-def=*source
  int* buf = source;
  call_increment_buf(&buf);
}

void write_to_star_star_p(unsigned char **p) // $ ast-def=p ir-def=**p ir-def=*p
{
  **p = 0;
}

void write_to_star_buf(unsigned char *buf) // $ ast-def=buf ir-def=*buf
{
  unsigned char *c = buf;
  write_to_star_star_p(&c);
}

void test_write_to_star_buf(unsigned char *source) // $ ast-def=source ir-def=*source
{
  write_to_star_buf(source);
  sink(*source); // clean
}

void does_not_write_source_to_dereference(int *p) // $ ast-def=p ir-def=*p
{
  int x = source();
  p = &x;
  *p = 42;
}

void test_does_not_write_source_to_dereference()
{
  int x;
  does_not_write_source_to_dereference(&x);
  sink(x); // $ ast=733:7 ir SPURIOUS: ast=726:11
}

void sometimes_calls_sink_eq(int x, int n) {
  if(n == 0) {
    sink(x); // $ ast,ir=751:27 ast,ir=755:32 SPURIOUS: ast=749:27 ast,ir=753:32 // IR spurious results because we only have call contexts of depth 1
  }
}

void call_sometimes_calls_sink_eq(int x, int n) {
  sometimes_calls_sink_eq(x, n);
}

void test_sometimes_calls_sink_eq_1() {
  sometimes_calls_sink_eq(source(), 1);
  sometimes_calls_sink_eq(0, 0);
  sometimes_calls_sink_eq(source(), 0);

  call_sometimes_calls_sink_eq(source(), 1);
  call_sometimes_calls_sink_eq(0, 0);
  call_sometimes_calls_sink_eq(source(), 0);
}

void sometimes_calls_sink_lt(int x, int n) {
  if(n < 10) {
    sink(x); // $ ast,ir=771:27 ast,ir=775:32 SPURIOUS: ast=769:27 ast,ir=773:32 // IR spurious results because we only have call contexts of depth 1
  }
}

void call_sometimes_calls_sink_lt(int x, int n) {
  sometimes_calls_sink_lt(x, n);
}

void test_sometimes_calls_sink_lt() {
  sometimes_calls_sink_lt(source(), 10);
  sometimes_calls_sink_lt(0, 0);
  sometimes_calls_sink_lt(source(), 2);

  call_sometimes_calls_sink_lt(source(), 10);
  call_sometimes_calls_sink_lt(0, 0);
  call_sometimes_calls_sink_lt(source(), 2);

}

void sometimes_calls_sink_switch(int x, int n) {
  switch(n) {
    case 0:
      sink(x); // $ ast,ir=790:31 SPURIOUS: ast,ir=788:31 // IR spurious results because IRGuard doesn't understand switch statements.
      break;
  }
}

void test_sometimes_calls_sink_switch() {
  sometimes_calls_sink_switch(source(), 1);
  sometimes_calls_sink_switch(0, 0);
  sometimes_calls_sink_switch(source(), 0);
}

void intPointerSource(int *ref_source, const int* another_arg);

void test() {
  MyStruct a;
  intPointerSource(a.content, a.content);
  indirect_sink(a.content); // $ ast ir
}

namespace MoreGlobalTests {
  int **global_indirect1;
  int **global_indirect2;
  int **global_direct;

  void set_indirect1()
  {
    *global_indirect1 = indirect_source();
  }

  void read_indirect1() {
    sink(global_indirect1); // clean
    indirect_sink(*global_indirect1); // $ ir MISSING: ast
  }

  void set_indirect2()
  {
    **global_indirect2 = source();
  }

  void read_indirect2() {
    sink(global_indirect2); // clean
    sink(**global_indirect2); // $ ir MISSING: ast
  }

  // overload source with a boolean parameter so
  // that we can define a variant that return an int**.
  int** source(bool);

  void set_direct()
  {
    global_direct = source(true);
  }

  void read_direct() {
    sink(global_direct); // $ ir MISSING: ast
    indirect_sink(global_direct); // clean
  }
}

void test_references() {
  int x = source();
  int &y = x;
  sink(y); // $ ast,ir

  int* px = indirect_source();
  int*& rpx = px;
  indirect_sink((int*)rpx); // $ ast,ir
}

namespace GlobalArrays {
  void test1() {
    static const int static_local_array_dynamic[] = { ::source() };
    sink(*static_local_array_dynamic); // $ ir MISSING: ast
  }

  const int* source(bool);

  void test2() {
    static const int* static_local_pointer_dynamic = source(true);
    sink(static_local_pointer_dynamic); // $ ast,ir
  }

  static const int global_array_dynamic[] = { ::source() };

  void test3() {
    sink(*global_array_dynamic); // $ MISSING: ir,ast // Missing in IR because no 'IRFunction' for global_array is generated because the type of global_array_dynamic is "deeply const".
  }

  const int* source(bool);

  static const int* global_pointer_dynamic = source(true);

  void test4() {
    sink(global_pointer_dynamic); // $ ir MISSING: ast
  }

  void test5() {
    static const char static_local_array_static[] = "source";
    static const char static_local_array_static_indirect_1[] = "indirect_source(1)";
    static const char static_local_array_static_indirect_2[] = "indirect_source(2)";
    sink(static_local_array_static); // clean
    sink(static_local_array_static_indirect_1); // $ ir MISSING: ast
    indirect_sink(static_local_array_static_indirect_1); // clean
    sink(static_local_array_static_indirect_2); // clean
    indirect_sink(static_local_array_static_indirect_2); // $ ir MISSING: ast
  }

  void test6() {
    static const char* static_local_pointer_static = "source";
    static const char* static_local_pointer_static_indirect_1 = "indirect_source(1)";
    static const char* static_local_pointer_static_indirect_2 = "indirect_source(2)";
    sink(static_local_pointer_static); // $ ir MISSING: ast
    sink(static_local_pointer_static_indirect_1); // clean
    indirect_sink(static_local_pointer_static_indirect_1); // $ ir MISSING: ast
    sink(static_local_pointer_static_indirect_2); // clean: static_local_pointer_static_indirect_2 does not have 2 indirections
    indirect_sink(static_local_pointer_static_indirect_2); // clean: static_local_pointer_static_indirect_2 does not have 2 indirections
  }

  static const char global_array_static[] = "source";
  static const char global_array_static_indirect_1[] = "indirect_source(1)";
  static const char global_array_static_indirect_2[] = "indirect_source(2)";

  void test7() {
    sink(global_array_static); // clean
    sink(*global_array_static); // clean
    sink(global_array_static_indirect_1); // $ ir MISSING: ast
    sink(*global_array_static_indirect_1); // clean
    indirect_sink(global_array_static); // clean
    indirect_sink(global_array_static_indirect_1); // clean
    indirect_sink(global_array_static_indirect_2); // $ ir MISSING: ast
  }

  static const char* global_pointer_static = "source";
  static const char* global_pointer_static_indirect_1 = "indirect_source(1)";
  static const char* global_pointer_static_indirect_2 = "indirect_source(2)";

  void test8() {
    sink(global_pointer_static); // $ ir MISSING: ast
    sink(global_pointer_static_indirect_1); // clean
    indirect_sink(global_pointer_static_indirect_1); // $ ir MISSING: ast
    sink(global_pointer_static_indirect_2); // clean: global_pointer_static_indirect_2 does not have 2 indirections
    indirect_sink(global_pointer_static_indirect_2); // clean: global_pointer_static_indirect_2 does not have 2 indirections
  }
}

namespace global_variable_conflation_test {
  int* global_pointer;

  void def() {
    global_pointer = nullptr;
    *global_pointer = source();
  }

  void use() {
    sink(global_pointer); // clean
    sink(*global_pointer); // $ ir MISSING: ast
  }
}

char* gettext(const char*);
char* dgettext(const char*, const char*);
char* ngettext(const char*, const char*, unsigned long int);
char* dngettext (const char*, const char *, const char *, unsigned long int);

namespace test_gettext {
  char* source();
  char* indirect_source();

  void test_gettext() {
    char* data = source();
    char* translated = gettext(data);
    sink(translated); // clean 
    indirect_sink(translated); // clean
  }

  void indirect_test_dgettext() {
    char* data = indirect_source();
    char* translated = gettext(data);
    sink(translated); // clean
    indirect_sink(translated); // $ ir MISSING: ast
  }

  void test_dgettext() {
    char* data = source();
    char* domain = source(); // Should not trace from this source
    char* translated = dgettext(domain, data);
    sink(translated); // clean 
    indirect_sink(translated); // clean
  }

  void indirect_test_gettext() {
    char* data = indirect_source();
    char* domain = indirect_source(); // Should not trace from this source
    char* translated = dgettext(domain, data);
    sink(translated); // clean
    indirect_sink(translated); // $ ir MISSING: ast
  }

  void test_ngettext() {
    char* data = source();
    char* np = nullptr; // Don't coun't as a source
    
    char* translated = ngettext(data, np, 0);
    sink(translated); // clean 
    indirect_sink(translated); // clean

    translated = ngettext(np, data, 0);
    sink(translated); // clean 
    indirect_sink(translated); // clean
  }

  void indirect_test_ngettext() {
    char* data = indirect_source();
    char* np = nullptr; // Don't coun't as a source
    
    char* translated = ngettext(data, np, 0);
    sink(translated); // clean 
    indirect_sink(translated); // $ ir MISSING: ast

    translated = ngettext(np, data, 0);
    sink(translated); // clean 
    indirect_sink(translated); // $ ir MISSING: ast
  }

  void test_dngettext() {
    char* data = source();
    char* np = nullptr; // Don't coun't as a source
    char* domain = source(); // Should not trace from this source
    
    char* translated = dngettext(domain, data, np, 0);
    sink(translated); // clean 
    indirect_sink(translated); // clean

    translated = dngettext(domain, np, data, 0);
    sink(translated); // clean 
    indirect_sink(translated); // clean
  }

  void indirect_test_dngettext() {
    char* data = indirect_source();
    char* np = nullptr; // Don't coun't as a source
    char* domain = indirect_source(); // Should not trace from this source
    
    char* translated = dngettext(domain, data, np, 0);
    sink(translated); // clean 
    indirect_sink(translated); // $ ir MISSING: ast

    translated = dngettext(domain, np, data, 0);
    sink(translated); // clean 
    indirect_sink(translated); // $ ir MISSING: ast
  }

  void indirect_test_gettext_no_flow_from_domain() {
    char* domain = source(); // Should not trace from this source
    char* translated = dgettext(domain, nullptr);
    sink(translated); // clean 
    indirect_sink(translated); // clean
  }
}

void* memset(void*, int, size_t);

void memset_test(char* buf) { // $ ast-def=buf ir-def=*buf
	memset(buf, source(), 10);
	sink(*buf); // $ ir MISSING: ast
}

void flow_out_of_address_with_local_flow() {
  MyStruct a;
  a.content = nullptr;
  sink(&a); // $ SPURIOUS: ast
}

static void static_func_that_reassigns_pointer_before_sink(char** pp) { // $ ast-def=pp ir-def=*pp ir-def=**pp
    *pp = "";
    indirect_sink(*pp); // clean
}

void test_static_func_that_reassigns_pointer_before_sink() {
    char* p = (char*)indirect_source();
    static_func_that_reassigns_pointer_before_sink(&p);
}

void single_object_in_both_cases(bool b, int x, int y) {
  int* p;
  if(b) {
    p = &x;
  } else {
    p = &y;
  }
  *p = source();
  *p = 0;
  sink(*p); // clean
}
