Open5

setjmpの結果を代入してはいけない

okuokuokuoku

WasmLinuxで使っちゃってるし実際gccやclangでは正常に動作するんだけど一応...

okuokuokuoku

setjmpを書いて良い場所

Cの規格やPOSIXでは setjmp を書ける場所が厳密に決まっている:

https://pubs.opengroup.org/onlinepubs/9699919799/functions/setjmp.html

An application shall ensure that an invocation of setjmp() appears in one of the following contexts only:

  • The entire controlling expression of a selection or iteration statement
  • One operand of a relational or equality operator with the other operand an integral constant expression, with the resulting expression being the entire controlling expression of a selection or iteration statement
  • The operand of a unary '!' operator with the resulting expression being the entire controlling expression of a selection or iteration
  • The entire expression of an expression statement (possibly cast to void)

要は分岐にしか書けない。例えば、

if(i = setjmp(jb)){ ... }

のように i に代入するのは違反になる。

例えば https://www.jpcert.or.jp/sc-rules/c-msc22-c.html に挙がっているように、

if(i != setjmp(jb)){ ... }

のように式の中で比較するのは構わない。

okuokuokuoku

じゃぁ何で longjmp は値を取るんだよ

具体的な値で比較することは違反ではないので、例えば longjmp 後の処理を switch による分岐で指定するみたいなユースケースがある。

switch(setjmp(jb)){ case 1: ...; }
okuokuokuoku

代入が動作しないケースは存在するのか?

自分の知る限り、実際には setjmp の結果の代入ができないアーキテクチャは無い。これは、常識的なアーキテクチャでは基本的に setjmp は複数回returnする通常のC ABIの関数として実装されているためだと思う。つまり、 setjmp の呼出し側からは通常の関数に見えるため、代入を禁止するモチベーションが無い。

いわゆるSanitizer類はこのような利用をtrapできるかもしれない。

また、未定義挙動って事はコンパイラはそれを利用して最適化しても良いということでもあるので、正常動作は誰も保証してくれないということになる。

okuokuokuoku

returns_twice 属性

gccやclangは returns_twice 属性を setjmpvfork のような2度(以上)returnする関数に設定することを推奨している。gccは警告の制御くらいにしか言及していないが、ClangのバックエンドであるLLVMはtail-call最適化も無効にするとしている。

https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-returns_005ftwice-function-attribute

The returns_twice attribute tells the compiler that a function may return more than one time. The compiler ensures that all registers are dead before calling such a function and emits a warning about the variables that may be clobbered after the second return from the function. Examples of such functions are setjmp and vfork. The longjmp-like counterpart of such function, if any, might need to be marked with the noreturn attribute.

https://llvm.org/docs/LangRef.html

(ClangではなくLLVM言語のマニュアル -- ただgccの returns_twice 属性は結局LLVMのこのattributeにlowerされる)

This attribute indicates that this function can return twice. The C setjmp is an example of such a function. The compiler disables some optimizations (like tail calls) in the caller of these functions.

呼出し側をtail-call最適化してしまうと、こんどは setjmp 呼出し側の関数を returns_twice として扱わなければならなくなる。そのように感染させていってしまうのはよくない; C言語の setjmp は、実際には setjmp した関数自体から2度(以上)returnすることは無い -- そもそも setjmp した関数から抜けたら longjmp してはいけない -- ため、 returns_twice を呼出し元にまで感染させていく必要性は本来存在しない。