檢視 Clang Static Analyzer 的 ExplodedGraph

之前一直好奇爲何 Clang Static Analyzer (CSA) 中 dump 函式都是以 JSON 的形式輸出的,今天纔發現原來是與檢視 ExplodedGraph 相關。

通過 dump.ViewExplodedGraph 這個 checker 可以將當前被分析程式碼的 ExplodedGraph 以 dot 檔案的形式報存下來。但其中的每一個 ExplodedNode 都依然是 JSON 格式的字串。如果需要清晰展現其中的內容,則需要 exploded-graph-rewrite 工具來對這份 dot 檔案進行處理。

該工具位於 LLVM 項目下的 ./clang/utils/analyzer/exploded-graph-rewriter.py 位置。使用該工具可以將剛剛生成的 ExplodedGraph 的 dot 檔案轉換爲對應的 SVG 檔案(其中使用了 GraphViz 負責格式轉換),並同時對每一個 ExplodedNode 中的 JSON 字串進行解析並生成對應的表格化展示,以方便查看。

處理前的 ExplodedNode(JSON 格式):

{ "state_id": 135,
  "program_points": [
    { "kind": "Statement", "stmt_kind": "DeclRefExpr", "stmt_id": 627, "pointer": "0x5556f11554b8", "pretty": "x", "location": { "line": 3, "column": 20, "file": "temp.cpp" }, "stmt_point_kind": "PostLValue", "tag": null, "node_id": 8, "is_sink": 0, "has_report": 0 },
    { "kind": "Statement", "stmt_kind": "DeclRefExpr", "stmt_id": 627, "pointer": "0x5556f11554b8", "pretty": "x", "location": { "line": 3, "column": 20, "file": "temp.cpp" }, "stmt_point_kind": "PostStmt", "tag": null, "node_id": 9, "is_sink": 0, "has_report": 0 }
  ],
  "program_state": {
    "store": { "pointer": "0x5556f1178008", "items": [
      { "cluster": "x", "pointer": "0x5556f1177f70", "items": [
        { "kind": "Direct", "offset": 0, "value": "0 S32b" }
      ]}
    ]},
    "environment": { "pointer": "0x5556f11776d0", "items": [
      { "lctx_id": 1, "location_context": "#0 Call", "calling": "f", "location": null, "items": [
        { "stmt_id": 627, "pretty": "x", "value": "&x" }
      ]}
    ]},
    "constraints": null,
    "equivalence_classes": null,
    "disequality_info": null,
    "dynamic_types": null,
    "dynamic_casts": null,
    "constructing_objects": null,
    "checker_messages": null
  }
}

處理後的同一個 ExplodedNode:

ExplodedNode after being handled

Commit Phabricator Revision to GitHub

Summarized workflow from LLVM documentation. Just keep this as a checklist for future commits. In this passage, I will use D102669 as an example to show the entire commit procedure. Since the commit is done on the patch branch. Let’s start by checking out the branch.

1. Check out the patch branch.

> git checkout D102669

2. Update commit message. Go to the Phabricator page of D102669. Copy the revision title as the header of the commit message. Then, on the option-menu to the right, select Edit Revision. Copy the revision summary as the body of the commit message. Remember to add a blank line after the header. Finally, add the revision link as the last line.

Go to the Phabricator page of D102669. On the option menu to the right, select Edit Revision.

3. Rebase the patch onto the latest commit on GitHub and then rerun the test cases locally.

> git pull --rebase origin main
> ninja check-clang-analysis

4. Re-check the list of commits to be pushed.

> git log origin/main...HEAD

5. Push the commit.

git push origin HEAD:main

As a first-time committer, the push operation was rejected twice before it was finally committed. First, GitHub requires the git push operation cannot be authenticated via username and password. Therefore, I replaced the remote link with the SSH link to solve the problem.

Username for 'https://github.com': Snape3058 
Password for 'https://[email protected]': 
remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead. 
remote: Please see https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/ for more information. 
fatal: Authentication failed for 'https://github.com/llvm/llvm-project.git/'

Second, when you set your Emails private, GitHub will reject the pushing requirement if the operation will leak your Email address. Therefore, you need to uncheck the option to allow this operation.

