初版は、Lua 5.0向けに書かれました。それ以降のバージョンでもほとんどは関連性がありますが、いくつかの違いがあります。
第4版はLua 5.3をターゲットとしており、Amazonやその他の書店から入手できます。
本書を購入することで、Luaプロジェクトのサポートにも貢献できます。


28.2 – メタテーブル

現在のインプリメンテーションには重大なセキュリティホールがあります。ユーザーがarray.set(io.stdin, 1, 0)のように記述すると思うとします。io.stdinの値は、ストリーム(FILE*)へのポインタを持ったユーザーデータです。ユーザーデータだから、array.setは喜んでこれを有効な引数として受け入れます。考えられる結果はメモリクリーニングです(運がよければ、インデックスが範囲外というエラーになるかもしれません)。そのような動作は、Luaライブラリでは許容できません。Cライブラリをどのように使おうと、Cデータをクリーニングしたり、Luaからコアダンプを生成したりするべきではありません。

配列を他のユーザーデータと区別するために、配列に一意のメタテーブルを作成します。(ユーザーデータにもメタテーブルを使用できることを思い出してください)。その後、配列を作成するたび、このメタテーブルでマークします。また、配列を受け取るたび、この配列に正しいメタテーブルがあるかを確認します。Luaコードはユーザーデータのメタテーブルを変更できないので、コードを偽装することはできません。

また、新しいメタテーブルを格納する場所も必要です。そうすることで、新しい配列を作成したり、特定のユーザーデータが配列かどうかを確認したりするためにアクセスできます。前述のように、メタテーブルを格納するための一般的なオプションが2つあります。レジストリ、またはライブラリ内の関数のアップバリュです。Luaでは、通常、新しいCタイプはレジストリに登録し、タイプ名をインデックス、メタテーブルを値として使用します。他のどのレジストリインデックスの場合と同様に、タイプ名は慎重に選択して、競合を避ける必要があります。この新しいタイプは「LuaBook.array」と呼びます。

いつものように、補助ライブラリはいくつかの関数を提供し、ここでの支援を行います。使用する新しい補助関数は次のとおりです。

    int   luaL_newmetatable (lua_State *L, const char *tname);
    void  luaL_getmetatable (lua_State *L, const char *tname);
    void *luaL_checkudata (lua_State *L, int index,
                                         const char *tname);
luaL_newmetatable関数は新しいテーブルを作成し(メタテーブルとして使用されます)、新しいテーブルをスタックの先頭に格納し、テーブルとレジストリ内の指定名を関連付けます。これは二重結び付けを行います。名前をテーブルのキーとして使用し、テーブルを名前のキーとして使用します。(この二重結び付けにより、他の2つの関数のインプリメンテーションが高速になります)。luaL_getmetatable関数は、レジストリからtnameに関連付けられているメタテーブルを取得します。最後に、luaL_checkudataは、指定したスタック位置にあるオブジェクトが、指定した名前に一致するメタテーブルを持ったユーザーデータであるかどうかをチェックします。オブジェクトに正しいメタテーブルがない場合(または、ユーザーデータではない場合)はNULLを返します。それ以外の場合は、ユーザーデータアドレスを返します。

これで、インプリメンテーションを開始できます。最初のステップは、ライブラリを開く関数を変更することです。新しいバージョンでは、配列のメタテーブルとして使用されるテーブルを作成する必要があります。

    int luaopen_array (lua_State *L) {
      luaL_newmetatable(L, "LuaBook.array");
      luaL_openlib(L, "array", arraylib, 0);
      return 1;
    }

次のステップは、newarrayを変更して、作成するすべての配列にメタテーブルを設定するようにします。

    static int newarray (lua_State *L) {
      int n = luaL_checkint(L, 1);
      size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
      NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
    
      luaL_getmetatable(L, "LuaBook.array");
      lua_setmetatable(L, -2);
    
      a->size = n;
      return 1;  /* new userdatum is already on the stack */
    }
lua_setmetatable関数はスタックからテーブルをポップし、指定されたインデックスにあるオブジェクトのメタテーブルとして設定します。この場合は、新しいユーザーデータです。

最後に、setarraygetarray、およびgetsizeは、最初の引数として有効な配列が渡されているかどうかを確認する必要があります。間違った引数の場合にはエラーを発生させたいので、次の補助関数を定義します。

    static NumArray *checkarray (lua_State *L) {
      void *ud = luaL_checkudata(L, 1, "LuaBook.array");
      luaL_argcheck(L, ud != NULL, 1, "`array' expected");
      return (NumArray *)ud;
    }
checkarray を使用すると、getsize の新しい定義は単純明瞭になります
    static int getsize (lua_State *L) {
      NumArray *a = checkarray(L);
      lua_pushnumber(L, a->size);
      return 1;
    }

setarraygetarray も 2 番目の引数としてインデックスをチェックするコードを共有するため、以下の関数で共通部分を抽出します

    static double *getelem (lua_State *L) {
      NumArray *a = checkarray(L);
      int index = luaL_checkint(L, 2);
    
      luaL_argcheck(L, 1 <= index && index <= a->size, 2,
                       "index out of range");
    
      /* return element address */
      return &a->values[index - 1];
    }
getelem を定義した後、setarraygetarray は単純明瞭です
    static int setarray (lua_State *L) {
      double newvalue = luaL_checknumber(L, 3);
      *getelem(L) = newvalue;
      return 0;
    }
    
    static int getarray (lua_State *L) {
      lua_pushnumber(L, *getelem(L));
      return 1;
    }
ここで、array.get(io.stdin, 10) のようなコードを実行しようとしても、適切なエラーメッセージが表示されます
    error: bad argument #1 to `getarray' (`array' expected)