以前の記事
本記事で扱うCINTのバージョンについて
ROOT 5で使用されているCINTは最終バージョンの5.18ですが、私が自作ツールでCINTを扱い始めた当時の最新バージョンである5.15を長年使用しているため、このバージョンのソースコードで解説したいと思います。
変数管理テーブル
まずは、CINTの変数管理テーブルG__var_array
構造体の定義から見ていきます。本記事では、デストラクタ不正呼び出し問題の修正において重要と思われるメンバー変数のみ解説します。
/**************************************************************************
* structure for variable table
*
**************************************************************************/
struct G__var_array {
/* union for variable pointer */
long p[G__MEMDEPTH]; /* used to be int */
int allvar;
#ifndef G__OLDIMPLEMENTATION1543
char *varnamebuf[G__MEMDEPTH]; /* variable name */
#else
char varnamebuf[G__MEMDEPTH][G__MAXNAME]; /* variable name */
#endif
int hash[G__MEMDEPTH]; /* hash table of varname */
int varlabel[G__MEMDEPTH+1][G__MAXVARDIM]; /* points varpointer */
short paran[G__MEMDEPTH];
char bitfield[G__MEMDEPTH];
#ifdef G__VARIABLEFPOS
int filenum[G__MEMDEPTH];
int linenum[G__MEMDEPTH];
#endif
/* type information,
if pointer : Char,Int,Short,Long,Double,U(struct,union)
if value : char,int,short,long,double,u(struct,union) */
G__SIGNEDCHAR_T type[G__MEMDEPTH];
G__SIGNEDCHAR_T constvar[G__MEMDEPTH];
short p_tagtable[G__MEMDEPTH]; /* tagname if struct,union */
short p_typetable[G__MEMDEPTH]; /* typename if typedef */
short statictype[G__MEMDEPTH];
G__SIGNEDCHAR_T reftype[G__MEMDEPTH];
/* chain for next G__var_array */
struct G__var_array *next;
G__SIGNEDCHAR_T access[G__MEMDEPTH]; /* private, protected, public */
#ifdef G__SHOWSTACK /* not activated */
struct G__ifunc_table *ifunc;
int ifn;
struct G__var_array *prev_local;
int prev_filenum;
short prev_line_number;
long struct_offset;
int tagnum;
int exec_memberfunc;
#endif
#ifdef G__VAARG
struct G__param *libp;
#endif
#ifndef G__NEWINHERIT
char isinherit[G__MEMDEPTH];
#endif
G__SIGNEDCHAR_T globalcomp[G__MEMDEPTH];
#ifdef G__FONS_COMMENT
struct G__comment_info comment[G__MEMDEPTH];
#endif
#ifndef G__OLDIMPLEMENTATION2038
struct G__var_array *enclosing_scope;
struct G__var_array **inner_scope;
#endif
} ;
allvar
には登録済みの変数の個数が格納されています。変数がG__MEMDEPTH
個を超えた場合は新たにsizeof(struct G__var_array)
バイトの領域が確保され、next
がその領域を指します。
type
は登録済みの変数の型情報を格納するための配列で、型の種類に対応する値は以下の表のとおりです。例えばchar*
であれば'C'
が格納されます。
type | 型の種類 |
---|---|
'c' | char |
'b' | unsigned char |
's' | short |
'r' | unsigned short |
'i' | int |
'h' | unsigned int |
'l' | long |
'k' | unsigned long |
'g' | bool |
'f' | float |
'd' | double |
'u' | class / struct / union |
大文字 | ポインタ型 |
'E' | FILE* |
'Y' | void* |
p_tagtable
はユーザー定義型の管理IDを格納するための配列で、type
の値が'u'
または'U'
の場合にのみ意味を持ちます。有効な場合は0以上の値が、無効な場合は-1が格納されます。
解放処理
次に、変数の解放処理を行うG__destroy
関数を見ていきます。この関数はインタプリタ上で実行された関数が正常終了したか、それとも構文エラー等で中断されたかに関わらず呼び出されます。1番目の引数は変数管理テーブルへのポインタで、2番目の引数はグローバル変数の管理テーブルかどうかを表すフラグです。
/***********************************************************************
* void G__destroy(var,isglobal)
***********************************************************************/
/* destroy local variable and free memory*/
void G__destroy(var,isglobal)
struct G__var_array *var;
int isglobal;
{
登録されている変数がG__MEMDEPTH
個を超える場合は、G__destroy
関数を再帰的に呼び出します。その後のfor文で、登録時とは逆順に変数を解放していきます。
/*******************************************
* If there are any sub var array list,
* destroy it too.
*******************************************/
if(var->next) {
#ifndef G__OLDIMPLEMENTATION1081
if(var->allvar==G__MEMDEPTH) {
#endif
G__destroy(var->next,isglobal);
free((void*)var->next);
var->next=NULL;
#ifndef G__OLDIMPLEMENTATION1081
}
else {
fprintf(stderr,"!!!Fatal Error: Interpreter memory overwritten by illegal access.!!!\n");
fprintf(stderr,"!!!Terminate session!!!\n");
}
#endif
}
for(itemp=var->allvar-1;itemp>=remain;itemp--) {
以下が、for文の中でデストラクタを呼び出すかどうかを判断している箇所です。未初期化の変数だった場合は、if文の中が実行されないようにする必要があります。
/****************************************************
* If C++ class, destructor has to be called
****************************************************/
if(var->type[itemp]=='u' && 0==G__ansiheader && 0==G__prerun) {
私は以下の2つの修正方法を考えました。
-
G__var_array
構造体にコンストラクタが呼び出し済みかどうかを表すメンバー変数を追加する。 - コンストラクタの呼び出し前にエラー終了する場合は
type
の値を'u'
以外に変更する。
この時は、修正箇所が少なくて済みそうだという理由で2番目の方法を選択しました。この安易な選択のせいで後々苦労することになったのかもしれません。
生成処理
最後に、変数を生成するために定義文を処理するG__define_var
関数を見ていきます。1番目の引数はユーザー定義型の管理IDで、2番目の引数はtypedefされた型名の管理IDです。この関数に、エラー発生時にtype
の値を差し替える処理を追加する必要があります。
/******************************************************************
* void G__define_var(tagnum,typenum)
*
* Called by
* G__DEFVAR macro
* G__DEFREFVAR macro
* G__define_struct()
* G__define_type()
* G__define_type()
*
* Declaration of variable, function or ANSI function header
*
* variable: type varname1, varname2=initval ;
* ^
* function: type funcname(param decl) { body }
* ^
* ANSI function header: funcname( type para1, type para2,...)
* ^ or ^
******************************************************************/
void G__define_var(tagnum,typenum)
int tagnum,typenum; /* overrides global variables */
{
G__value reg;
関数内で使用されているG__value
構造体の定義は以下の通りです。メンバー変数type
の意味はG__var_array
構造体のtype
と同じで、tagnum
の意味はp_tagtable
と同じです。
/**************************************************************************
* struct of internal data
*
**************************************************************************/
typedef struct {
union {
double d;
long i; /* used to be int */
struct G__p2p reftype;
#ifndef G__OLDIMPLEMENTATION845
char ch;
short sh;
int in;
float fl;
unsigned char uch;
unsigned short ush;
unsigned int uin;
unsigned long ulo;
#endif
#ifndef G__OLDIMPLEMENTATION2189
G__int64 ll;
G__uint64 ull;
long double ld;
#endif
} obj;
int type;
int tagnum;
int typenum;
#ifdef G__REFERENCETYPE2
long ref;
#endif
#ifndef G__OLDIMPLEMENTATION1259
G__SIGNEDCHAR_T isconst;
#endif
} G__value ;
以下が、メモリの確保と変数管理テーブルへの登録を行っている箇所です。コンパイル済みライブラリに含まれている型だった場合は、まだ処理が行われません。
この時点ではreg.type
の値が'\0'
に設定されているため、G__letvariable
関数はコンストラクタの呼び出しを行いません。
/************************************************************
* 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;
}
次に、コンストラクタに引数として渡される式の評価がG__getexpr
関数によって行われます。
reg=G__getexpr(temp);
最後に、コンストラクタの呼び出しが行われます。コンパイル済みライブラリに含まれている型とインタプリタで処理される型で処理が分かれています。本記事では、前者の処理については省略します。
エラーが無ければ、reg.type
とreg.tagnum
の値がG__getexpr
関数で評価された式の結果の型に設定されているため、G__letvariable
関数はコンストラクタの呼び出しを行います。その後のif文で、コンストラクタの呼び出しがエラーになった場合の後始末を行っています。
if(G__CPPLINK==G__struct.iscpplink[tagnum]) {
/* 略 */
}
else {
#ifndef G__OLDIMPLEMENTATION1073
if(G__asm_wholefunction) {
G__oprovld=1;
}
#endif
G__letvariable(new_name ,reg ,&G__global ,G__p_local);
#ifndef G__OLDIMPLEMENTATION1073
if(G__asm_wholefunction) {
G__oprovld=0;
#ifdef G__ASM_DBG
if(G__asm_dbg) G__fprinterr(G__serr,"%3x: POPSTROS\n",G__asm_cp);
#endif
G__asm_inst[G__asm_cp]=G__POPSTROS;
G__inc_cp_asm(1,0);
}
#endif
}
if(G__return>G__RETURN_NORMAL) {
G__decl=store_decl;
G__constvar=0;
G__tagnum = store_tagnum;
G__typenum = store_typenum;
G__reftype=G__PARANORMAL;
G__static_alloc=store_static_alloc2;
#ifndef G__OLDIMPLEMENTATION1119
G__dynconst=0;
#endif
#ifndef G__OLDIMPLEMENTATION1322
G__globalvarpointer = G__PVOID;
#endif
#ifndef G__PHILIPPE21
G__prerun = store_prerun;
#endif
return;
}
修正
エラー発生時にtype
の値を差し替える処理は、G__getexpr
関数の呼び出しとコンストラクタの呼び出しの間に追加します。以下のように、差し替え後に後始末をして関数を終了しています。
差し替えに使用した値は'u'
以外であれば何でも良かったのですが、他の型と被らないように記号を使用しました。
#ifndef G__NCPRO_OLDIMPLEMENTATION39
if(G__return>G__RETURN_NORMAL) {
G__hash(name,hash,i)
var = G__getvarentry(name,hash,&ig15,&G__global,G__p_local);
if(var) {
var->type[ig15] = '@';
var->p_tagtable[ig15] = -1;
var->p_typetable[ig15] = -1;
}
G__decl=store_decl;
G__constvar=0;
G__tagnum = store_tagnum;
G__typenum = store_typenum;
G__reftype=G__PARANORMAL;
G__static_alloc=store_static_alloc2;
#ifndef G__OLDIMPLEMENTATION1119
G__dynconst=0;
#endif
#ifndef G__OLDIMPLEMENTATION1322
G__globalvarpointer = G__PVOID;
#endif
#ifndef G__PHILIPPE21
G__prerun = store_prerun;
#endif
return;
}
#endif
それと、追加された処理内で使用している変数の宣言文と初期化処理を、ユーザー定義型の処理を行っているスコープの先頭部分に追加する必要があります。
/**************************************************************
* 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;
strcpy(name,new_name);
p=strchr(name,'[');
if(p) *p='\0';
#endif
以上で、変数の定義文で式の評価がエラーになった場合に発生していた、未初期化の変数に対してデストラクタが不正に呼び出されてしまう問題の修正は終わりです。
次回の記事(その3)では、別のケースで発生するデストラクタ不正呼び出し問題について解説したいと思います。