diff --git a/exemplos_codigos/imagens/c/2.dot b/exemplos_codigos/imagens/c/2.dot
new file mode 100644
index 0000000000000000000000000000000000000000..d53a91ec34a31586c5828d75b54c74cb39a1be40
--- /dev/null
+++ b/exemplos_codigos/imagens/c/2.dot
@@ -0,0 +1,141 @@
+digraph "../exemplos/2.c.015t.cfg" {
+overlap=false;
+subgraph "cluster_main" {
+	style="dashed";
+	color="black";
+	label="main ()";
+	subgraph cluster_0_2 {
+	style="filled";
+	color="darkgreen";
+	fillcolor="grey88";
+	label="loop 2";
+	labeljust=l;
+	penwidth=2;
+	fn_0_basic_block_9 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 9\>:\l\
+|_9\ =\ array[middle];\l\
+|search.3_10\ =\ search;\l\
+|if\ (_9\ !=\ search.3_10)\l\
+\ \ goto\ \<bb\ 10\>;\ [INV]\l\
+else\l\
+\ \ goto\ \<bb\ 11\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_10 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 10\>:\l\
+|if\ (first\ \<=\ last)\l\
+\ \ goto\ \<bb\ 6\>;\ [INV]\l\
+else\l\
+\ \ goto\ \<bb\ 11\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_6 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 6\>:\l\
+|_5\ =\ array[middle];\l\
+|search.2_6\ =\ search;\l\
+|if\ (_5\ \<\ search.2_6)\l\
+\ \ goto\ \<bb\ 7\>;\ [INV]\l\
+else\l\
+\ \ goto\ \<bb\ 8\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_7 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 7\>:\l\
+|first\ =\ middle\ +\ 1;\l\
+|_7\ =\ first\ +\ last;\l\
+|middle\ =\ _7\ /\ 2;\l\
+goto\ \<bb\ 9\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_8 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 8\>:\l\
+|last\ =\ middle\ +\ -1;\l\
+|_8\ =\ first\ +\ last;\l\
+|middle\ =\ _8\ /\ 2;\l\
+}"];
+
+	}
+	subgraph cluster_0_1 {
+	style="filled";
+	color="darkgreen";
+	fillcolor="grey88";
+	label="loop 1";
+	labeljust=l;
+	penwidth=2;
+	fn_0_basic_block_4 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 4\>:\l\
+|n.0_2\ =\ n;\l\
+|if\ (c\ \<\ n.0_2)\l\
+\ \ goto\ \<bb\ 3\>;\ [INV]\l\
+else\l\
+\ \ goto\ \<bb\ 5\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_3 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 3\>:\l\
+|_1\ =\ &array[c];\l\
+|scanf\ (\"%d\",\ _1);\l\
+|c\ =\ c\ +\ 1;\l\
+}"];
+
+	}
+	fn_0_basic_block_0 [shape=Mdiamond,style=filled,fillcolor=white,label="ENTRY"];
+
+	fn_0_basic_block_1 [shape=Mdiamond,style=filled,fillcolor=white,label="EXIT"];
+
+	fn_0_basic_block_2 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 2\>:\l\
+|scanf\ (\"%d\",\ &n);\l\
+|c\ =\ 0;\l\
+goto\ \<bb\ 4\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_5 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 5\>:\l\
+|scanf\ (\"%d\",\ &search);\l\
+|first\ =\ 0;\l\
+|n.1_3\ =\ n;\l\
+|last\ =\ n.1_3\ +\ -1;\l\
+|_4\ =\ first\ +\ last;\l\
+|middle\ =\ _4\ /\ 2;\l\
+goto\ \<bb\ 9\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_11 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 11\>:\l\
+|_11\ =\ array[middle];\l\
+|search.4_12\ =\ search;\l\
+|if\ (_11\ ==\ search.4_12)\l\
+\ \ goto\ \<bb\ 12\>;\ [INV]\l\
+else\l\
+\ \ goto\ \<bb\ 13\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_12 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 12\>:\l\
+|__builtin_puts\ (&\"Binary\ search\ successful!\"[0]);\l\
+goto\ \<bb\ 14\>;\ [INV]\l\
+}"];
+
+	fn_0_basic_block_13 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 13\>:\l\
+|__builtin_puts\ (&\"Search\ failed!\"[0]);\l\
+}"];
+
+	fn_0_basic_block_14 [shape=record,style=filled,fillcolor=lightgrey,label="{\<bb\ 14\>:\l\
+|n\ =\ \{CLOBBER(eol)\};\l\
+|search\ =\ \{CLOBBER(eol)\};\l\
+|array\ =\ \{CLOBBER(eol)\};\l\
+|return;\l\
+}"];
+
+	fn_0_basic_block_0:s -> fn_0_basic_block_2:n [style="solid,bold",color=black,weight=100,constraint=true];
+	fn_0_basic_block_2:s -> fn_0_basic_block_4:n [style="solid,bold",color=black,weight=100,constraint=true];
+	fn_0_basic_block_3:s -> fn_0_basic_block_4:n [style="dotted,bold",color=blue,weight=10,constraint=false];
+	fn_0_basic_block_4:s -> fn_0_basic_block_3:n [style="solid,bold",color=forestgreen,weight=10,constraint=true];
+	fn_0_basic_block_4:s -> fn_0_basic_block_5:n [style="solid,bold",color=darkorange,weight=10,constraint=true];
+	fn_0_basic_block_5:s -> fn_0_basic_block_9:n [style="solid,bold",color=black,weight=100,constraint=true];
+	fn_0_basic_block_6:s -> fn_0_basic_block_7:n [style="solid,bold",color=forestgreen,weight=10,constraint=true];
+	fn_0_basic_block_6:s -> fn_0_basic_block_8:n [style="solid,bold",color=darkorange,weight=10,constraint=true];
+	fn_0_basic_block_7:s -> fn_0_basic_block_9:n [style="dotted,bold",color=blue,weight=10,constraint=false];
+	fn_0_basic_block_8:s -> fn_0_basic_block_9:n [style="dotted,bold",color=blue,weight=10,constraint=false];
+	fn_0_basic_block_9:s -> fn_0_basic_block_10:n [style="solid,bold",color=forestgreen,weight=10,constraint=true];
+	fn_0_basic_block_9:s -> fn_0_basic_block_11:n [style="solid,bold",color=darkorange,weight=10,constraint=true];
+	fn_0_basic_block_10:s -> fn_0_basic_block_6:n [style="solid,bold",color=forestgreen,weight=10,constraint=true];
+	fn_0_basic_block_10:s -> fn_0_basic_block_11:n [style="solid,bold",color=darkorange,weight=10,constraint=true];
+	fn_0_basic_block_11:s -> fn_0_basic_block_12:n [style="solid,bold",color=forestgreen,weight=10,constraint=true];
+	fn_0_basic_block_11:s -> fn_0_basic_block_13:n [style="solid,bold",color=darkorange,weight=10,constraint=true];
+	fn_0_basic_block_12:s -> fn_0_basic_block_14:n [style="solid,bold",color=black,weight=100,constraint=true];
+	fn_0_basic_block_13:s -> fn_0_basic_block_14:n [style="solid,bold",color=black,weight=100,constraint=true];
+	fn_0_basic_block_14:s -> fn_0_basic_block_1:n [style="solid,bold",color=black,weight=10,constraint=true];
+	fn_0_basic_block_0:s -> fn_0_basic_block_1:n [style="invis",constraint=true];
+}
+}
diff --git a/exemplos_codigos/imagens/c/2.png b/exemplos_codigos/imagens/c/2.png
new file mode 100644
index 0000000000000000000000000000000000000000..9df67456462ecbd350f40be3e94cc923fb3e7d7e
Binary files /dev/null and b/exemplos_codigos/imagens/c/2.png differ
diff --git a/exemplos_codigos/imagens/python/2.pycfg.dot b/exemplos_codigos/imagens/python/2.pycfg.dot
new file mode 100644
index 0000000000000000000000000000000000000000..cfd027a91fa061dc6b9f392bf5e0890a89776dbe
--- /dev/null
+++ b/exemplos_codigos/imagens/python/2.pycfg.dot
@@ -0,0 +1,50 @@
+strict digraph "" {
+	node [label="\N"];
+	0	[label="0: start"];
+	21	[label="0: stop"];
+	0 -> 21;
+	1	[label="1: enter: main()"];
+	3	[label="2: array = ([0] * 100)"];
+	1 -> 3;
+	2	[label="1: exit: main()"];
+	20	[label="26: return 0"];
+	20 -> 2;
+	4	[label="3: n = int(input('Enter number of elements: '))"];
+	3 -> 4;
+	5	[label="4: c = 0"];
+	4 -> 5;
+	6	[label="6: while: (c < n)"];
+	5 -> 6;
+	7	[label="7: array[c] = int(input(f'Enter element {(c + 1)}: '))"];
+	6 -> 7;
+	8	[label="10: search = int(input('Enter value to find: '))"];
+	6 -> 8;
+	7 -> 6;
+	9	[label="11: first = 0"];
+	8 -> 9;
+	10	[label="12: last = (n - 1)"];
+	9 -> 10;
+	11	[label="13: middle = ((first + last) // 2)"];
+	10 -> 11;
+	12	[label="15: while: ((array[middle] != search) and (first <= last))"];
+	11 -> 12;
+	13	[label="16: if: (array[middle] < search)"];
+	12 -> 13;
+	17	[label="22: if: ((first <= last) and (array[middle] == search))"];
+	12 -> 17;
+	16	[label="20: middle = ((first + last) // 2)"];
+	16 -> 12;
+	14	[label="17: first = (middle + 1)"];
+	13 -> 14;
+	15	[label="19: last = (middle - 1)"];
+	13 -> 15;
+	14 -> 16;
+	15 -> 16;
+	18	[label="23: print('Binary search successful!')"];
+	17 -> 18;
+	19	[label="25: print('Search failed!')"];
+	17 -> 19;
+	18 -> 20;
+	19 -> 20;
+}
+
diff --git a/exemplos_codigos/imagens/python/2.pycfg.png b/exemplos_codigos/imagens/python/2.pycfg.png
new file mode 100644
index 0000000000000000000000000000000000000000..a1dcd15f53c6cc928f1eb72a5137620564490a91
Binary files /dev/null and b/exemplos_codigos/imagens/python/2.pycfg.png differ
diff --git a/exemplos_codigos/imagens/python/2.staticfg.dot b/exemplos_codigos/imagens/python/2.staticfg.dot
new file mode 100644
index 0000000000000000000000000000000000000000..b541ff191d6d8e34da40f410f42c324f189d2bbb
--- /dev/null
+++ b/exemplos_codigos/imagens/python/2.staticfg.dot
@@ -0,0 +1,66 @@
+digraph "cluster2.py" {
+	graph [label="2.py"]
+	1 [label="def main():...
+"]
+	subgraph clustermain {
+		graph [label=main]
+		3 [label="array = [0] * 100
+n = int(input('Enter number of elements: '))
+c = 0
+"]
+		"3_calls" [label=int shape=box]
+		3 -> "3_calls" [label=calls style=dashed]
+		4 [label="while c < n:
+"]
+		5 [label="array[c] = int(input(f'Enter element {c + 1}: '))
+c += 1
+"]
+		"5_calls" [label=int shape=box]
+		5 -> "5_calls" [label=calls style=dashed]
+		5 -> 4 [label=""]
+		4 -> 5 [label="c < n"]
+		6 [label="search = int(input('Enter value to find: '))
+first = 0
+last = n - 1
+middle = (first + last) // 2
+"]
+		"6_calls" [label=int shape=box]
+		6 -> "6_calls" [label=calls style=dashed]
+		7 [label="while array[middle] != search and first <= last:
+"]
+		8 [label="if array[middle] < search:
+"]
+		10 [label="first = middle + 1
+"]
+		11 [label="middle = (first + last) // 2
+"]
+		11 -> 7 [label=""]
+		10 -> 11 [label=""]
+		8 -> 10 [label="array[middle] < search"]
+		12 [label="last = middle - 1
+"]
+		12 -> 11 [label=""]
+		8 -> 12 [label="(array[middle] >= search)"]
+		7 -> 8 [label="array[middle] != search and first <= last"]
+		9 [label="if first <= last and array[middle] == search:
+"]
+		13 [label="print('Binary search successful!')
+"]
+		"13_calls" [label=print shape=box]
+		13 -> "13_calls" [label=calls style=dashed]
+		14 [label="return 0
+"]
+		13 -> 14 [label=""]
+		9 -> 13 [label="first <= last and array[middle] == search"]
+		15 [label="print('Search failed!')
+"]
+		"15_calls" [label=print shape=box]
+		15 -> "15_calls" [label=calls style=dashed]
+		15 -> 14 [label=""]
+		9 -> 15 [label="(not (first <= last and array[middle] == search))"]
+		7 -> 9 [label="(not (array[middle] != search and first <= last))"]
+		6 -> 7 [label=""]
+		4 -> 6 [label="(c >= n)"]
+		3 -> 4 [label=""]
+	}
+}
diff --git a/exemplos_codigos/imagens/python/2.staticfg.pdf b/exemplos_codigos/imagens/python/2.staticfg.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..6f13f5fab6c74880701be4ff39a591a81dbe8d04
Binary files /dev/null and b/exemplos_codigos/imagens/python/2.staticfg.pdf differ
diff --git a/src/dotmatrix/README.md b/src/dotmatrix/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0e5db53bd89eedee8389c8ff1ea979f117a4566f
--- /dev/null
+++ b/src/dotmatrix/README.md
@@ -0,0 +1,17 @@
+# DOTMATRIX
+
+This converts a DOT file to an adjacency matrix.
+
+#### Example usage:
+First, include the dotmatrix module.
+Then, call ```dotmatrix.convert_dot_file_to_matrix```. The first argument is the path to the target dot file, and the second argument is a callback function to process the results.
+
+e.g.
+```
+const dotmatrix = require('./dotmatrix.js');
+const dot_path = process.argv[2];
+
+dotmatrix.convert_dot_file_to_matrix(dot_path, (matrix) => {
+  console.log(matrix);
+});
+```
diff --git a/src/dotmatrix/dotmatrix.js b/src/dotmatrix/dotmatrix.js
new file mode 100644
index 0000000000000000000000000000000000000000..0b1b871394431cf72d748e0d666aaef8763744b8
--- /dev/null
+++ b/src/dotmatrix/dotmatrix.js
@@ -0,0 +1,87 @@
+const fs = require('fs');
+const readline = require('readline');
+
+module.exports = (function () {
+  this.edges = new Array();
+  this.adjacency_list = {};
+
+  function convert_edges_to_adjacency_list(edges) {
+    edges.sort();
+    const vertices = new Array();
+
+    for (let i = 0; i < edges.length; i++) {
+      const vertex_left = edges[i][0].replace(';', '');
+      const vertex_right = edges[i][1].replace(';', '');
+
+      if (!vertices.includes(vertex_left)) {
+        vertices.push(vertex_left);
+      }
+      if (!vertices.includes(vertex_right)) {
+        vertices.push(vertex_right);
+      }
+
+      if (this.adjacency_list[vertex_left] == undefined) {
+        this.adjacency_list[vertex_left] = new Array(vertex_right);
+      } else {
+        this.adjacency_list[vertex_left].push(vertex_right);
+      }
+
+      if (this.adjacency_list[vertex_right] == undefined) {
+        this.adjacency_list[vertex_right] = new Array();
+      }
+    }
+
+    vertices.sort();
+    return vertices;
+  }
+
+  function convert_list_to_matrix(list) {
+    const num_unique_vertices = list.length;
+    const adjacency_matrix = new Array(num_unique_vertices);
+    for (let i = 0; i < num_unique_vertices; i++) {
+      adjacency_matrix[i] = new Array(num_unique_vertices);
+    }
+
+    for (let i = 0; i < num_unique_vertices; i++) {
+      for (let j = 0; j < num_unique_vertices; j++) {
+        adjacency_matrix[i][j] = 0;
+      }
+    }
+
+    for (let i = 0; i < num_unique_vertices; i++) {
+      const current_vertex = list[i];
+      this.adjacency_list[current_vertex].forEach((vertex) => {
+        adjacency_matrix[i][list.indexOf(vertex)] = 1;
+      });
+      process.stdout.write(i + '\r');
+    }
+    return adjacency_matrix;
+  }
+
+  function convert_dot_file_to_matrix(path, callback) {
+    this.read_stream = fs.createReadStream(path).setEncoding('ascii');
+
+    const rl = readline.createInterface({
+      input: this.read_stream
+    });
+
+    rl.on('line', (line) => {
+      if (line.includes('->')) {
+        edges.push(line.split(' -> '));
+      }
+      if (line.includes('--')) {
+        edges.push(line.split(' -- '));
+      }
+    });
+
+    rl.on('close', () => {
+      const vertices = convert_edges_to_adjacency_list(edges);
+      const adjacency_matrix = convert_list_to_matrix(vertices);
+      callback(adjacency_matrix);
+    });
+  }
+
+  return {
+    convert_dot_file_to_matrix: convert_dot_file_to_matrix
+  };
+}());
diff --git a/src/dotmatrix/dotmatrix_example.js b/src/dotmatrix/dotmatrix_example.js
new file mode 100644
index 0000000000000000000000000000000000000000..ec0a69184f7f6f5f8a5a1427b45e40c97156c2da
--- /dev/null
+++ b/src/dotmatrix/dotmatrix_example.js
@@ -0,0 +1,6 @@
+const dotmatrix = require('./dotmatrix.js');
+const dot_path = process.argv[2];
+
+dotmatrix.convert_dot_file_to_matrix(dot_path, (matrix) => {
+  console.log(matrix);
+});
diff --git a/src/dotmatrix/package.json b/src/dotmatrix/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..6cda197159d966fe428560ae92054b82bd577953
--- /dev/null
+++ b/src/dotmatrix/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "dotmatrix",
+  "version": "0.1.0",
+  "description": "Converts a DOT file to an adjacency matrix",
+  "main": "dotmatrix.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/pmusgrave/dotmatrix.git"
+  },
+  "keywords": [
+    "dot",
+    "gv",
+    "adjacency",
+    "matrix"
+  ],
+  "author": "Paul Musgrave",
+  "license": "ISC",
+  "bugs": {
+    "url": "https://github.com/pmusgrave/dotmatrix/issues"
+  },
+  "homepage": "https://github.com/pmusgrave/dotmatrix#readme"
+}
diff --git a/src/dotmatrix/test.dot b/src/dotmatrix/test.dot
new file mode 100644
index 0000000000000000000000000000000000000000..d06840bfbac20a0daed9fe0069e116da1796f492
--- /dev/null
+++ b/src/dotmatrix/test.dot
@@ -0,0 +1,10 @@
+a -- b
+a -- d
+b -- c
+b -- e
+e -- d
+a -- b
+a -- d
+b -- c
+b -- e
+e -- d
diff --git a/src/staticfg/.gitignore b/src/staticfg/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..443872b0388d933cf03fb8e0b957a127e3f037aa
--- /dev/null
+++ b/src/staticfg/.gitignore
@@ -0,0 +1,13 @@
+# Compiled python modules.
+*.pyc
+
+# Setuptools distribution folder.
+/dist/
+
+# Python egg metadata, regenerated from source files by setuptools.
+/*.egg-info
+
+**/.DS_Store
+
+/build
+/\.idea
\ No newline at end of file
diff --git a/src/staticfg/LICENSE b/src/staticfg/LICENSE
new file mode 100755
index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64
--- /dev/null
+++ b/src/staticfg/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/src/staticfg/README.md b/src/staticfg/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..2e0f4485e35d3d167503cf56b15e8f9094ccbb50
--- /dev/null
+++ b/src/staticfg/README.md
@@ -0,0 +1,55 @@
+# StatiCFG
+Python3 control flow graph generator
+
+StatiCFG is a package that can be used to produce control flow graphs (CFGs) for Python 3 programs. The CFGs it generates
+can be easily visualised with graphviz and used for static analysis. This analysis is actually the main purpose of
+the module, hence the name of **StatiC**FG.
+
+Below is an example of a piece of code that generates the Fibonacci sequence and the CFG produced for it with StatiCFG.
+
+```python
+def fib():
+    a, b = 0, 1
+    while True:
+        yield a
+        a, b = b, a + b
+
+fib_gen = fib()
+for _ in range(10):
+    next(fib_gen)
+```
+
+![Fibonacci CFG](assets/example_cfg.png)
+
+## Installation
+
+To install StatiCFG, simply clone this repository and run the command `pip3 install --upgrade .` inside of it. Please note that
+you will also need to install [Graphviz](https://www.graphviz.org/) on your machine to be able to visualise the control flow
+graphs generated by StatiCFG.
+
+## Usage
+
+To use StatiCFG, simply import the module in your Python interpreter or program, and use the `staticfg.CFGBuilder` class to 
+build CFGs. For example, to build the CFG of a program defined in a file with the path *./example.py*, the following code can 
+be used:
+
+```
+from staticfg import CFGBuilder
+
+cfg = CFGBuilder().build_from_file('example.py', './example.py')
+```
+
+This returns the CFG for the code in *./example.py* in the `cfg` variable. The first parameter of `build_from_file` is the 
+desired name for the CFG, and the second one is the path to the file containing the source code. The produced CFG can then be 
+visualised with:
+
+```
+cfg.build_visual('exampleCFG', 'pdf')
+```
+
+The first paramter of `build_visual` is the desired name for the DOT file produced by the method, and the second one is the
+format to use for the visualisation.
+
+The *build_cfg.py* script present in the */examples* folder of this repository can be used to directly generate the CFG of some 
+Python program and visualise it. To do so, simply call the script with the command `python3 build_cfg.py 
+<path_to_some_source>`.
diff --git a/src/staticfg/assets/example_cfg.png b/src/staticfg/assets/example_cfg.png
new file mode 100755
index 0000000000000000000000000000000000000000..0ec93bf38ce8d7e065ca3a6ce76033d8c32ddfcc
Binary files /dev/null and b/src/staticfg/assets/example_cfg.png differ
diff --git a/src/staticfg/examples/build_cfg.py b/src/staticfg/examples/build_cfg.py
new file mode 100755
index 0000000000000000000000000000000000000000..a9ba84cb57a7586dc25798e69e3a89d75c2d0f42
--- /dev/null
+++ b/src/staticfg/examples/build_cfg.py
@@ -0,0 +1,16 @@
+#!usr/bin/python3
+
+import argparse
+from staticfg import CFGBuilder
+
+parser = argparse.ArgumentParser(description='Generate the control flow graph\
+ of a Python program')
+parser.add_argument('input_file', help='Path to a file containing a Python\
+ program for which the CFG must be generated')
+parser.add_argument('output_file', help='Path to a file where the produced\
+ visualisation of the CFG must be saved')
+
+args = parser.parse_args()
+cfg_name = args.input_file.split('/')[-1]
+cfg = CFGBuilder().build_from_file(cfg_name, args.input_file)
+cfg.build_visual(args.output_file, format='pdf', calls=True)
diff --git a/src/staticfg/setup.py b/src/staticfg/setup.py
new file mode 100755
index 0000000000000000000000000000000000000000..06dc1bbe892488131fae148a6b1ed0303c4016e8
--- /dev/null
+++ b/src/staticfg/setup.py
@@ -0,0 +1,16 @@
+from setuptools import setup
+
+
+setup(name='staticfg',
+      version='0.9.6',
+      url='https://github.com/coetaur0/staticfg',
+      license='Apache 2',
+      author='Aurelien Coet',
+      author_email='aurelien.coet19@gmail.com',
+      description='Control flow graph generator for Python3 programs',
+      packages=['staticfg'],
+      test_suite='tests',
+      install_requires=[
+        'astor',
+        'graphviz',
+      ])
diff --git a/src/staticfg/staticfg/__init__.py b/src/staticfg/staticfg/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..79807578fe3089bc86dfc2c88642f95fbb446185
--- /dev/null
+++ b/src/staticfg/staticfg/__init__.py
@@ -0,0 +1,2 @@
+from .builder import CFGBuilder
+from .model import Block, Link, CFG
diff --git a/src/staticfg/staticfg/builder.py b/src/staticfg/staticfg/builder.py
new file mode 100755
index 0000000000000000000000000000000000000000..8779d857d97b37c4f3642247a7018d8d61585c38
--- /dev/null
+++ b/src/staticfg/staticfg/builder.py
@@ -0,0 +1,460 @@
+"""
+Control flow graph builder.
+"""
+# Aurelien Coet, 2018.
+# Modified by Andrei Nacu, 2020
+
+import ast
+from .model import Block, Link, CFG
+import sys
+
+
+def is_py38_or_higher():
+    if sys.version_info.major == 3 and sys.version_info.minor >= 8:
+        return True
+    return False
+
+
+NAMECONSTANT_TYPE = ast.Constant if is_py38_or_higher() else ast.NameConstant
+
+
+def invert(node):
+    """
+    Invert the operation in an ast node object (get its negation).
+
+    Args:
+        node: An ast node object.
+
+    Returns:
+        An ast node object containing the inverse (negation) of the input node.
+    """
+    inverse = {ast.Eq: ast.NotEq,
+               ast.NotEq: ast.Eq,
+               ast.Lt: ast.GtE,
+               ast.LtE: ast.Gt,
+               ast.Gt: ast.LtE,
+               ast.GtE: ast.Lt,
+               ast.Is: ast.IsNot,
+               ast.IsNot: ast.Is,
+               ast.In: ast.NotIn,
+               ast.NotIn: ast.In}
+
+    if type(node) == ast.Compare:
+        op = type(node.ops[0])
+        inverse_node = ast.Compare(left=node.left, ops=[inverse[op]()],
+                                   comparators=node.comparators)
+    elif isinstance(node, ast.BinOp) and type(node.op) in inverse:
+        op = type(node.op)
+        inverse_node = ast.BinOp(node.left, inverse[op](), node.right)
+    elif type(node) == NAMECONSTANT_TYPE and node.value in [True, False]:
+        inverse_node = NAMECONSTANT_TYPE(value=not node.value)
+    else:
+        inverse_node = ast.UnaryOp(op=ast.Not(), operand=node)
+
+    return inverse_node
+
+
+def merge_exitcases(exit1, exit2):
+    """
+    Merge the exitcases of two Links.
+
+    Args:
+        exit1: The exitcase of a Link object.
+        exit2: Another exitcase to merge with exit1.
+
+    Returns:
+        The merged exitcases.
+    """
+    if exit1:
+        if exit2:
+            return ast.BoolOp(ast.And(), values=[exit1, exit2])
+        return exit1
+    return exit2
+
+
+class CFGBuilder(ast.NodeVisitor):
+    """
+    Control flow graph builder.
+
+    A control flow graph builder is an ast.NodeVisitor that can walk through
+    a program's AST and iteratively build the corresponding CFG.
+    """
+
+    def __init__(self, separate=False):
+        super().__init__()
+        self.after_loop_block_stack = []
+        self.curr_loop_guard_stack = []
+        self.current_block = None
+        self.separate_node_blocks = separate
+
+    # ---------- CFG building methods ---------- #
+    def build(self, name, tree, asynchr=False, entry_id=0):
+        """
+        Build a CFG from an AST.
+
+        Args:
+            name: The name of the CFG being built.
+            tree: The root of the AST from which the CFG must be built.
+            async: Boolean indicating whether the CFG being built represents an
+                   asynchronous function or not. When the CFG of a Python
+                   program is being built, it is considered like a synchronous
+                   'main' function.
+            entry_id: Value for the id of the entry block of the CFG.
+
+        Returns:
+            The CFG produced from the AST.
+        """
+        self.cfg = CFG(name, asynchr=asynchr)
+        # Tracking of the current block while building the CFG.
+        self.current_id = entry_id
+        self.current_block = self.new_block()
+        self.cfg.entryblock = self.current_block
+        # Actual building of the CFG is done here.
+        self.visit(tree)
+        self.clean_cfg(self.cfg.entryblock)
+        return self.cfg
+
+    def build_from_src(self, name, src):
+        """
+        Build a CFG from some Python source code.
+
+        Args:
+            name: The name of the CFG being built.
+            src: A string containing the source code to build the CFG from.
+
+        Returns:
+            The CFG produced from the source code.
+        """
+        tree = ast.parse(src, mode='exec')
+        return self.build(name, tree)
+
+    def build_from_file(self, name, filepath):
+        """
+        Build a CFG from some Python source file.
+
+        Args:
+            name: The name of the CFG being built.
+            filepath: The path to the file containing the Python source code
+                      to build the CFG from.
+
+        Returns:
+            The CFG produced from the source file.
+        """
+        with open(filepath, 'r') as src_file:
+            src = src_file.read()
+            return self.build_from_src(name, src)
+
+    # ---------- Graph management methods ---------- #
+    def new_block(self):
+        """
+        Create a new block with a new id.
+
+        Returns:
+            A Block object with a new unique id.
+        """
+        self.current_id += 1
+        return Block(self.current_id)
+
+    def add_statement(self, block, statement):
+        """
+        Add a statement to a block.
+
+        Args:
+            block: A Block object to which a statement must be added.
+            statement: An AST node representing the statement that must be
+                       added to the current block.
+        """
+        block.statements.append(statement)
+
+    def add_exit(self, block, nextblock, exitcase=None):
+        """
+        Add a new exit to a block.
+
+        Args:
+            block: A block to which an exit must be added.
+            nextblock: The block to which control jumps from the new exit.
+            exitcase: An AST node representing the 'case' (or condition)
+                      leading to the exit from the block in the program.
+        """
+        newlink = Link(block, nextblock, exitcase)
+        block.exits.append(newlink)
+        nextblock.predecessors.append(newlink)
+
+    def new_loopguard(self):
+        """
+        Create a new block for a loop's guard if the current block is not
+        empty. Links the current block to the new loop guard.
+
+        Returns:
+            The block to be used as new loop guard.
+        """
+        if (self.current_block.is_empty() and
+                len(self.current_block.exits) == 0):
+            # If the current block is empty and has no exits, it is used as
+            # entry block (condition test) for the loop.
+            loopguard = self.current_block
+        else:
+            # Jump to a new block for the loop's guard if the current block
+            # isn't empty or has exits.
+            loopguard = self.new_block()
+            self.add_exit(self.current_block, loopguard)
+        return loopguard
+
+    def new_functionCFG(self, node, asynchr=False):
+        """
+        Create a new sub-CFG for a function definition and add it to the
+        function CFGs of the CFG being built.
+
+        Args:
+            node: The AST node containing the function definition.
+            async: Boolean indicating whether the function for which the CFG is
+                   being built is asynchronous or not.
+        """
+        self.current_id += 1
+        # A new sub-CFG is created for the body of the function definition and
+        # added to the function CFGs of the current CFG.
+        func_body = ast.Module(body=node.body)
+        func_builder = CFGBuilder()
+        self.cfg.functioncfgs[node.name] = func_builder.build(node.name,
+                                                              func_body,
+                                                              asynchr,
+                                                              self.current_id)
+        self.current_id = func_builder.current_id + 1
+
+    def clean_cfg(self, block, visited=[]):
+        """
+        Remove the useless (empty) blocks from a CFG.
+
+        Args:
+            block: The block from which to start traversing the CFG to clean
+                   it.
+            visited: A list of blocks that already have been visited by
+                     clean_cfg (recursive function).
+        """
+        # Don't visit blocks twice.
+        if block in visited:
+            return
+        visited.append(block)
+
+        # Empty blocks are removed from the CFG.
+        if block.is_empty():
+            for pred in block.predecessors:
+                for exit in block.exits:
+                    self.add_exit(pred.source, exit.target,
+                                  merge_exitcases(pred.exitcase,
+                                                  exit.exitcase))
+                    # Check if the exit hasn't yet been removed from
+                    # the predecessors of the target block.
+                    if exit in exit.target.predecessors:
+                        exit.target.predecessors.remove(exit)
+                # Check if the predecessor hasn't yet been removed from
+                # the exits of the source block.
+                if pred in pred.source.exits:
+                    pred.source.exits.remove(pred)
+
+            block.predecessors = []
+            # as the exits may be modified during the recursive call, it is unsafe to iterate on block.exits
+            # Created a copy of block.exits before calling clean cfg , and iterate over it instead.
+            for exit in block.exits[:]:
+                self.clean_cfg(exit.target, visited)
+            block.exits = []
+        else:
+            for exit in block.exits[:]:
+                self.clean_cfg(exit.target, visited)
+
+    # ---------- AST Node visitor methods ---------- #
+    def goto_new_block(self, node):
+        if self.separate_node_blocks:
+            newblock = self.new_block()
+            self.add_exit(self.current_block, newblock)
+            self.current_block = newblock
+        self.generic_visit(node)
+
+    def visit_Expr(self, node):
+        self.add_statement(self.current_block, node)
+        self.goto_new_block(node)
+
+    def visit_Call(self, node):
+        def visit_func(node):
+            if type(node) == ast.Name:
+                return node.id
+            elif type(node) == ast.Attribute:
+                # Recursion on series of calls to attributes.
+                func_name = visit_func(node.value)
+                func_name += "." + node.attr
+                return func_name
+            elif type(node) == ast.Str:
+                return node.s
+            elif type(node) == ast.Subscript:
+                return node.value.id
+            else:
+                return type(node).__name__
+
+        func = node.func
+        func_name = visit_func(func)
+        self.current_block.func_calls.append(func_name)
+
+    def visit_Assign(self, node):
+        self.add_statement(self.current_block, node)
+        self.goto_new_block(node)
+
+    def visit_AnnAssign(self, node):
+        self.add_statement(self.current_block, node)
+        self.goto_new_block(node)
+
+    def visit_AugAssign(self, node):
+        self.add_statement(self.current_block, node)
+        self.goto_new_block(node)
+
+    def visit_Raise(self, node):
+        # TODO
+        pass
+
+    def visit_Assert(self, node):
+        self.add_statement(self.current_block, node)
+        # New block for the case in which the assertion 'fails'.
+        failblock = self.new_block()
+        self.add_exit(self.current_block, failblock, invert(node.test))
+        # If the assertion fails, the current flow ends, so the fail block is a
+        # final block of the CFG.
+        self.cfg.finalblocks.append(failblock)
+        # If the assertion is True, continue the flow of the program.
+        successblock = self.new_block()
+        self.add_exit(self.current_block, successblock, node.test)
+        self.current_block = successblock
+        self.goto_new_block(node)
+
+    def visit_If(self, node):
+        # Add the If statement at the end of the current block.
+        self.add_statement(self.current_block, node)
+
+        # Create a new block for the body of the if.
+        if_block = self.new_block()
+        self.add_exit(self.current_block, if_block, node.test)
+
+        # Create a block for the code after the if-else.
+        afterif_block = self.new_block()
+
+        # New block for the body of the else if there is an else clause.
+        if len(node.orelse) != 0:
+            else_block = self.new_block()
+            self.add_exit(self.current_block, else_block, invert(node.test))
+            self.current_block = else_block
+            # Visit the children in the body of the else to populate the block.
+            for child in node.orelse:
+                self.visit(child)
+            # If encountered a break, exit will have already been added
+            if not self.current_block.exits:
+                self.add_exit(self.current_block, afterif_block)
+        else:
+            self.add_exit(self.current_block, afterif_block, invert(node.test))
+
+        # Visit children to populate the if block.
+        self.current_block = if_block
+        for child in node.body:
+            self.visit(child)
+        if not self.current_block.exits:
+            self.add_exit(self.current_block, afterif_block)
+
+        # Continue building the CFG in the after-if block.
+        self.current_block = afterif_block
+
+    def visit_While(self, node):
+        loop_guard = self.new_loopguard()
+        self.current_block = loop_guard
+        self.add_statement(self.current_block, node)
+        self.curr_loop_guard_stack.append(loop_guard)
+        # New block for the case where the test in the while is True.
+        while_block = self.new_block()
+        self.add_exit(self.current_block, while_block, node.test)
+
+        # New block for the case where the test in the while is False.
+        afterwhile_block = self.new_block()
+        self.after_loop_block_stack.append(afterwhile_block)
+        inverted_test = invert(node.test)
+        # Skip shortcut loop edge if while True:
+        if not (isinstance(inverted_test, NAMECONSTANT_TYPE) and
+                inverted_test.value is False):
+            self.add_exit(self.current_block, afterwhile_block, inverted_test)
+
+        # Populate the while block.
+        self.current_block = while_block
+        for child in node.body:
+            self.visit(child)
+        if not self.current_block.exits:
+            # Did not encounter a break statement, loop back
+            self.add_exit(self.current_block, loop_guard)
+
+        # Continue building the CFG in the after-while block.
+        self.current_block = afterwhile_block
+        self.after_loop_block_stack.pop()
+        self.curr_loop_guard_stack.pop()
+
+    def visit_For(self, node):
+        loop_guard = self.new_loopguard()
+        self.current_block = loop_guard
+        self.add_statement(self.current_block, node)
+        self.curr_loop_guard_stack.append(loop_guard)
+        # New block for the body of the for-loop.
+        for_block = self.new_block()
+        self.add_exit(self.current_block, for_block, node.iter)
+
+        # Block of code after the for loop.
+        afterfor_block = self.new_block()
+        self.add_exit(self.current_block, afterfor_block)
+        self.after_loop_block_stack.append(afterfor_block)
+        self.current_block = for_block
+
+        # Populate the body of the for loop.
+        for child in node.body:
+            self.visit(child)
+        if not self.current_block.exits:
+            # Did not encounter a break
+            self.add_exit(self.current_block, loop_guard)
+
+        # Continue building the CFG in the after-for block.
+        self.current_block = afterfor_block
+        # Popping the current after loop stack,taking care of errors in case of nested for loops
+        self.after_loop_block_stack.pop()
+        self.curr_loop_guard_stack.pop()
+
+    def visit_Break(self, node):
+        assert len(self.after_loop_block_stack), "Found break not inside loop"
+        self.add_exit(self.current_block, self.after_loop_block_stack[-1])
+
+    def visit_Continue(self, node):
+        assert len(self.curr_loop_guard_stack), "Found continue outside loop"
+        self.add_exit(self.current_block, self.curr_loop_guard_stack[-1])
+
+    def visit_Import(self, node):
+        self.add_statement(self.current_block, node)
+
+    def visit_ImportFrom(self, node):
+        self.add_statement(self.current_block, node)
+
+    def visit_FunctionDef(self, node):
+        self.add_statement(self.current_block, node)
+        self.new_functionCFG(node, asynchr=False)
+
+    def visit_AsyncFunctionDef(self, node):
+        self.add_statement(self.current_block, node)
+        self.new_functionCFG(node, asynchr=True)
+
+    def visit_Await(self, node):
+        afterawait_block = self.new_block()
+        self.add_exit(self.current_block, afterawait_block)
+        self.goto_new_block(node)
+        self.current_block = afterawait_block
+
+    def visit_Return(self, node):
+        self.add_statement(self.current_block, node)
+        self.cfg.finalblocks.append(self.current_block)
+        # Continue in a new block but without any jump to it -> all code after
+        # the return statement will not be included in the CFG.
+        self.current_block = self.new_block()
+
+    def visit_Yield(self, node):
+        self.cfg.asynchr = True
+        afteryield_block = self.new_block()
+        self.add_exit(self.current_block, afteryield_block)
+        self.current_block = afteryield_block
diff --git a/src/staticfg/staticfg/model.py b/src/staticfg/staticfg/model.py
new file mode 100755
index 0000000000000000000000000000000000000000..4dd139fa338c4c3221605aaebdb34b264a988e04
--- /dev/null
+++ b/src/staticfg/staticfg/model.py
@@ -0,0 +1,241 @@
+"""
+Control flow graph for Python programs.
+"""
+# Aurelien Coet, 2018.
+
+import ast
+import astor
+import graphviz as gv
+
+
+class Block(object):
+    """
+    Basic block in a control flow graph.
+
+    Contains a list of statements executed in a program without any control
+    jumps. A block of statements is exited through one of its exits. Exits are
+    a list of Links that represent control flow jumps.
+    """
+
+    __slots__ = ["id", "statements", "func_calls", "predecessors", "exits"]
+
+    def __init__(self, id):
+        # Id of the block.
+        self.id = id
+        # Statements in the block.
+        self.statements = []
+        # Calls to functions inside the block (represents context switches to
+        # some functions' CFGs).
+        self.func_calls = []
+        # Links to predecessors in a control flow graph.
+        self.predecessors = []
+        # Links to the next blocks in a control flow graph.
+        self.exits = []
+
+    def __str__(self):
+        if self.statements:
+            return "block:{}@{}".format(self.id, self.at())
+        return "empty block:{}".format(self.id)
+
+    def __repr__(self):
+        txt = "{} with {} exits".format(str(self), len(self.exits))
+        if self.statements:
+            txt += ", body=["
+            txt += ", ".join([ast.dump(node) for node in self.statements])
+            txt += "]"
+        return txt
+
+    def at(self):
+        """
+        Get the line number of the first statement of the block in the program.
+        """
+        if self.statements and self.statements[0].lineno >= 0:
+            return self.statements[0].lineno
+        return None
+
+    def is_empty(self):
+        """
+        Check if the block is empty.
+
+        Returns:
+            A boolean indicating if the block is empty (True) or not (False).
+        """
+        return len(self.statements) == 0
+
+    def get_source(self):
+        """
+        Get a string containing the Python source code corresponding to the
+        statements in the block.
+
+        Returns:
+            A string containing the source code of the statements.
+        """
+        src = ""
+        for statement in self.statements:
+            if type(statement) in [ast.If, ast.For, ast.While]:
+                src += (astor.to_source(statement)).split('\n')[0] + "\n"
+            elif type(statement) == ast.FunctionDef or\
+                 type(statement) == ast.AsyncFunctionDef:
+                src += (astor.to_source(statement)).split('\n')[0] + "...\n"
+            else:
+                src += astor.to_source(statement)
+        return src
+
+    def get_calls(self):
+        """
+        Get a string containing the calls to other functions inside the block.
+
+        Returns:
+            A string containing the names of the functions called inside the
+            block.
+        """
+        txt = ""
+        for func_name in self.func_calls:
+            txt += func_name + '\n'
+        return txt
+
+
+class Link(object):
+    """
+    Link between blocks in a control flow graph.
+
+    Represents a control flow jump between two blocks. Contains an exitcase in
+    the form of an expression, representing the case in which the associated
+    control jump is made.
+    """
+
+    __slots__ = ["source", "target", "exitcase"]
+
+    def __init__(self, source, target, exitcase=None):
+        assert type(source) == Block, "Source of a link must be a block"
+        assert type(target) == Block, "Target of a link must be a block"
+        # Block from which the control flow jump was made.
+        self.source = source
+        # Target block of the control flow jump.
+        self.target = target
+        # 'Case' leading to a control flow jump through this link.
+        self.exitcase = exitcase
+
+    def __str__(self):
+        return "link from {} to {}".format(str(self.source), str(self.target))
+
+    def __repr__(self):
+        if self.exitcase is not None:
+            return "{}, with exitcase {}".format(str(self),
+                                                 ast.dump(self.exitcase))
+        return str(self)
+
+    def get_exitcase(self):
+        """
+        Get a string containing the Python source code corresponding to the
+        exitcase of the Link.
+
+        Returns:
+            A string containing the source code.
+        """
+        if self.exitcase:
+            return astor.to_source(self.exitcase)
+        return ""
+
+
+class CFG(object):
+    """
+    Control flow graph (CFG).
+
+    A control flow graph is composed of basic blocks and links between them
+    representing control flow jumps. It has a unique entry block and several
+    possible 'final' blocks (blocks with no exits representing the end of the
+    CFG).
+    """
+
+    def __init__(self, name, asynchr=False):
+        assert type(name) == str, "Name of a CFG must be a string"
+        assert type(asynchr) == bool, "Async must be a boolean value"
+        # Name of the function or module being represented.
+        self.name = name
+        # Type of function represented by the CFG (sync or async). A Python
+        # program is considered as a synchronous function (main).
+        self.asynchr = asynchr
+        # Entry block of the CFG.
+        self.entryblock = None
+        # Final blocks of the CFG.
+        self.finalblocks = []
+        # Sub-CFGs for functions defined inside the current CFG.
+        self.functioncfgs = {}
+
+    def __str__(self):
+        return "CFG for {}".format(self.name)
+
+    def _visit_blocks(self, graph, block, visited=[], calls=True):
+        # Don't visit blocks twice.
+        if block.id in visited:
+            return
+
+        nodelabel = block.get_source()
+
+        graph.node(str(block.id), label=nodelabel)
+        visited.append(block.id)
+
+        # Show the block's function calls in a node.
+        if calls and block.func_calls:
+            calls_node = str(block.id)+"_calls"
+            calls_label = block.get_calls().strip()
+            graph.node(calls_node, label=calls_label,
+                       _attributes={'shape': 'box'})
+            graph.edge(str(block.id), calls_node, label="calls",
+                       _attributes={'style': 'dashed'})
+
+        # Recursively visit all the blocks of the CFG.
+        for exit in block.exits:
+            self._visit_blocks(graph, exit.target, visited, calls=calls)
+            edgelabel = exit.get_exitcase().strip()
+            graph.edge(str(block.id), str(exit.target.id), label=edgelabel)
+
+    def _build_visual(self, format='pdf', calls=True):
+        graph = gv.Digraph(name='cluster'+self.name, format=format,
+                           graph_attr={'label': self.name})
+        self._visit_blocks(graph, self.entryblock, visited=[], calls=calls)
+
+        # Build the subgraphs for the function definitions in the CFG and add
+        # them to the graph.
+        for subcfg in self.functioncfgs:
+            subgraph = self.functioncfgs[subcfg]._build_visual(format=format,
+                                                               calls=calls)
+            graph.subgraph(subgraph)
+
+        return graph
+
+    def build_visual(self, filepath, format, calls=True, show=True):
+        """
+        Build a visualisation of the CFG with graphviz and output it in a DOT
+        file.
+
+        Args:
+            filename: The name of the output file in which the visualisation
+                      must be saved.
+            format: The format to use for the output file (PDF, ...).
+            show: A boolean indicating whether to automatically open the output
+                  file after building the visualisation.
+        """
+        graph = self._build_visual(format, calls)
+        graph.render(filepath, view=show)
+
+    def __iter__(self):
+        """
+        Generator that yields all the blocks in the current graph, then
+        recursively yields from any sub graphs
+        """
+        visited = set()
+        to_visit = [self.entryblock]
+
+        while to_visit:
+            block = to_visit.pop(0)
+            visited.add(block)
+            for exit_ in block.exits:
+                if exit_.target in visited or exit_.target in to_visit:
+                    continue
+                to_visit.append(exit_.target)
+            yield block
+
+        for subcfg in self.functioncfgs.values():
+            yield from subcfg
\ No newline at end of file
diff --git a/src/staticfg/teste.png b/src/staticfg/teste.png
new file mode 100644
index 0000000000000000000000000000000000000000..07c638167850a826f0ad3b168ef0b294fe4ee7c9
Binary files /dev/null and b/src/staticfg/teste.png differ
diff --git a/src/staticfg/tests/test_model.py b/src/staticfg/tests/test_model.py
new file mode 100755
index 0000000000000000000000000000000000000000..4950a47c53f133dc8027c10143dfb352d029cfc8
--- /dev/null
+++ b/src/staticfg/tests/test_model.py
@@ -0,0 +1,194 @@
+import unittest
+import ast
+from staticfg.model import *
+from staticfg.builder import CFGBuilder, is_py38_or_higher
+
+
+class TestBlock(unittest.TestCase):
+
+    def test_instanciation(self):
+        block = Block(1)
+        self.assertEqual(block.id, 1)
+        self.assertEqual(block.statements, [])
+        self.assertEqual(block.func_calls, [])
+        self.assertEqual(block.predecessors, [])
+        self.assertEqual(block.exits, [])
+
+    def test_str_representation(self):
+        block = Block(1)
+        self.assertEqual(str(block), "empty block:1")
+        tree = ast.parse("a = 1")
+        block.statements.append(tree.body[0])
+        self.assertEqual(str(block), "block:1@1")
+
+    def test_repr(self):
+        block = Block(1)
+        self.assertEqual(repr(block), "empty block:1 with 0 exits")
+        tree = ast.parse("a = 1")
+        block.statements.append(tree.body[0])
+        if is_py38_or_higher():
+            self.assertEqual(repr(block), "block:1@1 with 0 exits, body=[\
+Assign(targets=[Name(id='a', ctx=Store())], value=Constant(value=1, kind=None), type_comment=None)]")
+        else:
+            self.assertEqual(repr(block), "block:1@1 with 0 exits, body=[\
+Assign(targets=[Name(id='a', ctx=Store())], value=Num(n=1))]")
+
+    def test_at(self):
+        block = Block(1)
+        self.assertEqual(block.at(), None)
+        tree = ast.parse("a = 1")
+        block.statements.append(tree.body[0])
+        self.assertEqual(block.at(), 1)
+
+    def test_is_empty(self):
+        block = Block(1)
+        self.assertTrue(block.is_empty())
+        tree = ast.parse("a = 1")
+        block.statements.append(tree.body[0])
+        self.assertFalse(block.is_empty())
+
+    def test_get_source(self):
+        block = Block(1)
+        self.assertEqual(block.get_source(), "")
+        tree = ast.parse("a = 1")
+        block.statements.append(tree.body[0])
+        self.assertEqual(block.get_source(), "a = 1\n")
+
+    def test_get_calls(self):
+        block = Block(1)
+        self.assertEqual(block.get_calls(), "")
+        block.func_calls.append("fun")
+        self.assertEqual(block.get_calls(), "fun\n")
+
+
+class TestLink(unittest.TestCase):
+
+    def test_instanciation(self):
+        block1 = Block(1)
+        block2 = Block(2)
+        with self.assertRaises(AssertionError):
+            Link(2, block2)  # Source of a link must be a block.
+            Link(block1, 2)  # Target of a link must be a block.
+
+        condition = ast.parse("a == 1").body[0]
+        link = Link(block1, block2, condition)
+        self.assertEqual(link.source, block1)
+        self.assertEqual(link.target, block2)
+        self.assertEqual(link.exitcase, condition)
+
+    def test_str_representation(self):
+        block1 = Block(1)
+        block2 = Block(2)
+        link = Link(block1, block2)
+        self.assertEqual(str(link), "link from empty block:1 to empty block:2")
+
+    def test_repr(self):
+        block1 = Block(1)
+        block2 = Block(2)
+        condition = ast.parse("a == 1").body[0]
+        link = Link(block1, block2, condition)
+        self.assertEqual(repr(link), "link from empty block:1 to empty block:2\
+, with exitcase {}".format(ast.dump(condition)))
+
+    def test_get_exitcase(self):
+        block1 = Block(1)
+        block2 = Block(2)
+        condition = ast.parse("a == 1").body[0]
+        link = Link(block1, block2, condition)
+        self.assertEqual(link.get_exitcase(), "a == 1\n")
+
+
+class TestCFG(unittest.TestCase):
+
+    def test_instanciation(self):
+        with self.assertRaises(AssertionError):
+            CFG(2, False)  # Name of a CFG must be a string.
+            CFG('cfg', 2)  # Async argument must be a boolean.
+
+        cfg = CFG('cfg', False)
+        self.assertEqual(cfg.name, 'cfg')
+        self.assertFalse(cfg.asynchr)
+        self.assertEqual(cfg.entryblock, None)
+        self.assertEqual(cfg.finalblocks, [])
+        self.assertEqual(cfg.functioncfgs, {})
+
+    def test_str_representation(self):
+        cfg = CFG('cfg', False)
+        self.assertEqual(str(cfg), 'CFG for cfg')
+
+    def test_iter(self):
+        src = """\
+def fib():
+    a, b = 0, 1
+    while True:
+        yield a
+        a, b = b, a + b
+"""
+        cfg = CFGBuilder().build_from_src("fib", src)
+        expected_block_sources = [
+            "def fib():...\n",
+            "a, b = 0, 1\n",
+            "while True:\n",
+            "yield a\n",
+            "a, b = b, a + b\n"
+        ]
+        for actual_block, expected_src in zip(cfg, expected_block_sources):
+            self.assertEqual(actual_block.get_source(), expected_src)
+
+    def test_break(self):
+        src = """\
+def foo():
+    i = 0
+    while True:
+        i += 1
+        if i == 3:
+            break
+    for j in range(3):
+        i += j
+        if j == 2:
+            break
+    return i
+"""
+        cfg = CFGBuilder().build_from_src("foo", src)
+        expected_block_sources = [
+            "def foo():...\n",
+            "i = 0\n",
+            "while True:\n",
+            "i += 1\n"
+            "if i == 3:\n",
+            "for j in range(3):\n",
+            "i += j\n"
+            "if j == 2:\n",
+            "return i\n",
+            "",
+        ]
+        for actual_block, expected_src in zip(cfg, expected_block_sources):
+            self.assertEqual(actual_block.get_source(), expected_src)
+
+    def test_break_in_main_body(self):
+        src = """\
+def foo():
+    i = 0
+    while True:
+        i += 1
+        break
+    for j in range(3):
+        i += j
+        break
+    return i
+"""
+        cfg = CFGBuilder().build_from_src("foo", src)
+        expected_block_sources = [
+            "def foo():...\n",
+            "i = 0\n",
+            "while True:\n",
+            "i += 1\n",
+            "for j in range(3):\n",
+            "i += j\n",
+            "return i\n"
+        ]
+        for actual_block, expected_src in zip(cfg, expected_block_sources):
+            self.assertEqual(actual_block.get_source(), expected_src)
+
+if __name__ == "__main__":
+    unittest.main()