LoginSignup
2
1

More than 5 years have passed since last update.

C/C++インタプリタ CINTのデストラクタ不正呼び出し問題と格闘した話 その4

Last updated at Posted at 2018-11-02

以前の記事

その1
その2
その3

修正対象

修正が必要になる関数は以下の表のとおりです。

ファイル 関数
decl.c G__define_var
ifunc.c G__interpret_func
pcode.c G__LD_p0_struct
pcode.c G__get_LD_p0_p2f
pcode.c G__get_LD_p1_p2f
pcode.c G__get_LD_pn_p2f
pcode.c G__get_ST_p0_p2f
pcode.c G__get_ST_p1_p2f
pcode.c G__get_ST_pn_p2f
var.c G__class_2nd_decl
var.c G__letvariable
var.c G__getvariable
bc_exec_asm.h G__exec_asm

decl.c

G__define_var関数

ユーザー定義型の処理を行っているスコープの先頭部分に、初期化済みを表すフラグ変数を追加します。

decl.c
      /**************************************************************
      * declaration of struct object, no pointer, no reference type
      **************************************************************/
      if(var_type=='u'&&
#ifndef G__OLDIMPLEMENTATION871
         (G__def_struct_member==0||-1==G__def_tagnum||
          'n'==G__struct.type[G__def_tagnum])
#else
         G__def_struct_member==0
#endif
         &&new_name[0]!='*'&&
         G__reftype==G__PARANORMAL) {

#ifndef G__NCPRO_OLDIMPLEMENTATION39
        struct G__var_array *var;
        char name[G__MAXNAME];
        char *p;
        int hash,ig15;
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        int initflag=0;
#endif

        strcpy(name,new_name);
        p=strchr(name,'[');
        if(p) *p='\0';
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        G__hash(name,hash,i)
        var = G__getvarentry(name,hash,&ig15,&G__global,G__p_local);
        if(var && '@'!=var->type[ig15]) initflag=1;
#endif
#endif

以下はtypeを元に戻している部分です。コンパイル済みライブラリに含まれている型だった場合は、ここで処理を行います。インタプリタで処理される型だった場合は、G__letvariable関数の中で処理が行われます。

decl.c
        /************************************************************
        * memory allocation and table entry generation
        ************************************************************/
        store_struct_offset = G__store_struct_offset;
        if(G__CPPLINK!=G__struct.iscpplink[tagnum]) {
          /* allocate memory area for constructed object by interpreter */
          G__var_type = var_type;
#ifndef G__OLDIMPLEMENTATION1349
          G__decl_obj=1;
#endif
          G__store_struct_offset=G__int(G__letvariable(new_name,reg,&G__global
                                                       ,G__p_local));
#ifndef G__OLDIMPLEMENTATION1349
          G__decl_obj=0;
#endif
#ifndef G__OLDIMPLEMENTATION1073
          if(0==G__store_struct_offset &&
             G__asm_wholefunction && G__asm_noverflow) {
            G__store_struct_offset = G__PVOID;
          }
#endif
        }
        else {
          /* precompiled class, 
           * memory will be allocated by new in constructor function below */
          G__store_struct_offset = G__PVOID;
#ifndef G__NCPRO_OLDIMPLEMENTATION40
          if(var && '@'==var->type[ig15]) var->type[ig15] = 'u';
#endif
        }

バイトコードへのコンパイル時に、実行されなかったブロック内で定義された変数のtypeを差し替える処理を行います。ユーザー定義型の処理を行っているスコープの最後の部分に追加します。

decl.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if(!initflag && G__asm_loopcompile && G__no_exec_compile) {
          G__hash(name,hash,i)
          var = G__getvarentry(name,hash,&ig15,&G__global,G__p_local);
          if(var) var->type[ig15] = '@';
        }
#endif
      } /* of if(var_type=='u'&&G__def_struct_member.... */

ifunc.c

G__interpret_func関数

バイトコードインタプリタでの実行中に未初期化の変数に対してデストラクタが呼び出された場合は、thisポインタの値を一時的に0に変更することで呼び出しをスキップしています。

