最初のC ++コンパイラの30周年記念:Cfrontでエラーを探す

ビョルン・ストラウストルプ

著者:アンドレイ・カルポフ、ビョルン・ストラウストルプ。



CfrontはC ++コンパイラで、1983年以来存在しており、BjörnStroustrupによって開発されました。 当時、彼は「クラスを持つC」として知られていました。 Cfrontには本格的なパーサー、シンボルテーブルがあり、各クラス、関数などにツリーを構築しました。 CfrontはCPreに基づいていました。 Cfrontは、1990年頃まで言語の開発を決定しました。 C ++の多くの不明瞭な点は、Cfront実装の制限に関連しています。 その理由は、CfrontがC ++からCに変換されたためです。要するに、CfrontはC ++プログラマにとって神聖なアーティファクトです。 そして、私はこのプロジェクトをチェックせずに通り過ぎることができませんでした。



はじめに



Cfrontをテストするというアイデアは、このコンパイラの最初のリリースバージョンの30周年を記念したメモ「 30 YEARS OF C ++ 」によって促されました。 BjörnStraustrupに連絡して、Cfrontのソースコードを入手しました。 何らかの理由で、それらを取得することは全体の話になると思いました。 すべてがシンプルであることが判明しました。 これらのソースはhttp://www.softwarepreservation.org/projects/c_plus_plus/で公開されており、誰でも利用できます。



検証のために、1985年10月にリリースされたCfrontの最初の商用バージョンが選択されました。 結局、彼女はちょうど30歳でした。



Björnは、チェックはそれほど簡単ではないかもしれないと警告しました。



これは、1MB 1MHzマシンで実行するように設計され、元のPC(640KB)でも使用される*非常に古いソフトウェアであることを忘れないでください。 また、私のフルタイムの仕事の一部として、一人(私)によって行われました。



そして本当に。 プロジェクトを取り、検証することはとても不可能でした。 たとえば、当時、クラス名と関数名を区切るのに4つのドット(::)ではなく、ドット(。)だけが使用されていました。 例:

inline Pptr type.addrof() { return new ptr(PTR,this,0); }
      
      





PVS-Studioアナライザーはこの準備ができていませんでした。 ソースを手動で調べて修正した学生に同僚をつなぐ必要がありました。 完全ではありませんでしたが、助けました。 とにかく、多くの場所でPVS-Studioは目を膨らませて分析を拒否します。 それにもかかわらず、なんとかプロジェクトを検証できました。



私はすぐに何か壮大なものを見つけられなかったと言わなければなりません。 重大なバグはありませんでした。3つの理由が考えられます。
  1. プロジェクトは小規模です。 143個のファイルで100 KLOCのみ。
  2. コードは高品質です。
  3. それでも、PVS-Studioアナライザーはすべてを検証することはできませんでした。


「これらはすべて言葉です。 コードを見せてください。」Linus Torvalds



しかし、十分な言葉。 私たちの読者は、ストラウストルップ自身の少なくとも1つの間違いを見るためにここに集まりました。 コードを見てみましょう。



最初の断片

 typedef class classdef * Pclass; #define PERM(p) p->permanent=1 Pexpr expr.typ(Ptable tbl) { .... Pclass cl; .... cl = (Pclass) nn->tp; PERM(cl); if (cl == 0) error('i',"%k %s'sT missing",CLASS,s); .... }
      
      





PVS-Studio警告:V595 nullptrに対して検証される前に、 'cl'ポインターが使用されました。 行を確認:927、928。expr.c 927



clポインターはNULLにすることができます。 これは、(cl == 0)かどうかを確認することで証明されます。 問題は、このチェックの前でさえ、このポインターが逆参照されることです。 これはPERMマクロで発生します。



つまり マクロを展開すると、次のようになります。

 cl = (Pclass) nn->tp; cl->permanent=1 if (cl == 0) error('i',"%k %s'sT missing",CLASS,s);
      
      





2番目のフラグメント