remote: Resolving deltas: 100% (21/21), completed with 21 local objects.                                                                                                                                             
remote: error: GH007: Your push would publish a private email address.                                                                                                                                               
remote: You can make your email public or disable this protection by visiting:                                                                                                                                       
remote: http://github.com/settings/emails                                                                                                                                                                            
To github.com:llvm/llvm-project.git                                                                                                                                                                                  
 ! [remote rejected]           HEAD -> main (push declined due to email privacy restrictions)                                                                                                                        
error: failed to push some refs to '[email protected]:llvm/llvm-project.git'

That’s all for my first commit to GitHub for the LLVM project. Hope it would be helpful to my future commits and other contributors.

References:

1. https://llvm.org/docs/DeveloperPolicy.html#commit-messages
2. https://www.llvm.org/docs/Phabricator.html#committing-a-change
3. https://www.llvm.org/docs/GettingStarted.html#commit-from-git

鯨魚背上的大貓咪——在 Linux 電腦上通過 Docker 來運行 Mac OS X

鯨魚,即是 Docker。而 Mac OS X,因其之前使用大型貓咪來命名,故而稱其爲大貓咪。在 Linux 電腦上通過 Docker 來運行 Mac OS X,自然也就是鯨魚背上的大貓咪了。——題記

在處理 D102669 時,遇到了一個僅在 Mac 上纔會觸發的 bug。而手上除了豹豹有一臺公司的 MBP 外,整個家族也就再無觸手可即的 Mac 了。雖說是公司的電腦,但畢竟是豹豹的,所以也就不好意思拿來編譯和 debug。再加上一直以來在 OS X 上都沒有搞掂 debug 相關的事情,於是便萌生了自己來弄一個 Mac 電腦的想法。

作爲一個窮鬼書生,買是買不起的,畢竟家父不是國王。於是,便思考起左道旁門來。比如:進到一家蘋果店裏,把展出的 Mac 電腦連到實驗室的伺服器上開一個反向代理,然後白嫖人家的展示機。不過最後還是鑑於良心上的譴責以及確實登錄會存在問題而放棄了這個方案。(好孩子不可以學我這樣做哦)

因此,看起來比較切實可行的方案就只剩下了黑蘋果和虛擬機。在一次偶然的搜索時,發現了這樣一個項目:Docker-OSX,於是瞬間豁然開朗。這個項目把 QEMU KVM 運行 OS X 虛擬機所必須的文件都封裝到一個 Docker 鏡像中,從而完美地解決了自己配置 OS X 虛擬機搞不掂的問題。使用起來也相當方便且順暢,只需要一條命令把這個 Docker 鏡像運行起來,你便擁有了一個可用的 OS X 環境。

