The N4JS IDE comes with several tools to get insights into the source code such as the AST and the control flow. In this post we present the control flow graph view.
When learning a programming language, we probably start with a small
Hello World! program. From there on, we learn not only language keywords and libraries, but also get an implicit understanding in which order statements and expressions are executed. This order is called
control flow. For instance, we learn the effects of
if and
for statements which are also called
control statements, since they have a big influence on the execution order. The most difficult statement in this respect is probably the
try-finally control statement, which is explained later.
Hello World
function f() {
console.log("Hello World!");
}
Let's have a look at the
Hello World! from a control flow perspective, first. The source code of
Hello World! is simple, but it already consists of three important elements:
- the method call console.log,
- the nested argument "Hello World!", and
- the function body of f() which contains the two elements mentioned above.
The function body is also called
control flow container.
The image above shows the control flow graph of the
Hello World! example. The method call is separated into the receiver and its property
log. The next element the control flow goes to is the argument
"Hello World!" until it reaches the method call of
log as the final element.
Loops
The example above showed the succeeding control flow of some code elements. This control flow gets more interesting in statements that introduce branches such as loop statements do. In the example below, the control flow of a
for-loop is shown. To indicate the start and end of the function, and also the body of the loop, the function calls
start(),
loop() and
end() are used.
function f() {
start();
for (var i=0; i<2; i++) {
loop();
}
end();
}
The control flow of the loop example shows branches and merges. After the condition
i<2, either the body is entered via the edge named
LoopEnter, or the loop is exited. In case the body was entered, the control flow first targets the call to
loop() and then goes back to the entry of the condition.
Try-Finally
Finally blocks have tricky semantics since they can be entered and exited in two specific ways: normally and abruptly. Abrupt control flow occurs after a
return,
continue,
break or
throw statement. These will introduce a jump that targets either the end of the function or the entry of a finally block. In case the control flow jumps to a finally block, this finally block will be executed normally. However, since the block was entered abruptly, it will exit abruptly again. This means, that there will be a second jump from the exit of the finally block to either the end of function or the entry of the next finally block.
The following example shows this behaviour by using some dead code elements, which have a grey background colour in the graph image. These dead code elements are not reachable, since there is no normal control flow path that exits the try block.
function t() {
"start";
try {
2;
return;
3;
}
finally {
"finally";
}
"end";
}
Final catch
In case you ever wondered how a thrown exception can be caught without using a catch block, have a look at the final example. It is true that once a finally block was entered abruptly, it can only be exited abruptly. However, the kind of abrupt control flow might be changed. In the example below, it is changed from throwing an exception to breaking the loop. Of course, after the loop was exited due to the
break statement, the control flow is normal again and the thrown exception remains without effect. Hence, the last statement
"end" is executed which would have been skipped otherwise.
function t() {
"start";
do {
try {
2;
throw "exception";
}
finally {
break;
}
}
while (
4);
"end";
}
by Marcus Mews