1# frozen_string_literal: true
2begin
3  require_relative 'helper'
4rescue LoadError
5end
6
7module Fiddle
8  class TestHandle < TestCase
9    include Fiddle
10
11    def test_safe_handle_open
12      Thread.new do
13        $SAFE = 1
14        assert_raise(SecurityError) {
15          Fiddle::Handle.new(LIBC_SO.dup.taint)
16        }
17      end.join
18    ensure
19      $SAFE = 0
20    end
21
22    def test_safe_function_lookup
23      Thread.new do
24        h = Fiddle::Handle.new(LIBC_SO)
25        $SAFE = 1
26        assert_raise(SecurityError) {
27          h["qsort".dup.taint]
28        }
29      end.join
30    ensure
31      $SAFE = 0
32    end
33
34    def test_to_i
35      handle = Fiddle::Handle.new(LIBC_SO)
36      assert_kind_of Integer, handle.to_i
37    end
38
39    def test_static_sym_unknown
40      assert_raise(DLError) { Fiddle::Handle.sym('fooo') }
41      assert_raise(DLError) { Fiddle::Handle['fooo'] }
42    end
43
44    def test_static_sym
45      begin
46        # Linux / Darwin / FreeBSD
47        refute_nil Fiddle::Handle.sym('dlopen')
48        assert_equal Fiddle::Handle.sym('dlopen'), Fiddle::Handle['dlopen']
49        return
50      rescue
51      end
52
53      begin
54        # NetBSD
55        require '-test-/dln/empty'
56        refute_nil Fiddle::Handle.sym('Init_empty')
57        assert_equal Fiddle::Handle.sym('Init_empty'), Fiddle::Handle['Init_empty']
58        return
59      rescue
60      end
61    end unless /mswin|mingw/ =~ RUBY_PLATFORM
62
63    def test_sym_closed_handle
64      handle = Fiddle::Handle.new(LIBC_SO)
65      handle.close
66      assert_raise(DLError) { handle.sym("calloc") }
67      assert_raise(DLError) { handle["calloc"] }
68    end
69
70    def test_sym_unknown
71      handle = Fiddle::Handle.new(LIBC_SO)
72      assert_raise(DLError) { handle.sym('fooo') }
73      assert_raise(DLError) { handle['fooo'] }
74    end
75
76    def test_sym_with_bad_args
77      handle = Handle.new(LIBC_SO)
78      assert_raise(TypeError) { handle.sym(nil) }
79      assert_raise(TypeError) { handle[nil] }
80    end
81
82    def test_sym
83      handle = Handle.new(LIBC_SO)
84      refute_nil handle.sym('calloc')
85      refute_nil handle['calloc']
86    end
87
88    def test_handle_close
89      handle = Handle.new(LIBC_SO)
90      assert_equal 0, handle.close
91    end
92
93    def test_handle_close_twice
94      handle = Handle.new(LIBC_SO)
95      handle.close
96      assert_raise(DLError) do
97        handle.close
98      end
99    end
100
101    def test_dlopen_returns_handle
102      assert_instance_of Handle, dlopen(LIBC_SO)
103    end
104
105    def test_initialize_noargs
106      handle = Handle.new
107      refute_nil handle['rb_str_new']
108    end
109
110    def test_initialize_flags
111      handle = Handle.new(LIBC_SO, RTLD_LAZY | RTLD_GLOBAL)
112      refute_nil handle['calloc']
113    end
114
115    def test_enable_close
116      handle = Handle.new(LIBC_SO)
117      assert !handle.close_enabled?, 'close is enabled'
118
119      handle.enable_close
120      assert handle.close_enabled?, 'close is not enabled'
121    end
122
123    def test_disable_close
124      handle = Handle.new(LIBC_SO)
125
126      handle.enable_close
127      assert handle.close_enabled?, 'close is enabled'
128      handle.disable_close
129      assert !handle.close_enabled?, 'close is enabled'
130    end
131
132    def test_NEXT
133      begin
134        # Linux / Darwin
135        #
136        # There are two special pseudo-handles, RTLD_DEFAULT and RTLD_NEXT.  The  former  will  find
137        # the  first  occurrence  of the desired symbol using the default library search order.  The
138        # latter will find the next occurrence of a function in the search order after  the  current
139        # library.   This  allows  one  to  provide  a  wrapper  around a function in another shared
140        # library.
141        # --- Ubuntu Linux 8.04 dlsym(3)
142        handle = Handle::NEXT
143        refute_nil handle['malloc']
144        return
145      rescue
146      end
147
148      begin
149        # BSD
150        #
151        # If dlsym() is called with the special handle RTLD_NEXT, then the search
152        # for the symbol is limited to the shared objects which were loaded after
153        # the one issuing the call to dlsym().  Thus, if the function is called
154        # from the main program, all the shared libraries are searched.  If it is
155        # called from a shared library, all subsequent shared libraries are
156        # searched.  RTLD_NEXT is useful for implementing wrappers around library
157        # functions.  For example, a wrapper function getpid() could access the
158        # "real" getpid() with dlsym(RTLD_NEXT, "getpid").  (Actually, the dlfunc()
159        # interface, below, should be used, since getpid() is a function and not a
160        # data object.)
161        # --- FreeBSD 8.0 dlsym(3)
162        require '-test-/dln/empty'
163        handle = Handle::NEXT
164        refute_nil handle['Init_empty']
165        return
166      rescue
167      end
168    end unless /mswin|mingw/ =~ RUBY_PLATFORM
169
170    def test_DEFAULT
171      handle = Handle::DEFAULT
172      refute_nil handle['malloc']
173    end unless /mswin|mingw/ =~ RUBY_PLATFORM
174
175    def test_dlerror
176      # FreeBSD (at least 7.2 to 7.2) calls nsdispatch(3) when it calls
177      # getaddrinfo(3). And nsdispatch(3) doesn't call dlerror(3) even if
178      # it calls _nss_cache_cycle_prevention_function with dlsym(3).
179      # So our Fiddle::Handle#sym must call dlerror(3) before call dlsym.
180      # In general uses of dlerror(3) should call it before use it.
181      require 'socket'
182      Socket.gethostbyname("localhost")
183      Fiddle.dlopen("/lib/libc.so.7").sym('strcpy')
184    end if /freebsd/=~ RUBY_PLATFORM
185
186    def test_no_memory_leak
187      assert_no_memory_leak(%w[-W0 -rfiddle.so], '', '100_000.times {Fiddle::Handle.allocate}; GC.start', rss: true)
188    end
189
190    if /cygwin|mingw|mswin/ =~ RUBY_PLATFORM
191      def test_fallback_to_ansi
192        k = Fiddle::Handle.new("kernel32.dll")
193        ansi = k["GetFileAttributesA"]
194        assert_equal(ansi, k["GetFileAttributes"], "should fallback to ANSI version")
195      end
196    end
197  end
198end if defined?(Fiddle)
199