別家的使用教程已經有很多了,這裏就不再贅述,我只給出一些個人的使用經驗。官方教程影片在這裏(https://youtu.be/wLezYl77Ll8),有興趣可自行學習。我這邊使用的是默認版本的鏡像,也就是 Mac OS 10.15 Catalina。啓動之後,默認的鏡像是裸機環境,需要自行安裝系統。安裝大約耗時半小時,安裝完成之後就可以直接使用了。由於沒有 GPU 加速,因此 UI 會比較卡頓。但打開 SSH 登錄之後,直接使用控制檯時並不存在卡頓的問題。因此如果需求是使用 GUI 應用較多的話,還是需要考慮一下配置 GPU 加速的。由於我這邊是跑編譯和 debug,用的工具也是控制檯上運行的 Vim 和 GDB/LLDB,而 GUI 的卡頓對於我的影響也就微乎其微了。

由於編譯需要消耗比較多的記憶體以及 CPU 資源,於是便轉移到了實驗室的伺服器上來運行。這裏便遇到了第一個需要解決的問題:遠端的 QEMU 界面如何在本地顯示。解決的方法就是 X forwarding。首先在 SSH 連接實驗室的伺服器時需要開啓 X forwarding,即 ssh 命令的 -X-Y 參數。同時爲了不卡頓,還需要開啓流量壓縮,即 -C 參數。爲了鑑權方便,我這裏選擇了 -Y 參數。如果是使用其他 SSH 客戶端的話,需要查看一下自己的客戶端如何啓用這兩個選項。

local$ ssh -YC user@hostname

然而,這只是第一步。這只能讓伺服器上運行的 GUI 程式顯示在本地的熒屏上。而對於一個 Docker container 來講,其運行的環境可以認爲完全是另外一臺電腦,因此還需要讓 Docker container 中的 GUI 程式可以利用窩們轉發的 X 數據。需要解決的有兩點:連接和鑑權。這裏採用了直接使用 host 網路來啓動的方式來解決連接問題,即 --net=host。此時,Docker container 中的 DISPLAY 變量不需要額外改動,直接使用外邊的值即可。此外,X server 鑑權所依賴的 $HOME/.Xauthority,也需要給 1000 用戶開放讀取權限並放到 Docker container 中(如果你本人的賬戶 ID 不是 1000 的話)。

remote$ chmod 666 ~/.Xauthority

記憶體和 CPU 用量的設置是通過環境變量來進行的:CPU 是 SMP 以及 CORES,記憶體是 RAM(單位:GiB)。默認參數是 4 核 4 GiB 記憶體。此外,如果想要持久化硬碟中的數據,可以將 Docker image 中的 /home/arch/OSX-KVM/mac_hdd_ng.img 文件拷貝出來,然後在啓動 Docker 的時候放進去即可。

remote$ docker run -it --rm \
          -v /path/to/disk:/tmp/disk \
          --entrypoint=/bin/bash \
          sickcodes/docker-osx:latest
container$ cp /home/arch/OSX-KVM/mac_hdd_ng.img /tmp/disk

啓動 QEMU 時,會將虛擬機 SSH 的 22 端口轉發到 INTERNAL_SSH_PORT(默認爲 10022),以及 VNC 的 5900 端口轉發到 SCREEN_SHARE_PORT(默認爲 5900)。由於前面使用了 host 網路,因此如果這兩個默認佔用的端口如果已被使用,則需要通過這兩個環境變量來重新定義。

remote$ docker run --rm -it \
          --net=host -e DISPLAY=$DISPLAY \
          -v $HOME/.Xauthority:/home/arch/.Xauthority \
          --device /dev/kvm \
          -v /path/to/disk/mac_hdd_ng.img:/home/arch/OSX-KVM/mac_hdd_ng.img \
          -e INTERNAL_SSH_PORT=40022 \
          -e SCREEN_SHARE_PORT=45900 \
          -e SMP=32 -e CORES=32 -e RAM=64 \
          sickcodes/docker-osx:latest

這個時候,如果沒有遇到什麼奇怪的問題的話,應該就可以看到控制檯上刷出一些 QEMU 的啓動 log 然後彈出虛擬機的熒屏窗口了。

至此,你便擁有了一隻鯨魚背上的大貓咪。後面的工作就交給 Homebrew 吧。

从 GitHub 下载一个 Repo 中部分文件内的代码

當需要下載 GitHub 上某一個 Repo 中某個子文件夾中的內容時,可以借用 GitHub 提供的 SVN 支持來實現這一功能。

由於一些大工程中某些組件完全可以獨立使用(比如:LLVM-Lit),這種情況下如果採用完整 clone 整個 repo 然後取出其中所需的部分然後刪掉剩下的部分的方式來處理的話,未免顯得會有些有病。「如果能只下載所需要的這部分就好了!」

比如,需要從 LLVM 的 repo 中下載 LLVM-Lit 的代碼(位於:https://github.com/llvm/llvm-project/tree/main/llvm/utils/lit),則可以執行如下操作。

svn export https://github.com/llvm/llvm-project/trunk/llvm/utils/lit

此時,即可將該 repo 最新 commit 中 llvm/utils/lit 文件夾下的全部內容都獲取到當前目錄下的 lit 文件夾中了。需要注意的是這個 svn 的鏈接與在 GitHub 上瀏覽時瀏覽器中的鏈接並不是相同的。

引用:

  1. https://coderwall.com/p/o2fasg/how-to-download-a-project-subdirectory-from-github
  2. https://docs.github.com/en/github/importing-your-projects-to-github/support-for-subversion-clients

猫猫曾说:“Gmail 是最好的邮件客户端”

猫猫曾说:“Gmail 是最好的邮件客户端”。这里讲的 Gmail 指的并不完全是 Gmail 这个电子邮件系统,确切地说是其网页版前端,也就是我们日常工作中使用的最多的一个站点:gmail.com。

正在启动的 Gmail 网页客户端
正在启动的 Gmail 网页客户端

如果你不是像我一样将一个 Gmail 的页面 pin 在浏览器上,并且保持浏览器长期不关的话,这个加载动画将是你在使用 Gmail 的网页客户端的时候经常会看到的。而加载完成之后,打开的便是上面讲到过的这个最好的邮件客户端了。

之所以称其为邮件客户端,不单单是因其可以查看所有你名下的 Gmail 帐号的邮件,而是 Gmail 也可以通过 POP、IMAP 以及 SMTP 等协议来控制其他的邮箱,就像一个本地的邮件客户端那样(例如:Outlook、thunderbird 等)。通过将全部的邮箱聚合在一起,可以将各个被独立割裂的邮箱统一在一起使用。而所有邮件又都放于 Gmail 的服务器上,不会出现像本地邮件客户端那样在更换电脑时需要提前备份邮件数据的情况。而这一切的轻松自如,是从配置 Gmail 的邮件代收代发开始的。

首先进入“设置” -> “帐号和导入” 选项卡(由于日常使用的中文系统比较乱,我的 Google 帐号中配置的文字是简体字,其他语言请自己找找看),里面中间部分会有两项设置,其中一项是配置代发邮箱而另一项则是配置代收邮箱。

“帐号和导入” 选项卡
“帐号和导入” 选项卡

区别于本地邮件客户端,这里收取的邮箱和发送的邮箱是分别独立配置的。也就是说,Gmail 并不是按照邮箱帐号来对邮件进行托管的,而是将所有被托管帐号的邮件都混在一起。这样的设计使得用户不再需要注意邮件是被发送到哪个邮箱的,特别适合于像我这样同时拥有多个面对同一群人的邮箱账户的情况。

配置好代收代发邮箱之后,代收邮箱中的邮件会被统一收到这个 Gmail 邮箱中进行处理,而这个 Gmail 邮箱又同时可以通过配置的若干个发信帐号来发邮件。这样就避免了同时打开多个邮箱的网页客户端,甚至是与同一个人的对话由于收信邮箱的不同而出现割裂的窘境。

其实,好用的网页客户端并不只 Gmail 一家。之前用的 T 实验室的邮箱就基本满足了我对于邮件客户端的需求(有浏览器通知新邮件,界面交互比较符合人类逻辑等)。然而 T 实验室所提供的邮件客户端需要每 24 小时就重新登录一次,而且当登录超时之后便不会再有新邮件提醒了。而我同时所拥有的 S 实验室的邮箱的表现则更差,不仅没有浏览器通知,甚至还会重复的登出账户,这简直是反人类。同时,最近老板添了一个新的毛病——同一封邮件会同时发给我 T 实验室和 S 实验室的两个邮箱帐号,于是就会出现有时同一件事由于这两封邮件而被做了两次的情况。

由于我日常办公使用的是 Gsuit 来处理文档,因此 Google 帐号又是我最常用的邮箱。同时我的 Gmail 是用来接收飞机酒店等通知邮件的重要邮箱,虽然本不想将 Gmail 邮箱用于工作,但不得已也接收了很多与工作相关的邮件。既然这样,便随之萌生了一个想法——干脆使用 Gmail 来管理所有邮件吧。

然而这一工作的开始并不顺利。一来是三个邮箱里已经有了很多历史邮件,而这些邮件又有很多查询的必要。二来是 Gmail 一直一来的问题——会把某些类型的邮件附件文档标记为有害(例如:Office 文档等),从而不将其拉取到 Gmail 中,只会留下一条通知告知说 “喂~你的邮件有毒,我就不吃啦,你自己看着办吧。”,从而弄得我真的很崩溃。但即使是这样,最终我还是没能抵抗得过在一个浏览器中同时开三个邮箱标签的崩溃感,于是我在终于得闲之后大刀阔斧的折腾了一番。

因可疑而被退回服务器的邮件的通知
因可疑而被退回服务器的邮件的通知

在经历了将近三天的漫长折腾之后,三个邮箱终于被窝搞到了一起。终于可以愉快的使用工作邮箱了。同时由于所有邮件都被 Gmail 代管了,因此在移动设备端也仅需要下载一个 Gmail 的 App 即可实现所有邮件的查收和回复。相比于之前需要在 Gmail App 中配置另两个邮箱的帐号信息,两个邮箱的邮件也被割裂到两个不同的帐号中,现在无论从配置和使用上来讲都有了很大程度的愉悦度和方便性上的提升。

代发邮件的配置很简单,只需要配置好 SMTP 服务器的相关信息即可。关于服务器的信息可以查询邮箱提供者所给出的配置教程。需要的信息包括:服务器域名、端口,用户名和密码以及链接的加密方式。一般情况下 Gmail 可以自动探测出相关配置,如需修改请按照配置教程中给定的内容填写。

填入 SMTP 服务器信息
填入 SMTP 服务器信息

而代收配置的方式分为三种。第一种是 Gmailify,即对于部分 Gmail 所支持的邮箱提供商(如:Yahoo、AOL、Outlook、Hotmail),可以通过 Gmail 来直接使用所关联的邮箱。但请注意,Gmailify 仅支持一个邮箱账户。而对于拥有多个邮箱的用户,或者使用了 Gmail 不支持的邮箱的情况,则需要采用后两种方式:POP3 协议收取或邮箱转发推送。对于第一种方式,这里不做过多描述,详细信息请参阅 Gmail 的文档「为其他电子邮件帐号获取 Gmail 功能」。

使用 POP3 协议来收取邮件的方式,即是使 Gmail 工作于邮件客户端的形式。在这种方式下,Gmail 的服务器会定期去配置的服务器中直接收取邮件并呈现于 Gmail 的收件箱中。这种方式的配置类似于配置代发邮箱,在 Gmail 的配置中填入邮箱提供者给出的 POP 协议的配置信息,待 Gmail 查验通过后即可正常使用。

填入 POP 服务器信息
填入 POP 服务器信息

而使用邮箱中的邮件自动转发功能来实现收取的方式,是使 Gmail 工作于普通邮箱的形式。在这种方式下,邮件是通过邮箱提供者服务器自动推送到 Gmail 的收件箱中并呈现的。这种方式的配置需要在源邮箱中进行,一般情况下邮箱都会提供自动转发的功能,使用该功能将邮件转发至自己的 Gmail 邮箱即可。

于是就产生了一个问题:这后两种方式到底那个更好呢?

猫猫说她更倾向于使用 POP 协议的方式来收取邮件。而我的话则是两种都采用了。T 实验室邮箱采用的是 POP 协议收取,而 S 实验室邮箱则采用的是邮箱自动转发来信的方式。主要的原因是 Gmail 对于 S 邮箱的查收非常不及时,而 T 邮箱则可以及时查收。然而并不知道这个查收延时是怎么设定的,也许是依照收件频率吧,反正不能自行配置。为了能够更快的收取 S 邮箱的邮件,于是决定将其配置为自动推送的方式。同时,也是为了兼容上文中提到的老板的新毛病。对于发到 S 邮箱的邮件,如果同时被发到了 T 邮箱,则不进行转发。(真是机智如我)

所以,对于这个问题,我的答案是:如果需要更及时的邮件查收的话,请使用邮件自动转发的方式进行推送。否则的话,可能 POP 协议收取的方式会更好些,毕竟转发是一次新的发送,依然有可能存在未能被 Gmail 成功收到的情况。

至于使用的效果如何,个人觉得 Gmail 的网页端确实很好用。支持图片的粘贴插入,邮件的自动归类,同时查找功能也更加强大。因此,如果后续没有其他使用问题产生的话,打算暂时先这么用下去了。

以上

后记:好像拉取的方式总会出现莫名的延迟,于是最后又改成全部采用原始邮箱自动转发推送的方式了。。。QAQ

從小包包裏掏出一個 WordPress

開站第一篇文章,總結一下如何使用 docker 來構建一個 WordPress 站點,以及期間遇到的問題。本站以個人筆記以及雜談的性質來提供,不求其他人也能看得懂,主要用於備忘。發表的文章所採用的語言包括所有窩會的語言,所以也沒辦法求其他人能一定看得懂。

首先回歸正題。

貓貓之前一直都說 docker 用起來如何方便,然而經過之前一次嘗試構建 mastodon 的慘痛經歷之後,一直都沒能有心情來嘗試搞事情。然而由於最近在趕完若干篇稿子之後,終於難得有幾日的閒暇,以及某些迫不得已的剛需,於是就又打算搞起來惹~。。。

之所以第一發選擇 WordPress,一來是因爲一直想有一個長期的備忘錄性質的筆記本,以及可以讓自己隨意畫畫寫寫的板子;而二來也是因爲據說這個東西搞起來比較簡單。而選擇使用 docker 來作爲搭建的基礎,則完全是因爲貓貓的安利了。當然,方便簡單也是主要被考慮的原因之一。畢竟,作爲一個寫稿子的,技術和能力都比不上經驗豐富的貓貓,也就只有量力而爲了。

起初找到的一個教程是如何使用 docker 起一個數據庫實例和一個 WordPress 實例,然後讓這兩個實例配合在一起使用。然而看過之後就覺得,手動管理兩個實例貌似操作起來過於繁瑣了。於是就想到了之前貓貓在教窩配置 mastodon 的時候提到過的 docker-compose 了。

於是,窩就噠噠噠跑去看 docker-compose 的教程了。由於有了一點點之前的理解,在簡單過了一遍前面的說明之後,就開始自己動手寫了起來:

version: "3"

services:
    db:
        restart: always
        image: mariadb:latest
        environment:
            MYSQL_ROOT_PASSWORD: xxxxxx
            MYSQL_DATABASE: wordpress
        volumes:
            - /root/workspace/wordpress/mysql/:/var/lib/mysql

    wordpress:
        depends_on:
            - db
        restart: always
        image: wordpress:latest
        environment:
            WORDPRESS_DB_HOST: db:3306
            WORDPRESS_DB_USER: root
            WORDPRESS_DB_PASSWORD: xxxxxx
        volumes:
            - /root/workspace/wordpress/html/:/var/www/html
        ports:
            - "80:80"

volumes:
    mysql:
    html:

最終,成品的配置文件就是醬紫惹。其實在寫的過程中又翻到了 docker-compose 教程裏面給的那個 WordPress 的配置文件。本來想用的來着,然而卻怎樣也跑不起來,總在說 MySQL 恢復數據庫失敗。於是,一氣之下就把數據庫換成了上面提到過的那份分開手動控制的那份教程裏面使用的 MariaDB。最後終於是跑起來了,docker-compose 不再報錯了。

於是,興沖沖的跑去用瀏覽器看了一下,然後發現數據庫連接出問題了 T_T。查了下 log 發現是數據庫拒絕連接了。雖然並不清楚是因爲神馬造成的拒絕連接,但是憑着多年以來的直覺,將數據庫用戶換成了 root,於是大功告成。

然而,又有了新的狀況。在配置文件裏面 volume 項中指定的文件夾裏面並沒有文件。問了貓貓,說讓窩看一下“資料庫起來沒有”,顯然這並不是窩應該會的操作。無奈之下,在貓貓的提醒下更新了一下 volume 中的路徑爲絕對路徑,然後又重啓了一下 docker 服務以及這兩個容器。接着收到了一條警告說之前指定的 volume 的值因爲使用了舊的容器所以無效。於是果斷的刪掉了之前的容器,然後又重新開了這兩個容器。終於,volume 文件夾內也有文件了。超讚!

這樣,應該也就差不多了吧,,,大概。。。剩下的也就是折騰 HTTPS 以及主題神馬的了,打算明天或者以後再弄吧。。。