今天的 Tetralet 又在唧唧喳喳了



« 五月 2017 »
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        






 

這次的 GTK3+ 不再配備安全帶囉!

Tetralet | 26 九月, 2011 21:10

前幾天,開始有人回報GCIN 會當掉,導致所有的 GNOME3 程式、甚至所有的 GTK+3 程式全開不起來。起初我也不是很在意啦,因為我的 GCIN 都很正常,也沒在用 GNOME3/GTK3+,加上出狀況的也不是用 Debian 系統,就算想幫也無從幫起 XD。但還好有 b4283 在 #gcin@freenode.net 貼出了 gdb 資訊:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff6db8e10 in gdk_screen_get_display () from /usr/lib/libgdk-3.so.0
(gdb) bt
#0  0x00007ffff6db8e10 in gdk_screen_get_display () from /usr/lib/libgdk-3.so.0
#1  0x00007fffec329afe in get_im (context_xim=0xaccc50) at gtkimcontextgcin.c:130
#2  0x00007fffec32a640 in gtk_im_context_gcin_set_cursor_location (context=0xaccc50, area=0x7fffffffd9d0) at gtkimcontextgcin.c:512
#3  0x00007ffff70e27d9 in ?? () from /usr/lib/libgtk-3.so.0
#4  0x00007ffff6dabd1f in ?? () from /usr/lib/libgdk-3.so.0
#5  0x00007ffff572429d in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0
#6  0x00007ffff5724a78 in ?? () from /usr/lib/libglib-2.0.so.0
#7  0x00007ffff57250ba in g_main_loop_run () from /usr/lib/libglib-2.0.so.0
#8  0x00007ffff7151fbd in gtk_main () from /usr/lib/libgtk-3.so.0
#9  0x000000000041a907 in main ()

唔,看一下原始碼就可以猜到十之八九是傳到 NULL 值的問題嘛,應該不太難修。但亂寫了幾個 patch,請 b4283 測試結果,卻發現怎麼樣都不得要領:死的地方非常奇怪。該檢查的地方都檢查了,可能出錯的地方也都一一避開了,還是一樣會當掉。直到 b4283 自己找到了問題點:

