設(shè)置
  • 日夜間
    隨系統(tǒng)
    淺色
    深色
  • 主題色

運(yùn)行個 Hello World 也能出 Bug?Python、Java、C++ 等 16 種語言中槍,最嚴(yán)重可導(dǎo)致文件丟失

量子位 2022/3/21 15:34:16 責(zé)編:汪淼

一句最簡單的 Hello World,居然也會出 Bug?

倒不是這句代碼還能寫錯,而是運(yùn)行時找到了許多操作系統(tǒng)對異常處理的漏洞。

在向 /dev/full 輸出結(jié)果,也就是設(shè)備空間不足、任何寫入都應(yīng)失敗的情況下,C 語言依然返回了 0,成功退出:

$ gcc hello.c -o hello
$ ./hello > /dev/full
$ echo $?
0

Bug 的最初發(fā)現(xiàn)者表示:這可不是一個小錯誤,本質(zhì)上是“打印到標(biāo)準(zhǔn)輸出”的任務(wù)。

發(fā)生了錯誤但不拋出異常,意味著即使出現(xiàn)數(shù)據(jù)丟失,進(jìn)程依然會繼續(xù)運(yùn)行。

于是他一不做二不休,又測試了 C++、Python、Java 等熱門語言,發(fā)了篇博客,很快就在論壇蓋起了高樓,討論度直接爆了:

而評論區(qū)網(wǎng)友一通 Debug,綜合整理下來,踩中這一 Bug 的語言,竟足足有 16 種之多!

Hello World 的 DeBug 過程

最初的發(fā)現(xiàn)者是一名名叫 sunfishcode 的技術(shù)博主,他在博客里展示了 C 和 Python 兩種語言的詳細(xì)的 deBug 過程。

主要使用的是 Linux 系統(tǒng)下的一個經(jīng)典的設(shè)備文件,/dev/ full。

/dev/ full 總是在寫入時返回設(shè)備無剩余空間(錯誤碼為 ENOSPC),常常用于測試程序能否正確處理 I / O 錯誤。

如果程序正常,那么就會返回錯誤報告:

$ echo "Hello World!" > /dev/full
bash: echo: write error: No space left on device
$ echo $?
1

而正如我們開頭所示的代碼,在用 C 語言進(jìn)行輸出時,hello 程序卻報告成功,返回了 0。

用 strace 命令跟蹤這一進(jìn)程產(chǎn)生的系統(tǒng)調(diào)用可以發(fā)現(xiàn),程序確實出現(xiàn)了故障:

$ strace -etrace=write ./hello > /dev/full
write(1, "Hello World!\n", 13)          = -1 ENOSPC (No space left on device)
+++ exited with 0 +++

而以“錯誤不該被悄悄傳遞”為口號的 Python 也著了道。

程序向 stderr 打印了一條消息,丟失了信息,但最后也返回了 0:

$ python2 hello.py > /dev/full
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
$ echo $?
0

這個 Bug 嚴(yán)重嗎?現(xiàn)實世界任何一個程序都不會拿 Hello World 當(dāng)作關(guān)鍵性安全問題,但“打印到標(biāo)準(zhǔn)輸出”卻是現(xiàn)實中確實會有的程序任務(wù)。

而這也正是 Hello World 這個最簡單的程序的本質(zhì)。

博主 sunfishcode 這樣說:

標(biāo)準(zhǔn)輸出可能意味著一個具體文件,那么如果這個文件剛好耗盡了空間,程序又因為 Bug 沒有檢測到這一錯誤呢?

父進(jìn)程不會知道子進(jìn)程失敗了,只會繼續(xù)運(yùn)行。但期望生成的輸出實際上已經(jīng)丟失了數(shù)據(jù)。

當(dāng)然,博主在最后也給出了沒有踩雷的語言列表:

網(wǎng)友熱議:這到底算不算 Bug?

目前,博主已經(jīng)針對這一 Bug 給出了一些解決方案,比如在 C 語言環(huán)境中可以采用這樣的方法:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("Hello, World!\n");

    if (fflush(stdout) != 0 || ferror(stdout) != 0) {
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

而評論區(qū)也貢獻(xiàn)了 Java 環(huán)境中的解決方案,即添加一個方法來獲得底層的、未包裝的 OutputStream:

System.out.println("Hello World!");
    if (System.out.checkError()) throw new IOException();

下方還有人補(bǔ)充到,Java 已經(jīng)引入的 RuntimeIOException 就可以用于 I / O 異常出現(xiàn)意外的情況:

因此我們可以引入一個新的類,比如 ErrorCheckingPrintStream,并將“ErrorCheckingPrintStream withErrorChecks ()”方法添加到 PrintStream 中。

而除此之外,評論區(qū)熱議的一個話題就是:

這位博主所公布的問題到底算不算是一個 Bug?

反對者直言作者是在標(biāo)題黨,還以為是發(fā)現(xiàn)了什么 C 語言標(biāo)準(zhǔn)庫里的 Bug,但實際上只是處理所有可能的系統(tǒng)調(diào)用的失敗情況:

Hello World 只是簡單地將 API 調(diào)用到文本界面,對一個簡單的接口進(jìn)行調(diào)用,我在那里沒有發(fā)現(xiàn)過任何 Bug。

有贊同的評論在下方做了進(jìn)一步的補(bǔ)充,他認(rèn)為 C 語言的編寫方式里本來就寫明:程序不關(guān)心任何形式的錯誤條件。

包括 printf 的返回值被忽略、輸出不被刷新、刷新的返回不被檢查、不關(guān)心 errno 值等等。

所以,用戶本就不應(yīng)該期望給定的系統(tǒng)調(diào)用返回額外的 errno 值,而是應(yīng)該用特殊方法處理特殊情況。

甚至有人表示:程序的失敗不是由程序控制結(jié)構(gòu)定義,而是由需求定義,Hello World 程序的需求難道包括主機(jī)系統(tǒng)的所有錯誤邊界嗎?

也有人更贊同作者,認(rèn)為 Hello World 不只是接口調(diào)用,實際是在要求操作系統(tǒng)在某處寫入數(shù)據(jù),而這正是簡單的程序與現(xiàn)實世界相關(guān)聯(lián)的地方:

這是一個嚴(yán)重的問題,而似乎在大多數(shù)時候,這種看似簡單的功能中存在的大量復(fù)雜性都被忽略了。

還有另辟蹊徑,從教育的角度來看的評論:

畢竟 C 語言時很多程序員的入門語言,hello.c 又是其中的第一個程序,要讓初學(xué)者更好地理解控制結(jié)構(gòu),塊,返回值,緩沖流的,printf 格式化語言等概念,所以還是把它當(dāng)成一個 Bug 吧。

那么你又怎么看?

參考鏈接:

[1]https://blog.sunfishcode.online/Bugs-in-hello-world/

[2]https://news.ycombinator.com/item?id=30611367

[3]https://github.com/sunfishcode/hello-world-vs-io-errors

廣告聲明:文內(nèi)含有的對外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。

相關(guān)文章

關(guān)鍵詞:Hello World,代碼,Bug

軟媒旗下網(wǎng)站: IT之家 最會買 - 返利返現(xiàn)優(yōu)惠券 iPhone之家 Win7之家 Win10之家 Win11之家

軟媒旗下軟件: 軟媒手機(jī)APP應(yīng)用 魔方 最會買 要知