ifunc.c
#ifdef G__ASM_IFUNC
  if(G__asm_exec) {
    ifn = G__asm_index;
#ifndef G__OLDIMPLEMENTATION864
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    store_struct_offset = G__store_struct_offset;
    if('@'==result7->type) G__store_struct_offset = 0;
#endif
    /* delete 0 ~destructor ignored */
    if(0==G__store_struct_offset && -1!=p_ifunc->tagnum && 
       0==p_ifunc->staticalloc[ifn] && '~'==p_ifunc->funcname[ifn][0]) {
#ifndef G__NCPRO_OLDIMPLEMENTATION40
      G__store_struct_offset = store_struct_offset;
#endif
      return(1);
    }
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    G__store_struct_offset = store_struct_offset;
#endif
#endif
    goto asm_ifunc_start;
  }
#endif

pcode.c

G__LD_p0_struct関数

バイトコードインタプリタでの実行中に未初期化の変数に対して再宣言が行われる場合は、変数管理テーブル内のtypeを元に戻した後で、スタックに積まれているG__value型のデータのtypeを差し替えます。

スタック上のG__value型のデータは、G__interpret_func関数に引数result7として渡されます。

pcode.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  if('@'==var->type[ig15]) {
    var->type[ig15] = 'u';
    buf->type = '@';
  }
#endif

G__get_LD_p?_p2f関数とG__get_ST_p?_p2f関数

これらの関数によって、バイトコードのロード命令とストア命令の処理で呼び出される関数が決定されます。

以下はG__get_LD_p0_p2f関数の修正内容で、未初期化の変数に対してG__LD_p0_struct関数を呼び出すように設定しています。他の5つの関数も修正のパターンは同じです。

pcode.c
  if(isupper(type)) {
    if('Z'==type) done=0;
#ifndef G__OLDIMMPLEMENTATION1341
    else if('P'==type || 'O'==type) *pinst = (long)G__LD_p0_double; 
#endif
    else *pinst = (long)G__LD_p0_pointer;
  }
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  else if('@'==type) {
    *pinst = (long)G__LD_p0_struct;
  }
