[Bug c++/93934] New: Unnecessary fld of uninitialized float stack variable results in ub of valid C++ code
vajdaz at protonmail dot com
gcc-bugzilla@gcc.gnu.org
Tue Feb 25 21:58:00 GMT 2020
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93934
Bug ID: 93934
Summary: Unnecessary fld of uninitialized float stack variable
results in ub of valid C++ code
Product: gcc
Version: 7.4.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: vajdaz at protonmail dot com
Target Milestone: ---
Valid C++ code is compiled to assembly that seems to be buggy. Below source
code and assembly output of the compiler.
Contents of source file float.cc:
extern void foo();
extern void bar();
double func(int size, double d, bool b) {
double result;
for(int i = 0; i < size; ++i) {
if (i == 0) {
foo();
if(b) result = d;
}
else {
bar();
if(b) result = 42.0;
}
}
return b && size > 0 ? result : 7.0;
}
I assume that this is valid C++ code. On C++ level there is no way to access an
uninitialized variable. The variable result is only used as a return value if
size is greater than zero, and b is true, in which case result will be
initialized before, for sure.
Create assembly:
$ g++ -m32 -O2 -fno-pic -fno-omit-frame-pointer -S float.cc
Output is:
.file "float.cc"
.text
.p2align 4,,15
.globl _Z4funcidb
.type _Z4funcidb, @function
_Z4funcidb:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
pushl %edi
pushl %esi
pushl %ebx
subl $44, %esp
.cfi_offset 7, -12
.cfi_offset 6, -16
.cfi_offset 3, -20
movl 8(%ebp), %ebx
movl 20(%ebp), %eax
fldl 12(%ebp)
testl %ebx, %ebx
fstpl -48(%ebp)
movl %eax, -36(%ebp)
jle .L2
movl %eax, %esi
xorl %edi, %edi
.p2align 4,,10
.p2align 3
.L5:
testl %edi, %edi
je .L16
call _Z3barv
movl %esi, %eax
testb %al, %al
je .L4
flds .LC0
fstpl -32(%ebp)
.L4:
addl $1, %edi
cmpl %edi, %ebx
jne .L5
cmpb $0, -36(%ebp)
fldl -32(%ebp)
je .L17
addl $44, %esp
popl %ebx
.cfi_remember_state
.cfi_restore 3
popl %esi
.cfi_restore 6
popl %edi
.cfi_restore 7
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.p2align 4,,10
.p2align 3
.L17:
.cfi_restore_state
fstp %st(0)
.L2:
flds .LC1
addl $44, %esp
popl %ebx
.cfi_remember_state
.cfi_restore 3
popl %esi
.cfi_restore 6
popl %edi
.cfi_restore 7
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.p2align 4,,10
.p2align 3
.L16:
.cfi_restore_state
call _Z3foov
fldl -48(%ebp)
movl %esi, %eax
testb %al, %al
fldl -32(%ebp)
fcmovne %st(1), %st
fstp %st(1)
fstpl -32(%ebp)
jmp .L4
.cfi_endproc
.LFE0:
.size _Z4funcidb, .-_Z4funcidb
.section .rodata.cst4,"aM",@progbits,4
.align 4
.LC0:
.long 1109917696
.align 4
.LC1:
.long 1088421888
.ident "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
.section .note.GNU-stack,"",@progbits
Following things happen in the assembly code when size is 1 and b is true for
example.
* The value of size is stored into %ebx
* The value of b is stored into %eax and -36(%ebp), later in %esi
* The value of d is loaded into the fpu register stack and stored into
-48(%ebp)
We continue straight to the line with "je .L16" and jump to the label .L16. And
here comes the interesting part:
.L16:
.cfi_restore_state
call _Z3foov
fldl -48(%ebp) <-- loading value of d into fpu
movl %esi, %eax
testb %al, %al <-- testing if b is true (for fcmovne below)
fldl -32(%ebp) <-- loading uninitialized stack variable result
fcmovne %st(1), %st <-- choosing which value to put back into result
fstp %st(1)
fstpl -32(%ebp)
jmp .L4
Why we have here a combination of two fldl instructions and a fcmovne? This
causes a possible fpu exception if the value of the uninitialized variable
named result is accidentally a SNaN value. Which means, the behavior of this
code is unpredictable.
This can be reproduced with any GCC since 4.4.7, if the -march option is set to
something that supports fcmov instructions. I used GCC 7.4.0.
Is this a bug, or do I miss something?
More information about the Gcc-bugs
mailing list