同じこと。 ポインターを交換し、そのときだけチェックされました:

 Pname name.normalize(Pbase b, Pblock bl, bit cast) { .... Pname n; Pname nn; TOK stc = b->b_sto; bit tpdf = b->b_typedef; bit inli = b->b_inline; bit virt = b->b_virtual; Pfct f; Pname nx; if (b == 0) error('i',"%d->N.normalize(0)",this); .... }
      
      





PVS-Studio警告:V595 nullptrに対して検証される前に「b」ポインターが使用されました。 行を確認してください:608、615。norm.c 608



第三の断片

 int error(int t, loc* lc, char* s ...) { .... if (in_error++) if (t!='t' || 4<in_error) { fprintf(stderr,"\nUPS!, error while handling error\n"); ext(13); } else if (t == 't') t = 'i'; .... }
      
      





PVS-Studio警告:V563この「else」ブランチは、前の「if」ステートメントに適用する必要がある可能性があります。 error.c 164



エラーがあるかどうかはわかりませんが、コードは正しく実行されません。 「else」は最も近い「if」を指します。 したがって、コードは見た目どおりに機能しません。 正しくフォーマットすると、以下が得られます。

 if (in_error++) if (t!='t' || 4<in_error) { fprintf(stderr,"\nUPS!, error while handling error\n"); ext(13); } else if (t == 't') t = 'i';
      
      





4番目のフラグメント

 extern genericerror(int n, char* s) { fprintf(stderr,"%s\n", s?s:"error in generic library function",n); abort(111); return 0; };
      
      





警告PVS-Studio:V576の形式が正しくありません。 'fprintf'関数を呼び出すときに、異なる数の実引数が予期されます。 予想:3.現在:4. generic.c 8



形式指定子に注意してください: "%s"。 文字列が印刷されます。 しかし、変数「n」は動作しませんでした。



その他



残念ながら(または幸いなことに)、本当のエラーのようなものは表示できません。 アナライザーはコードに一連の警告を出しましたが、注意する価値はありますが、危険ではありません。 たとえば、アナライザーは次のグローバル変数の名前を好みません。

 extern int Nspy, Nn, Nbt, Nt, Ne, Ns, Nstr, Nc, Nl;
      
      





PVS-Studio警告:V707グローバル変数に短い名前を付けることは悪い習慣と見なされます。 'Nn'変数の名前を変更することをお勧めします。 cfront.h 50



または、たとえば、fprintf()は修飾子 "%i"を使用してポインター値を出力します。 言語の最新バージョンでは、これに「%p」が使用されます。 しかし、私が理解しているように、30年前には「%p」はまだなく、コードは完全に正しいです。



興味深い観察



このポインター



以前は「これ」で彼らは一桁も大胆かつ無作法に働いていたことに気付きました。 このトピックに関するいくつかの例:

 expr.expr(TOK ba, Pexpr a, Pexpr b) { register Pexpr p; if (this) goto ret; .... this = p; .... } inline toknode.~toknode() { next = free_toks; free_toks = this; this = 0; }
      
      





ご覧のとおり、当時は「this」の値を取り、変更することは禁止されていませんでした。 ポインターを変更するだけでなく、 これをnullptrと比較することさえできなくなりました。



これは妄想の場所です



彼らが言うように、あなたは何も確信できません。 私が出会った次のコードが気に入った:

 /* this is the place for paranoia */ if (this == 0) error('i',"0->Cdef.dcl(%d)",tbl); if (base != CLASS) error('i',"Cdef.dcl(%d)",base); if (cname == 0) error('i',"unNdC"); if (cname->tp != this) error('i',"badCdef"); if (tbl == 0) error('i',"Cdef.dcl(%n,0)",cname); if (tbl->base != TABLE) error('i',"Cdef.dcl(%n,tbl=%d)", cname,tbl->base);
      
      





BjörnStraustrupによるコメント





結論

Cfrontの価値を過大評価することは困難です。 彼はプログラミング業界全体の発展に影響を与え、世界に絶え間なく進化し続けるC ++言語を与えました。 C ++の作成で彼が行ったすべての作業に対して、私はBjörnに感謝します。 ありがとう 私は、Cfrontと少なくとも「隣に立って」喜んでいます。



すべての読者に感謝します。バグを減らしたいと思います。



All Articles