#endif
  else {

var.c

G__class_2nd_decl関数

バイトコードの出力モードを保存するための変数の宣言を、G__class_2nd_decl関数の先頭部分に追加します。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  int store_no_exec_compile;
#endif

バイトコードの出力中に未初期化の変数に対して再宣言が行われる場合は、デストラクタの呼び出し前にtypeを元に戻した上で、ブロック内を実行せずにバイトコードの出力を行うモードに変更します。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  store_no_exec_compile=G__no_exec_compile;
  if('@'==var->type[ig15]) {
    var->type[ig15]='u';
    G__no_exec_compile=1;
  }
#endif

処理後に、バイトコードの出力モードを元に戻します。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  G__no_exec_compile=store_no_exec_compile;
#endif

G__letvariable関数

初期化済みを表すフラグ変数の宣言を、G__letvariable関数の先頭部分に追加します。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  int initflag=1;
#endif

変数が未初期化だった場合は、typeを元に戻して初期化済みフラグを解除します。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    if('@'==var->type[ig15]) {
      var->type[ig15]='u';
      initflag=0;
    }
#endif

G__class_2nd_decl関数の呼び出しによる変数の再宣言が行われなかった場合は、G__letvariable関数を終了する前に再びtypeを差し替えます。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  if(!initflag) var->type[ig15]='@';
#endif
  return(result);

G__getvariable関数

初期化済みを表すフラグ変数の宣言を、G__getvariable関数の先頭部分に追加します。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
  int initflag=1;
#endif

バイトコードインタプリタでの実行中に未初期化の変数に対して再宣言が行われる場合は、変数管理テーブル内のtypeを元に戻します。

var.c
#ifdef G__ASM
  /************************************************************
   * If G__asm_exec is set by 'for(', 'while(' or 'do{}while('
   * loop in G__exec_statements(), execute this part and skip
   * parsing string.
   ************************************************************/
  if(G__asm_exec) {
    ig15=G__asm_index;
    paran = G__asm_param->paran;

    for(ig35=0;ig35<paran;ig35++) {
      para[ig35] = G__asm_param->para[ig35];
    }

    para[paran]=G__null;
    var=varglobal;
    if(varlocal==NULL)
      G__struct_offset =0;
    else
      G__struct_offset=G__store_struct_offset;
#ifndef G__NCPRO_OLDIMPLEMENTATION40
    if('@'==var->type[ig15]) {
      var->type[ig15]='u';
      initflag=0;
    }
#endif
    goto G__exec_asm_getvar;
  }
#endif

各種ロード命令を処理するswitch文の中で、G__getvariable関数の戻り値として使用されるG__value型の変数resulttypeを差し替えます。

戻り値はスタックに積まれた後で、G__interpret_func関数に引数result7として渡されます。

var.c
      case 'u': /* struct, union */
        G__GET_STRUCTVAR;
        if(ig25<paran) {
          G__tryindexopr(&result,para,paran,ig25);
        }
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if(!initflag) result.type='@';
#endif
        break;

実行されなかったブロックのバイトコードへのコンパイル時に呼び出された場合は、typeを元に戻して初期化済みフラグを解除します。

var.c
#ifndef G__OLDIMPLEMENTATION776
      if(G__no_exec_compile && 
         (G__CONSTVAR!=var->constvar[ig15] || isupper(var->type[ig15]) ||
          G__PARAREFERENCE==var->reftype[ig15] || 
          var->varlabel[ig15][1] || /* 2011 ??? */
          var->p[ig15]<0x1000) &&
         'p'!=tolower(var->type[ig15])) {
#else
      if(G__no_exec_compile) {
#endif
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if('@'==var->type[ig15]) {
          var->type[ig15]='u';
          initflag=0;
        }
#endif

その後、G__getvariable関数を終了する前に再びtypeを差し替えます。

var.c
#ifndef G__NCPRO_OLDIMPLEMENTATION40
        if(!initflag) var->type[ig15]='@';
#endif
        return(result);
      }

bc_exec_asm.h

G__exec_asm関数

バイトコードインタプリタの本体です。エラー発生時に変数管理テーブル内のtypeを差し替える処理は、この関数に追加する必要があります。

変数管理テーブルへのポインタとインデックスを保存するための変数の宣言を、G__exec_asm関数の先頭部分に追加します。

bc_exec_asm.h
#ifndef G__NCPRO_OLDIMPLEMENTATION41
  struct G__var_array *store_var=NULL,*store_varB=NULL;
  int store_index=0,store_indexB=0;
  int store_tagnumB=0;
#endif

G__LDST_VAR_P命令とG__LD_VAR命令の実行時に、命令バッファから変数管理テーブルへのポインタとインデックスを取り出して保存しておきます。

bc_exec_asm.h
#ifndef G__NCPRO_OLDIMPLEMENTATION41
      store_var=(struct G__var_array*)G__asm_inst[pc+4];
      store_index=G__asm_inst[pc+1];
#endif

G__LD_IFUNC命令の実行時に、デストラクタの呼び出しが完了したことによって変数が未初期化状態に戻った場合は、変数管理テーブルへのポインタとインデックスを差し替え処理用の変数にコピーしておきます。

また、コンストラクタの呼び出しが完了したことによって差し替え処理が不要になった場合は、ポインタをNULLに初期化しておきます。

bc_exec_asm.h
#ifndef G__NCPRO_OLDIMPLEMENTATION41
      if(store_var && '~'==funcnamebuf[0]) {
        store_varB=store_var;
        store_indexB=store_index;
        store_tagnumB=store_var->p_tagtable[store_index];
        store_var=NULL;
      }
      else if(store_varB && 0==strcmp(funcnamebuf,G__struct.name[store_tagnumB])) {
        store_varB=NULL;
      }
#endif

最後に、G__LD_FUNC命令とG__LD_IFUNC命令のエラー処理を行うif文の中に、typeの差し替え処理を追加します。

bc_exec_asm.h
      if(G__return!=G__RETURN_NON) {
#ifndef G__NCPRO_OLDIMPLEMENTATION41
        if(store_varB) {
          store_varB->type[store_indexB] = '@';
          store_varB->p_tagtable[store_indexB] = -1;
          store_varB->p_typetable[store_indexB] = -1;
        }
#endif
        G__asm_exec=0;
        return(1);
      }

以上で、バイトコードインタプリタでの実行中に変数の再宣言が行われた場合に発生していた、未初期化の変数に対してデストラクタが不正に呼び出されてしまう問題の修正は終わりです。

私がCINTのソースコードに行った他の修正の中で規模の大きい物については、今後もQiitaに解説記事を投稿する予定です。比較的簡単な修正については、Twitterで解説したいと思います。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1