362 lines
11 KiB
Plaintext
362 lines
11 KiB
Plaintext
# Copyright (C) 2012-2022 Free Software Foundation, Inc.
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with GCC; see the file COPYING3. If not see
|
|
# <http://www.gnu.org/licenses/>.
|
|
|
|
# Return 1 if compilation with -fsanitize=address is error-free for trivial
|
|
# code, 0 otherwise.
|
|
#
|
|
# NOTE: This should only be used between calls to asan_init and asan_finish.
|
|
# It is therefore defined here rather than in target-supports.exp.
|
|
|
|
proc check_effective_target_fsanitize_address {} {
|
|
if ![check_no_compiler_messages fsanitize_address executable {
|
|
int main (void) { return 0; }
|
|
}] {
|
|
return 0;
|
|
}
|
|
|
|
# asan doesn't work if there's a ulimit on virtual memory.
|
|
if ![is_remote target] {
|
|
if [catch {exec sh -c "ulimit -v"} ulimit_v] {
|
|
# failed to get ulimit
|
|
} elseif [regexp {^[0-9]+$} $ulimit_v] {
|
|
# ulimit -v gave a numeric limit
|
|
warning "skipping asan tests due to ulimit -v"
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
proc asan_include_flags {} {
|
|
global srcdir
|
|
global TESTING_IN_BUILD_TREE
|
|
|
|
set flags ""
|
|
|
|
if { [is_remote host] || ! [info exists TESTING_IN_BUILD_TREE] } {
|
|
return "${flags}"
|
|
}
|
|
|
|
set flags "-I$srcdir/../../libsanitizer/include"
|
|
|
|
return "$flags"
|
|
}
|
|
|
|
#
|
|
# asan_link_flags -- compute library path and flags to find libasan.
|
|
# (originally from g++.exp)
|
|
#
|
|
|
|
proc asan_link_flags_1 { paths lib } {
|
|
global srcdir
|
|
global ld_library_path
|
|
global shlib_ext
|
|
global ${lib}_saved_library_path
|
|
|
|
set gccpath ${paths}
|
|
set flags ""
|
|
|
|
set shlib_ext [get_shlib_extension]
|
|
set ${lib}_saved_library_path $ld_library_path
|
|
|
|
if { $gccpath != "" } {
|
|
if { [file exists "${gccpath}/libsanitizer/${lib}/.libs/lib${lib}.a"]
|
|
|| [file exists "${gccpath}/libsanitizer/${lib}/.libs/lib${lib}.${shlib_ext}"] } {
|
|
append flags " -B${gccpath}/libsanitizer/ "
|
|
append flags " -B${gccpath}/libsanitizer/${lib}/ "
|
|
append flags " -L${gccpath}/libsanitizer/${lib}/.libs "
|
|
append ld_library_path ":${gccpath}/libsanitizer/${lib}/.libs"
|
|
}
|
|
} else {
|
|
global tool_root_dir
|
|
|
|
set libdir [lookfor_file ${tool_root_dir} lib${lib}]
|
|
if { $libdir != "" } {
|
|
append flags "-L${libdir} "
|
|
append ld_library_path ":${libdir}"
|
|
}
|
|
}
|
|
|
|
set_ld_library_path_env_vars
|
|
|
|
return "$flags"
|
|
}
|
|
|
|
proc asan_link_flags { paths } {
|
|
return [asan_link_flags_1 $paths asan]
|
|
}
|
|
|
|
#
|
|
# asan_init -- called at the start of each subdir of tests
|
|
#
|
|
|
|
proc asan_init { args } {
|
|
global TEST_ALWAYS_FLAGS
|
|
global ALWAYS_CXXFLAGS
|
|
global TOOL_OPTIONS
|
|
global asan_saved_TEST_ALWAYS_FLAGS
|
|
global asan_saved_ALWAYS_CXXFLAGS
|
|
|
|
set link_flags ""
|
|
if ![is_remote host] {
|
|
if [info exists TOOL_OPTIONS] {
|
|
set link_flags "[asan_link_flags [get_multilibs ${TOOL_OPTIONS}]]"
|
|
} else {
|
|
set link_flags "[asan_link_flags [get_multilibs]]"
|
|
}
|
|
}
|
|
|
|
set include_flags "[asan_include_flags]"
|
|
|
|
if [info exists TEST_ALWAYS_FLAGS] {
|
|
set asan_saved_TEST_ALWAYS_FLAGS $TEST_ALWAYS_FLAGS
|
|
}
|
|
if [info exists ALWAYS_CXXFLAGS] {
|
|
set asan_saved_ALWAYS_CXXFLAGS $ALWAYS_CXXFLAGS
|
|
set ALWAYS_CXXFLAGS [concat "{ldflags=$link_flags}" $ALWAYS_CXXFLAGS]
|
|
set ALWAYS_CXXFLAGS [concat "{additional_flags=-fsanitize=address -g $include_flags}" $ALWAYS_CXXFLAGS]
|
|
} else {
|
|
if [info exists TEST_ALWAYS_FLAGS] {
|
|
set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=address -g $include_flags $TEST_ALWAYS_FLAGS"
|
|
} else {
|
|
set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=address -g $include_flags"
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# asan_finish -- called at the start of each subdir of tests
|
|
#
|
|
|
|
proc asan_finish { args } {
|
|
global TEST_ALWAYS_FLAGS
|
|
global asan_saved_TEST_ALWAYS_FLAGS
|
|
global asan_saved_ALWAYS_CXXFLAGS
|
|
global asan_saved_library_path
|
|
global ld_library_path
|
|
|
|
if [info exists asan_saved_ALWAYS_CXXFLAGS ] {
|
|
set ALWAYS_CXXFLAGS $asan_saved_ALWAYS_CXXFLAGS
|
|
} else {
|
|
if [info exists asan_saved_TEST_ALWAYS_FLAGS] {
|
|
set TEST_ALWAYS_FLAGS $asan_saved_TEST_ALWAYS_FLAGS
|
|
} else {
|
|
unset TEST_ALWAYS_FLAGS
|
|
}
|
|
}
|
|
if [info exists asan_saved_library_path ] {
|
|
set ld_library_path $asan_saved_library_path
|
|
set_ld_library_path_env_vars
|
|
}
|
|
clear_effective_target_cache
|
|
}
|
|
|
|
# Symbolize lines like
|
|
# #2 0xdeadbeef (/some/path/libsanitizer.so.0.0.0+0xbeef)
|
|
# in $output using addr2line to
|
|
# #2 0xdeadbeef in foobar file:123
|
|
proc asan_symbolize { output } {
|
|
set addresses [regexp -inline -all -line "^ *#\[0-9\]+ 0x\[0-9a-f\]+ \[(\](\[^)\]+)\[+\](0x\[0-9a-f\]+)\[)\]$" "$output"]
|
|
if { [llength $addresses] > 0 } {
|
|
set addr2line_name [find_binutils_prog addr2line]
|
|
set idx 1
|
|
while { $idx < [llength $addresses] } {
|
|
set key [regsub -all "\[\]\[\]" [lindex $addresses $idx] "\\\\&"]
|
|
set val [lindex $addresses [expr $idx + 1]]
|
|
lappend arr($key) $val
|
|
set idx [expr $idx + 3]
|
|
}
|
|
foreach key [array names arr] {
|
|
set args "-f -e $key $arr($key)"
|
|
set status [remote_exec host "$addr2line_name" "$args"]
|
|
if { [lindex $status 0] > 0 } continue
|
|
regsub -all "\r\n" [lindex $status 1] "\n" addr2line_output
|
|
regsub -all "\[\n\r\]BFD: \[^\n\r\]*" $addr2line_output "" addr2line_output
|
|
regsub -all "^BFD: \[^\n\r\]*\[\n\r\]" $addr2line_output "" addr2line_output
|
|
set addr2line_output [regexp -inline -all -line "^\[^\n\r]*" $addr2line_output]
|
|
set idx 0
|
|
foreach val $arr($key) {
|
|
if { [expr $idx + 1] < [llength $addr2line_output] } {
|
|
set fnname [lindex $addr2line_output $idx]
|
|
set fileline [lindex $addr2line_output [expr $idx + 1]]
|
|
if { "$fnname" != "??" } {
|
|
set newkey "$key+$val"
|
|
set repl($newkey) "$fnname $fileline"
|
|
}
|
|
set idx [expr $idx + 2]
|
|
}
|
|
}
|
|
}
|
|
set idx 0
|
|
set new_output ""
|
|
while {[regexp -start $idx -indices " #\[0-9\]+ 0x\[0-9a-f\]+ \[(\](\[^)\]+\[+\]0x\[0-9a-f\]+)\[)\]" "$output" -> addr] > 0} {
|
|
set low [lindex $addr 0]
|
|
set high [lindex $addr 1]
|
|
set val [string range "$output" $low $high]
|
|
append new_output [string range "$output" $idx [expr $low - 2]]
|
|
if [info exists repl($val)] {
|
|
append new_output "in $repl($val)"
|
|
} else {
|
|
append new_output "($val)"
|
|
}
|
|
set idx [expr $high + 2]
|
|
}
|
|
append new_output [string range "$output" $idx [string length "$output"]]
|
|
return "$new_output"
|
|
}
|
|
return "$output"
|
|
}
|
|
|
|
# Return a list of gtest tests, printed in the form
|
|
# DEJAGNU_GTEST_TEST AddressSanitizer_SimpleDeathTest
|
|
# DEJAGNU_GTEST_TEST AddressSanitizer_VariousMallocsTest
|
|
proc asan_get_gtest_test_list { output } {
|
|
set idx 0
|
|
set ret ""
|
|
while {[regexp -start $idx -indices "DEJAGNU_GTEST_TEST (\[^\n\r\]*)(\r\n|\n|\r)" "$output" -> testname] > 0} {
|
|
set low [lindex $testname 0]
|
|
set high [lindex $testname 1]
|
|
set val [string range "$output" $low $high]
|
|
lappend ret $val
|
|
set idx [expr $high + 1]
|
|
}
|
|
return $ret
|
|
}
|
|
|
|
# Return a list of gtest EXPECT_DEATH tests, printed in the form
|
|
# DEJAGNU_GTEST_EXPECT_DEATH1 statement DEJAGNU_GTEST_EXPECT_DEATH1 regexp DEJAGNU_GTEST_EXPECT_DEATH1
|
|
# DEJAGNU_GTEST_EXPECT_DEATH2 other statement DEJAGNU_GTEST_EXPECT_DEATH2 other regexp DEJAGNU_GTEST_EXPECT_DEATH2
|
|
proc asan_get_gtest_expect_death_list { output } {
|
|
set idx 0
|
|
set ret ""
|
|
while {[regexp -start $idx -indices "DEJAGNU_GTEST_EXPECT_DEATH(\[0-9\]*)" "$output" -> id ] > 0} {
|
|
set low [lindex $id 0]
|
|
set high [lindex $id 1]
|
|
set val_id [string range "$output" $low $high]
|
|
if {[regexp -start $low -indices "$val_id (.*) DEJAGNU_GTEST_EXPECT_DEATH$val_id (.*) DEJAGNU_GTEST_EXPECT_DEATH$val_id\[\n\r\]" "$output" whole statement regexpr ] == 0} { break }
|
|
set low [lindex $statement 0]
|
|
set high [lindex $statement 1]
|
|
set val_statement [string range "$output" $low $high]
|
|
set low [lindex $regexpr 0]
|
|
set high [lindex $regexpr 1]
|
|
set val_regexpr [string range "$output" $low $high]
|
|
lappend ret [list "$val_id" "$val_statement" "$val_regexpr"]
|
|
set idx [lindex $whole 1]
|
|
}
|
|
return $ret
|
|
}
|
|
|
|
# Replace ${tool}_load with a wrapper so that we can symbolize the output.
|
|
if { [info procs ${tool}_load] != [list] \
|
|
&& [info procs saved_asan_${tool}_load] == [list] } {
|
|
rename ${tool}_load saved_asan_${tool}_load
|
|
|
|
proc ${tool}_load { program args } {
|
|
global tool
|
|
global asan_last_gtest_test_list
|
|
global asan_last_gtest_expect_death_list
|
|
set result [eval [list saved_asan_${tool}_load $program] $args]
|
|
set output [lindex $result 1]
|
|
set symbolized_output [asan_symbolize "$output"]
|
|
set asan_last_gtest_test_list [asan_get_gtest_test_list "$output"]
|
|
set asan_last_gtest_expect_death_list [asan_get_gtest_expect_death_list "$output"]
|
|
set result [list [lindex $result 0] $symbolized_output]
|
|
return $result
|
|
}
|
|
}
|
|
|
|
# Utility for running gtest asan emulation under dejagnu, invoked via dg-final.
|
|
# Call pass if variable has the desired value, otherwise fail.
|
|
#
|
|
# Argument 0 handles expected failures and the like
|
|
proc asan-gtest { args } {
|
|
global tool
|
|
global asan_last_gtest_test_list
|
|
global asan_last_gtest_expect_death_list
|
|
|
|
if { ![info exists asan_last_gtest_test_list] } { return }
|
|
if { [llength $asan_last_gtest_test_list] == 0 } { return }
|
|
if { ![isnative] || [is_remote target] } { return }
|
|
|
|
set gtest_test_list $asan_last_gtest_test_list
|
|
unset asan_last_gtest_test_list
|
|
|
|
if { [llength $args] >= 1 } {
|
|
switch [dg-process-target [lindex $args 0]] {
|
|
"S" { }
|
|
"N" { return }
|
|
"F" { setup_xfail "*-*-*" }
|
|
"P" { }
|
|
}
|
|
}
|
|
|
|
# This assumes that we are three frames down from dg-test, and that
|
|
# it still stores the filename of the testcase in a local variable "name".
|
|
# A cleaner solution would require a new DejaGnu release.
|
|
upvar 2 name testcase
|
|
upvar 2 prog prog
|
|
|
|
set output_file "[file rootname [file tail $prog]].exe"
|
|
|
|
foreach gtest $gtest_test_list {
|
|
set testname "$testcase $gtest"
|
|
set status -1
|
|
|
|
setenv DEJAGNU_GTEST_ARG "$gtest"
|
|
set result [${tool}_load ./$output_file $gtest]
|
|
unsetenv DEJAGNU_GTEST_ARG
|
|
set status [lindex $result 0]
|
|
set output [lindex $result 1]
|
|
if { "$status" == "pass" } {
|
|
pass "$testname execution test"
|
|
if { [info exists asan_last_gtest_expect_death_list] } {
|
|
set gtest_expect_death_list $asan_last_gtest_expect_death_list
|
|
foreach gtest_death $gtest_expect_death_list {
|
|
set id [lindex $gtest_death 0]
|
|
set testname "$testcase $gtest [lindex $gtest_death 1]"
|
|
set regexpr [lindex $gtest_death 2]
|
|
set status -1
|
|
|
|
setenv DEJAGNU_GTEST_ARG "$gtest:$id"
|
|
set result [${tool}_load ./$output_file "$gtest:$id"]
|
|
unsetenv DEJAGNU_GTEST_ARG
|
|
set status [lindex $result 0]
|
|
set output [lindex $result 1]
|
|
if { "$status" == "fail" } {
|
|
pass "$testname execution test"
|
|
if { ![regexp $regexpr ${output}] } {
|
|
fail "$testname output pattern test"
|
|
send_log "Output should match: $regexpr\n"
|
|
} else {
|
|
pass "$testname output pattern test"
|
|
}
|
|
} elseif { "$status" == "pass" } {
|
|
fail "$testname execution test"
|
|
} else {
|
|
$status "$testname execution test"
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$status "$testname execution test"
|
|
}
|
|
unset asan_last_gtest_expect_death_list
|
|
}
|
|
|
|
return
|
|
}
|