int source();
void sink(...) {};

void arithAssignments(int source1, int clean1) {
  sink(clean1); // clean
  clean1 += source1;
  clean1 += 1;
  sink(clean1); // tainted

  clean1 = source1 = 1;
  sink(clean1); // clean
  source1 = clean1 = source();
  source1++;
  ++source1;
  source1 += 1;
  sink(source1); // tainted
  sink(++source1); // tainted
}

// --- globals ---

int increment(int x) {return x + 1;}
int zero(int x) {return 0;}

int global1 = 0;
int global2 = source();
int global3 = global2 + 1;
int global4 = increment(source());
int global5 = zero(source());
int global6, global7, global8, global9, global10;

void do_source()
{
	global6 = 0;
	global7 = source();
	global8 = global7 + 1;
	global9 = increment(source());
	global10 = zero(source());

	sink(global6);
	sink(global7); // tainted
	sink(global8); // tainted
	sink(global9); // tainted
	sink(global10);
}

void do_sink()
{
	sink(global1);
	sink(global2); // tainted [NOT DETECTED]
	sink(global3); // tainted [NOT DETECTED]
	sink(global4); // tainted [NOT DETECTED]
	sink(global5);
	sink(global6);
	sink(global7); // tainted [NOT DETECTED]
	sink(global8); // tainted [NOT DETECTED]
	sink(global9); // tainted [NOT DETECTED]
	sink(global10);
}

void global_test()
{
	do_source();
	do_sink();
}

// --- class fields ---

class MyClass {
public:
	MyClass() : a(0), b(source()) {
		c = source();
		d = 0;
	}

	void myMethod() {
		d = source();
	}

	int a, b, c, d;
};

void class_field_test() {
	MyClass mc1, mc2;

	mc1.myMethod();

	sink(mc1.a);
	sink(mc1.b); // tainted [NOT DETECTED with IR]
	sink(mc1.c); // tainted [NOT DETECTED with IR]
	sink(mc1.d); // tainted [NOT DETECTED with IR]
	sink(mc2.a);
	sink(mc2.b); // tainted [NOT DETECTED with IR]
	sink(mc2.c); // tainted [NOT DETECTED with IR]
	sink(mc2.d);
}

// --- arrays ---

void array_test(int i) {
	int arr1[10] = {0};
	int arr2[10] = {0};
	int arr3[10] = {0};

	arr1[5] = source();
	arr2[i] = source();
	arr3[5] = 0;

	sink(arr1[5]); // tainted
	sink(arr1[i]); // tainted [NOT DETECTED]
	sink(arr2[5]); // tainted [NOT DETECTED]
	sink(arr2[i]); // tainted [NOT DETECTED]
	sink(arr3[5]);
	sink(arr3[i]);
}

// --- pointers ---

void pointer_test() {
	int t1 = source();
	int t2 = 1;
	int t3 = 1;
	int *p1 = &t1;
	int *p2 = &t2;
	int *p3 = &t3;

	*p2 = source();

	sink(*p1); // tainted
	sink(*p2); // tainted [NOT DETECTED]
	sink(*p3);

	p3 = &t1;
	sink(*p3); // tainted

	*p3 = 0;
	sink(*p3); // [FALSE POSITIVE]
}

// --- return values ---

int select(int i, int a, int b) {
	if (i == 1) {
		return a;
	} else {
		return b;
	}
}

void fn_test(int i) {
	sink(select(i, 1, source())); // tainted
}

// --- strings ---

char *strcpy(char *destination, const char *source);
char *strcat(char *destination, const char *source);

namespace strings
{
	char *source(); // char* source

	void strings_test1() {
		char *tainted = source();
		char buffer[1024] = {0};

		sink(source()); // tainted
		sink(tainted); // tainted

		strcpy(buffer, "Hello, ");
		sink(buffer);
		strcat(buffer, tainted);
		sink(buffer); // tainted
	}
}

// --- pass by reference ---

namespace refs {
	void callee(int *p) {
		sink(*p); // tainted
	}

	void caller() {
		int x = source();
		callee(&x);
	}
}

void *memcpy(void *dest, void *src, int len);

void test_memcpy(int *source) {
	int x;
	memcpy(&x, source, sizeof(int));
	sink(x);
}

// --- swap ---

namespace std {
	template<class T> constexpr void swap(T& a, T& b);
}

