summaryrefslogtreecommitdiff
path: root/tool/leaked-globals
blob: b80ae3907376edd4ff29751f275ab2b9ce39e427 (plain)
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
#!/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_")
  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