1) Our goal is emitting goto rarely, not excluding them entirely.
However, most of the gotos one sees in IDA are due limitations in control-flow recovery, example:
switch (x) {
case 0:
do_0();
// Missing case 1
case 2:
do_2();
case 3:
do_3();
default:
do_default();
}
IDA will produce something like...
if (x > 3)
goto label;
switch (x) {
case 0:
do_0();
case 1:
label:
do_default();
case 2:
do_2();
case 3:
do_3();
}
gotos originally present in the original code are almost never the reason you see a goto in IDA.
In any case, in presence of gotos, we currently duplicate code and sometimes this is good.
Imagine the following code snippet which represents a legitimate use of gotos:
int *x = malloc(sizeof(int));
if (x == NULL)
goto cleanup;
int *y = malloc(sizeof(int));
if (y == NULL)
goto cleanup;
do_stuff(x, y);
cleanup:
if (x != NULL)
free(x);
if (y != NULL)
free(y);
return;
By "inlining" the gotos and doing some trivial optimizations we'd get:
int *x = malloc(sizeof(int));
if (x == NULL)
return;
int *y = malloc(sizeof(int));
if (y == NULL) {
free(x);
return;
}
do_stuff(x, y);
free(x);
free(y);
return;
Which is not bad a at all IMO.
2) We don't deal with duplication, it's way less of a problem, usually.
For deduplication, if it leads to gotos, we duplicate.