static void
get_im (GtkIMContextGCIN *context_xim)
{
  GdkWindow *client_window = context_xim->client_window;
  GdkScreen *screen = gdk_window_get_screen (client_window);
  if (screen == NULL) return;
  GdkDisplay *display = gdk_screen_get_display (screen);
才讓人驚覺到:不,問題看來很嚴重!如果猜得沒錯,出問題的很可能是 GTK3+,而不是 GCIN!去翻了 GTK3+ 的原始碼來看,果然!
(正確的說,是從 GTK+ 2.91.7 開始)
GdkDisplay *gdk_screen_get_display (GdkScreen *screen)
{
  return GDK_SCREEN_GET_CLASS(screen)->get_display (screen);
}

天呀!GTK3+ 竟然沒有檢查傳入值就直接 return()!難怪 GCIN 會當在那麼奇怪的地方!對照一下 GTK2+ 的程式碼:
GdkDisplay * gdk_screen_get_display (GdkScreen *screen)
{
  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

  return GDK_SCREEN_X11 (screen)->display;
}
GTK3+ 也未免太惡搞了!誰能告訴我為什麼要把那麼重要的檢查機制拿掉呢?不檢查輸入值是不是 GdkScreen 就回傳會不會太冒險了?那不是等同埋了一顆不定時炸彈?而明明原本 GTK+2 裡都是好好的呀?我再花了一點時間追了一下,GTK3+ 裡約有 100 個函數是不檢查輸入值就直接 return() 的:(以下完整列表。我沒辨法一一檢查每個函式。很可能掛一漏萬!)
gdk_color_hash()
gdk_color_to_css()
gdk_display_manager_get_default_display()
gdk_display_manager_list_displays()
gdk_display_manager_open_display()
gdk_drag_begin_for_device()
gdk_keys_name_compare()
gdk_property_get()
gdk_quartz_display_manager_get_default_display()
gdk_quartz_screen_get_height()
gdk_quartz_screen_get_height_mm()
gdk_quartz_screen_get_monitor_height_mm()
gdk_quartz_screen_get_monitor_width_mm()
gdk_quartz_screen_get_n_monitors()
gdk_quartz_screen_get_width()
gdk_quartz_screen_get_width_mm()
gdk_rgba_to_string()
gdk_screen_get_active_window()
gdk_screen_get_display()
gdk_screen_get_height()
gdk_screen_get_height_mm()
gdk_screen_get_monitor_height_mm()
gdk_screen_get_monitor_plug_name()
gdk_screen_get_monitor_width_mm()
gdk_screen_get_n_monitors()
gdk_screen_get_number()
gdk_screen_get_primary_monitor()
gdk_screen_get_rgba_visual()
gdk_screen_get_root_window()
gdk_screen_get_setting()
gdk_screen_get_system_visual()
gdk_screen_get_width()
gdk_screen_get_width_mm()
gdk_screen_get_window_stack()
gdk_screen_is_composited()
gdk_screen_list_visuals()
gdk_screen_make_display_name()
gdk_test_render_sync()
gdk_test_simulate_key()
gdk_visual_equal()
gdk_visual_hash()
gdk_win32_drag_context_drop_status()
gdk_win32_screen_get_height()
gdk_win32_screen_get_width()
gdk_window_get_decorations()
gdk_window_get_group()
gdk_window_get_impl_window()
gdk_window_get_type_hint()
gdk_window_has_impl()
gdk_window_has_no_impl()
gdk_window_is_offscreen()
gdk_window_is_toplevel()
gdk_window_ref_impl_surface()
gdk_x11_display_get_default_screen()
gdk_x11_display_get_n_screens()
gdk_x11_display_get_name()
gdk_x11_display_get_startup_notification_id()
gdk_x11_display_get_user_time()
gdk_x11_display_manager_get_default_display()
gdk_x11_display_manager_list_displays()
gdk_x11_display_supports_input_shapes()
gdk_x11_display_supports_shapes()
gdk_x11_drag_context_drop_status()
gdk_x11_screen_get_screen_number()
gdk_x11_screen_get_xscreen()
gtk_app_chooser_get_app_info()
gtk_assistant_buildable_custom_tag_start()
gtk_color_button_has_alpha()
gtk_cups_request_get_poll_state()
gtk_cups_request_get_result()
gtk_cups_request_is_done()
gtk_cups_result_get_error_code()
gtk_cups_result_get_error_status()
gtk_cups_result_get_error_string()
gtk_cups_result_get_error_type()
gtk_cups_result_get_response()
gtk_cups_result_is_error()
gtk_entry_buffer_normal_get_length()
gtk_file_filter_get_needed()
gtk_icon_view_accessible_item_compare()
gtk_im_context_thai_get_isc_mode()
gtk_notebook_page_compare()
gtk_notebook_page_compare_tab()
gtk_page_setup_get_orientation()
gtk_paper_size_is_custom()
gtk_print_job_get_collate()
gtk_print_job_get_n_up()
gtk_print_job_get_n_up_layout()
gtk_print_job_get_num_copies()
gtk_print_job_get_page_set()
gtk_print_job_get_pages()
gtk_print_job_get_reverse()
gtk_print_job_get_rotate()
gtk_print_job_get_scale()
gtk_print_settings_get()
gtk_printer_cups_get_ppd()
gtk_printer_option_widget_get_external_label()
gtk_printer_option_widget_has_external_label()
gtk_rbtree_reorder_invert_func()
gtk_recent_action_get_recent_manager()
gtk_recent_chooser_default_get_recent_manager()
gtk_recent_filter_get_needed()
gtk_scale_button_press()
gtk_size_group_get_widgets()
gtk_text_layout_get_cursor_visible()
gtk_tool_item_group_get_animation_timestamp()
gtk_tool_item_group_get_ellipsize_mode()
gtk_tool_shell_get_icon_size()
gtk_tool_shell_get_orientation()
gtk_tool_shell_get_style()
gtk_tree_model_filter_real_visible()
gtk_tree_model_filter_visible()
gtk_tree_row_reference_copy()
gtk_tree_view_get_fixed_height_mode()
gtk_tree_view_get_hover_expand()
gtk_tree_view_get_hover_selection()
gtk_tree_view_get_rubber_banding()
gtk_widget_in_destruction()

個人建議如果您有用到以上函數的,如果有必要請自行加上檢查機制。畢竟程式會 segfault 可是程式設計師之恥!XD

寫到這裡,也許有人會說,檢查輸入值應該是 GCIN 的工作呀,明明是 GCIN 傳遞了錯誤的 NULL 值給 GTK3+,怎麼可以反過來說是 GTK3+ 的錯呢?

是的,『盡一切努力寫出完美無缺的程式碼』是每一個程式設計師的職責。但現實況狀卻是,在程式執行其間還是會有太多的突發狀況無法掌握、難以預料,甚至很多狀況是程式設計師根本沒有預想到的,所以不管怎麼樣,多一道安全機制不會是壞事。像這次 GCIN 會當掉的原因我猜是 GCIN 在初始化期間GCIN 的視窗還沒有顯示出來之前)卻執行到了那個函式所引發出來的意想不到結果。好了,GCIN 沒料想到、GTK3+ 也不檢查,結果卻是所有 GNOME3/GTK+3 程式都打不開,這可不是使用者所樂見的結果!

