I would like to understand the branches generated by GCC compiler when using noexcept or throw() for marking non-throwing function. I know the differences between noexcept and throw(), but cannot figure out what are the additional branches defined when using throw() instead of noexcept and how to improve the coverage to have 100% of branch coverage?
Code snippet of tested example class:
class MyClass
{
public:
MyClass() noexcept :
iThrowFlag(false)
{}
void non_throwing_method() noexcept
{
try
{
throwing_method();
}
catch (...)
{
}
}
void throwing_method()
{
if (iThrowFlag)
{
throw std::exception();
}
}
public:
bool iThrowFlag;
};
There are 2 test cases defined, both passing:
- Verification if
non_throwing_methodcompletes when underlyingthrowing_methodis not throwing, - Verification if
non_throwing_methodcompletes when underlyingthrowing_methodis throwing any exception.
Below is the coverage report generated by GCC/GCOV for above code, all the lines and branches are covered (4/4 branches covered):
-: 0:Source:myclass.hh
-: 0:Graph:main.gcno
-: 0:Data:main.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <iostream>
-: 2:#include <string>
-: 3:
-: 4:using namespace std;
-: 5:
-: 6:class MyClass
-: 7:{
-: 8:public:
function _ZN7MyClassC2Ev called 2 returned 100% blocks executed 100%
2: 9: MyClass() noexcept :
2: 10: iThrowFlag(false)
2: 11: {}
-: 12:
function _ZN7MyClass19non_throwing_methodEv called 2 returned 100% blocks executed 100%
2: 13: void non_throwing_method() noexcept
-: 14: {
-: 15: try
-: 16: {
2: 17: throwing_method();
call 0 returned 100%
branch 1 taken 50% (fallthrough)
branch 2 taken 50% (throw)
-: 18: }
1: 19: catch (...)
call 0 returned 100%
-: 20: {
-: 21: }
2: 22: }
-: 23:
function _ZN7MyClass15throwing_methodEv called 2 returned 50% blocks executed 100%
2: 24: void throwing_method()
-: 25: {
2: 26: if (iThrowFlag)
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 27: {
1: 28: throw std::exception();
call 0 returned 100%
call 1 returned 100%
call 2 returned 0%
-: 29: }
1: 30: }
-: 31:
-: 32:public:
-: 33: bool iThrowFlag;
-: 34:};
As target platform does not support C++11 and noexcept, it was exchanged with throw() specifier as below:
class MyClass
{
public:
MyClass() throw() :
iThrowFlag(false)
{}
void non_throwing_method() throw()
{
try
{
throwing_method();
}
catch (...)
{
}
}
void throwing_method()
{
if (iThrowFlag)
{
throw std::exception();
}
}
public:
bool iThrowFlag;
};
Below the GCOV output with examined branches (5/8). Code with throw() generates additional 4 branches, which are quite hard to understand and to cover by testing:
-: 0:Source:myclass.hh
-: 0:Graph:main.gcno
-: 0:Data:main.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <iostream>
-: 2:#include <string>
-: 3:
-: 4:using namespace std;
-: 5:
-: 6:class MyClass
-: 7:{
-: 8:public:
function _ZN7MyClassC2Ev called 2 returned 100% blocks executed 100%
2: 9: MyClass() throw() :
2: 10: iThrowFlag(false)
2: 11: {}
-: 12:
function _ZN7MyClass19non_throwing_methodEv called 2 returned 100% blocks executed 75%
2: 13: void non_throwing_method() throw()
-: 14: {
-: 15: try
-: 16: {
2: 17: throwing_method();
call 0 returned 100%
branch 1 taken 50% (fallthrough)
branch 2 taken 50% (throw)
-: 18: }
1: 19: catch (...)
call 0 returned 100%
call 1 returned 100%
branch 2 taken 100% (fallthrough)
branch 3 taken 0% (throw)
branch 4 never executed
branch 5 never executed
call 6 never executed
-: 20: {
-: 21: }
2: 22: }
-: 23:
function _ZN7MyClass15throwing_methodEv called 2 returned 50% blocks executed 100%
2: 24: void throwing_method()
-: 25: {
2: 26: if (iThrowFlag)
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 27: {
1: 28: throw std::exception();
call 0 returned 100%
call 1 returned 100%
call 2 returned 0%
-: 29: }
1: 30: }
-: 31:
-: 32:public:
-: 33: bool iThrowFlag;
-: 34:};
Test cases are below (to have full details about the case):
void test1()
{
MyClass lObj;
lObj.non_throwing_method();
}
void test2()
{
MyClass lObj;
lObj.iThrowFlag = true;
lObj.non_throwing_method();
lObj.iThrowFlag = false;
}
I would appreciate if someone have an answer/explanation for described behavior.
EDIT: Additionally attached assembly code (only code of function: `non_throwing_method1 to compare).
With noexcept:
_ZN7MyClass19non_throwing_methodEv:
.LFB1253:
.loc 2 13 0
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA1253
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %rdi, -8(%rbp)
movq __gcov0._ZN7MyClass19non_throwing_methodEv(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv(%rip)
.loc 2 17 0
movq -8(%rbp), %rax
movq %rax, %rdi
.LEHB0:
call _ZN7MyClass15throwing_methodEv
.LEHE0:
movq __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip)
.loc 2 22 0
jmp .L7
.L6:
movq %rax, %rdx
movq __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip)
.loc 2 19 0
movq %rdx, %rax
movq %rax, %rdi
call __cxa_begin_catch
movq __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip)
call __cxa_end_catch
movq __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip)
.L7:
.loc 2 22 0
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1253:
.globl __gxx_personality_v0
.section .gcc_except_table._ZN7MyClass19non_throwing_methodEv,"aG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
.align 4
.LLSDA1253:
.byte 0xff
.byte 0x3
.uleb128 .LLSDATT1253-.LLSDATTD1253
.LLSDATTD1253:
.byte 0x1
.uleb128 .LLSDACSE1253-.LLSDACSB1253
.LLSDACSB1253:
.uleb128 .LEHB0-.LFB1253
.uleb128 .LEHE0-.LEHB0
.uleb128 .L6-.LFB1253
.uleb128 0x1
.LLSDACSE1253:
.byte 0x1
.byte 0
.align 4
.long 0
.LLSDATT1253:
.section .text._ZN7MyClass19non_throwing_methodEv,"axG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
.size _ZN7MyClass19non_throwing_methodEv, .-_ZN7MyClass19non_throwing_methodEv
.section .text._ZN7MyClass15throwing_methodEv,"axG",@progbits,_ZN7MyClass15throwing_methodEv,comdat
.align 2
.weak _ZN7MyClass15throwing_methodEv
.type _ZN7MyClass15throwing_methodEv, @function
With throw():
_ZN7MyClass19non_throwing_methodEv:
.LFB1253:
.loc 2 13 0
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA1253
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %rdi, -8(%rbp)
movq __gcov0._ZN7MyClass19non_throwing_methodEv(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv(%rip)
.loc 2 17 0
movq -8(%rbp), %rax
movq %rax, %rdi
.LEHB0:
call _ZN7MyClass15throwing_methodEv
.LEHE0:
movq __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip)
.loc 2 22 0
jmp .L3
.L8:
movq %rax, %rdx
movq __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip)
.loc 2 19 0
movq %rdx, %rax
movq %rax, %rdi
call __cxa_begin_catch
movq __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip)
.LEHB1:
call __cxa_end_catch
.LEHE1:
movq __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip), %rax
addq $1, %rax
movq %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip)
.loc 2 22 0
jmp .L3
.L9:
cmpq $-1, %rdx
je .L7
movq __gcov0._ZN7MyClass19non_throwing_methodEv+48(%rip), %rdx
addq $1, %rdx
movq %rdx, __gcov0._ZN7MyClass19non_throwing_methodEv+48(%rip)
movq %rax, %rdi
.LEHB2:
call _Unwind_Resume
.L7:
movq __gcov0._ZN7MyClass19non_throwing_methodEv+40(%rip), %rdx
addq $1, %rdx
movq %rdx, __gcov0._ZN7MyClass19non_throwing_methodEv+40(%rip)
.loc 2 13 0
movq %rax, %rdi
call __cxa_call_unexpected
.LEHE2:
.L3:
.loc 2 22 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1253:
.globl __gxx_personality_v0
.section .gcc_except_table._ZN7MyClass19non_throwing_methodEv,"aG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
.align 4
.LLSDA1253:
.byte 0xff
.byte 0x3
.uleb128 .LLSDATT1253-.LLSDATTD1253
.LLSDATTD1253:
.byte 0x1
.uleb128 .LLSDACSE1253-.LLSDACSB1253
.LLSDACSB1253:
.uleb128 .LEHB0-.LFB1253
.uleb128 .LEHE0-.LEHB0
.uleb128 .L8-.LFB1253
.uleb128 0x1
.uleb128 .LEHB1-.LFB1253
.uleb128 .LEHE1-.LEHB1
.uleb128 .L9-.LFB1253
.uleb128 0x3
.uleb128 .LEHB2-.LFB1253
.uleb128 .LEHE2-.LEHB2
.uleb128 0
.uleb128 0
.LLSDACSE1253:
.byte 0x1
.byte 0
.byte 0x7f
.byte 0
.align 4
.long 0
.LLSDATT1253:
.byte 0
.section .text._ZN7MyClass19non_throwing_methodEv,"axG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
.size _ZN7MyClass19non_throwing_methodEv, .-_ZN7MyClass19non_throwing_methodEv
.section .text._ZN7MyClass15throwing_methodEv,"axG",@progbits,_ZN7MyClass15throwing_methodEv,comdat
.align 2
.weak _ZN7MyClass15throwing_methodEv
.type _ZN7MyClass15throwing_methodEv, @function