| # 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 |
| } |