void test_swap() {
	int x, y;

	x = source();
	y = 0;

	sink(x); // tainted
	sink(y); // clean

	std::swap(x, y);

	sink(x); // clean [FALSE POSITIVE]
	sink(y); // tainted
}

// --- lambdas ---

void test_lambdas()
{
	int t = source();
	int u = 0;
	int v = 0;
	int w = 0;

	auto a = [t, u]() -> int {
		sink(t); // tainted
		sink(u); // clean
		return t;
	};
	sink(a()); // tainted

	auto b = [&] {
		sink(t); // tainted
		sink(u); // clean
		v = source(); // (v is reference captured)
	};
	b();
	sink(v); // tainted [NOT DETECTED]

	auto c = [=] {
		sink(t); // tainted
		sink(u); // clean
	};
	c();

	auto d = [](int a, int b) {
		sink(a); // tainted
		sink(b); // clean
	};
	d(t, u);

	auto e = [](int &a, int &b, int &c) {
		sink(a); // tainted
		sink(b); // clean
		c = source();
	};
	e(t, u, w);
	sink(w); // tainted
}

// --- taint through return value ---

int id(int x)
{
	return x;
}

void test_return()
{
	int x, y, z, t;

	t = source();
	x = 0;
	y = 0;
	z = 0;

	sink(t); // tainted
	sink(x);
	sink(y);
	sink(z);

	x = id(t);
	y = id(id(t));
	z = id(z);

	sink(t); // tainted
	sink(x); // tainted
	sink(y); // tainted
	sink(z);
}

// --- taint through parameters ---

void myAssign1(int &a, int &b)
{
	a = b;
}

void myAssign2(int &a, int b)
{
	a = b;
}

void myAssign3(int *a, int b)
{
	*a = b;
}

void myAssign4(int *a, int b)
{
	int c;

	c = b + 1;
	*a = c;
}

void myNotAssign(int &a, int &b)
{
	a = a + 1;
	b = b + 1;
}

void test_outparams()
{
	int t, a, b, c, d, e;

	t = source();
	a = 0;
	b = 0;
	c = 0;
	d = 0;
	e = 0;

	sink(t); // tainted
	sink(a);
	sink(b);
	sink(c);
	sink(d);
	sink(e);

	myAssign1(a, t);
	myAssign2(b, t);
	myAssign3(&c, t);
	myAssign4(&d, t);
	myNotAssign(e, t);

	sink(t); // tainted
	sink(a); // tainted
	sink(b); // tainted
	sink(c); // tainted [NOT DETECTED]
	sink(d); // tainted [NOT DETECTED]
	sink(e);
}

// --- strdup ---

typedef unsigned long size_t;
char *strdup(const char *s1);
char *strndup(const char *s1, size_t n);
wchar_t* wcsdup(const wchar_t* s1);

void test_strdup(char *source)
{
	char *a, *b, *c;

	a = strdup(source);
	b = strdup("hello, world");
	c = strndup(source, 100);
	sink(a); // tainted
	sink(b);
	sink(c); // tainted
}

void test_strndup(int source)
{
	char *a;

	a = strndup("hello, world", source);
	sink(a); // tainted
}

void test_wcsdup(wchar_t *source)
{
	wchar_t *a, *b;

	a = wcsdup(source);
	b = wcsdup(L"hello, world");
	sink(a); // tainted
	sink(b);
}

// --- qualifiers ---

class MyClass2 {
public:
	MyClass2(int value);
	void setMember(int value);
	int getMember();

	int member;
};

class MyClass3 {
public:
	MyClass3(const char *string);
	void setString(const char *string);
	const char *getString();

	const char *buffer;
};

void test_qualifiers()
{
	MyClass2 a(0), b(0), *c;
	MyClass3 d("");

	sink(a);
	sink(a.getMember());
	a.setMember(source());
	sink(a); // tainted
	sink(a.getMember()); // tainted

	sink(b);
	sink(b.getMember());
	b.member = source();
	sink(b); // tainted
	sink(b.member); // tainted
	sink(b.getMember());

	c = new MyClass2(0);

	sink(c);
	sink(c->getMember());
	c->setMember(source());
	sink(c); // tainted (deref)
	sink(c->getMember()); // tainted

	delete c;

	sink(d);
	sink(d.getString());
	d.setString(strings::source());
	sink(d); // tainted
	sink(d.getString()); // tainted
}