且個人認為,真正的問題點在於 GTK3+ 它是個 library。不管呼叫 GTK3+ 的應用程式如何惡搞,GTK3+ 應該要有一些自我防衛的機制防止應用程式不會莫名其妙就 segfault,更何況嚴格來說 NULL 值也不能算是個錯誤的值!而當 GTK3+ 偷懶不檢查輸入值之時,相反得就變成應用程式得要自行檢查。但為了保險起見,就變成了應用程式要每一個函數都檢查以確保所有的函式輸入輸出值都是完全正確的。畢竟只要傳錯值就是 segfault,像在上例中,傳的值如果不是 GdkScreen 甚至是 NULL 就很可能會當掉,這當然是件很可怕的事!而在 GTK2+ 裡,相同的狀況可能只是在 console 下印出個警告訊息就了事,雖然這也讓人很不快,但總比 GTK3+ 的 segfault 好。個人在測試新軟體時,只要當掉超過 3 次就會棄而不用:誰能忍受一天到晚當掉的程式呢?那不是隨時都要小心注意以免做到一半的事付諸流水?而 GTK3+ 的新做法將導致 segfautl 狀況更加頻繁。這可是場不折不扣的大災難!!

再舉個小例子,原本 GTK2+ 單純的如下程式(若 WM 有支援 composite,則將視窗設定為支援 opacity,有故意簡化,當然不是好範例 XD)

if (gdk_screen_is_composited(gtk_widget_get_screen(window)))
        gtk_widget_set_visual (GTK_WIDGET (window),
                               gdk_screen_get_rgba_visual(gtk_widget_get_screen(window)));

GTK3+ 裡,為了避免萬一傳到 NULL 值可能會導致 segfault,所以只好每拿到一個變數就檢查一次,於是程式碼一下子就要變成:

if (window != NULL)
{
        GdkScreen *screen = gtk_widget_get_screen(window);
        if (screen
!= NULL)
        {
                if (gdk_screen_is_composited(screen))
                {
                        GdkVisual *visual = gdk_screen_get_rgba_visual (screen);
                        if (visual
!= NULL) gtk_widget_set_visual (GTK_WIDGET (window), visual);
                }
        }
}

惡夢嗎?是的,個人認為這可真是場大惡夢!

迴響

隂謀論...

這些 function 是不是有意把 Gtk+ 搞爛的人種的啊

[回應] 大學內民明丸 @ 27/09/2011, 00:41

Re: 這次的 GTK3+ 不再配備安全帶囉!

Dear Tetralet,

貴站的文章都寫的極好,但下面該文的圖都失效了,不知道方便修復嗎?
http://tetralet.luna.com.tw/index.php?op=ViewArticle&articleId=200&blogId=1

感謝不盡

[回應] Sept @ 22/11/2011, 17:46

Re: 這次的 GTK3+ 不再配備安全帶囉!

從early fail的觀點來說,我不認為應該由GTK3來檢查。
GTK3是個library,所以他處在被動的地位,如果他發現了錯誤,除了印錯誤訊息之外也不能做什麼。
另外一再重複這些檢查也是一種浪費。

而且自己的code在有例外的情形下盲目的執行下去,不也是一種惡夢嗎?

[回應] buganini @ 18/12/2011, 23:34

Re: 這次的 GTK3+ 不再配備安全帶囉!

我的觀點是,寫程式,儘可能的對輸入輸出數值做個確認,而且反而比較同意 Tetralet說的值檢查,
因為以安全的原則而言,大部份的人都不會比「維護這個函式的人」了解是不是「例外」!
更何況,軟體的架構,向來都是「老鼠會」,上層沒做好,下層的會怨個半死!

在此分享一下,我習慣function 回傳值是拿來分類函式執行結果狀態的,
0代表「未知異常」,1以上為「已知成功」,-1以下代表「已知錯誤」,而且函式中只有一個return,
這種方式就算後來考慮偷懶不檢查也不需大修改,此外如果真的在乎效能,覺得條件式太多用#define 區隔就好。
不過,連我這個寫軔體都覺得不需那麼計較,因為忘記釋放的記憶體絕對比它多。

[回應] 夜白月 @ 28/05/2013, 20:47

Re: 夜白月

個人也是認為,傳入 NULL 值就會 segfault 的外部函式,老實說,是不及格的...

[回應] Tetralet @ 29/05/2013, 10:55

這算不算是 upstream 的 bug 呢?

我想知道這算是 upstream 的 bug(或者叫疏忽)呢,還是故意的?
請問你有上 bug tracker 問這件事嗎?

[回應] Star Brilliant @ 05/06/2013, 20:17

Re: 這算不算是 upstream 的 bug 呢?

個人認為應該算是 bug。

但個人並沒有去回報... Sorry

[回應] tetralet @ 11/06/2013, 17:21

authimage
驗證碼皆為英文大寫字母
僅輸入前4碼即可。後2碼是假的,欺敵用。
這是為了防制 Spam 而設計的。若造成您的不便還請見諒!
Accessible and Valid XHTML 1.0 Strict and CSS
Powered by LifeType - Design by BalearWeb