1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
#!/usr/bin/ruby
require_relative 'lib/colorize'
require 'shellwords'
until ARGV.empty?
case ARGV[0]
when /\A SYMBOL_PREFIX=(.*)/x
SYMBOL_PREFIX = $1
when /\A NM=(.*)/x # may be multiple words
NM = $1.shellsplit
when /\A PLATFORM=(.+)?/x
platform = $1
when /\A SOEXT=(.+)?/x
soext = $1
when /\A SYMBOLS_IN_EMPTYLIB=(.*)/x
SYMBOLS_IN_EMPTYLIB = $1.split(" ")
when /\A EXTSTATIC=(.+)?/x
EXTSTATIC = true
else
break
end
ARGV.shift
end
SYMBOLS_IN_EMPTYLIB ||= nil
EXTSTATIC ||= false
config = ARGV.shift
count = 0
col = Colorize.new
config_code = File.read(config)
REPLACE = config_code.scan(/\bAC_(?:REPLACE|CHECK)_FUNCS?\((\w+)/).flatten
# REPLACE << 'memcmp' if /\bAC_FUNC_MEMCMP\b/ =~ config_code
REPLACE.push('main', 'DllMain')
if platform and !platform.empty?
begin
h = File.read(platform)
rescue Errno::ENOENT
else
REPLACE.concat(
h .gsub(%r[/\*.*?\*/]m, " ") # delete block comments
.gsub(%r[//.*], " ") # delete oneline comments
.gsub(/^\s*#.*(?:\\\n.*)*/, "") # delete preprocessor directives
.gsub(/(?:\A|;)\K\s*typedef\s.*?;/m, "")
.scan(/\b((?!rb_|DEPRECATED|_)\w+)\s*\(.*\);/)
.flatten)
end
end
missing = File.dirname(config) + "/missing/"
ARGV.reject! do |n|
base = File.basename(n, ".*")
next true if REPLACE.include?(base)
unless (src = Dir.glob(missing + base + ".[cS]")).empty?
puts "Ignore #{col.skip(n)} because of #{src.map {|s| File.basename(s)}.join(', ')} under missing"
true
end
end
# darwin's ld64 seems to require exception handling personality functions to be
# extern, so we allow the Rust one.
REPLACE.push("rust_eh_personality") if RUBY_PLATFORM.include?("darwin")
print "Checking leaked global symbols..."
STDOUT.flush
if soext
soext = /\.#{soext}(?:$|\.)/
if EXTSTATIC
ARGV.delete_if {|n| soext =~ n}
elsif ARGV.size == 1
so = soext =~ ARGV.first
end
end
Pipe = Struct.new(:command) do
def open(&block) IO.popen(command, &block) end
def each(&block) open {|f| f.each(&block)} end
end
Pipe.new(NM + ARGV).each do |line|
line.chomp!
next so = nil if line.empty?
if so.nil? and line.chomp!(":")
so = soext =~ line || false
next
end
n, t, = line.split
next unless /[A-TV-Z]/ =~ t
next unless n.sub!(/^#{SYMBOL_PREFIX}/o, "")
next if n.include?(".")
next if !so and n.start_with?("___asan_")
next if !so and n.start_with?("__odr_asan_")
next if !so and n.start_with?("__retguard_")
next if !so and n.start_with?("__dtrace")
case n
when /\A(?:Init_|InitVM_|pm_|[Oo]nig|dln_|coroutine_)/
next
when /\Aruby_static_id_/
next unless so
when /\A(?:RUBY_|ruby_|rb_)/
next unless so and /_(threadptr|ec)_/ =~ n
when *SYMBOLS_IN_EMPTYLIB
next
end
next if REPLACE.include?(n)
puts col.fail("leaked") if count.zero?
count += 1
puts " #{n}"
end
case count
when 0
puts col.pass("none")
when 1
abort col.fail("1 un-prefixed symbol leaked")
else
abort col.fail("#{count} un-prefixed symbols leaked")
end
|