![]() |
Programming in Lua | ![]() |
Part IV. The C API Chapter 28. User-Defined Types in C |
Our current implementation has a major security hole.
Suppose the user writes something like array.set(io.stdin, 1, 0)
.
The value in io.stdin
is a userdatum
with a pointer to a stream (FILE*
).
Because it is a userdatum,
array.set
will gladly accept it as a valid argument;
the probable result will be a memory corruption
(with luck you can get an index-out-of-range error instead).
Such behavior is unacceptable for any Lua library.
No matter how you use a C library,
it should not corrupt C data or produce a core dump from Lua.
To distinguish arrays from other userdata, we create a unique metatable for it. (Remember that userdata can also have metatables.) Then, every time we create an array, we mark it with this metatable; and every time we get an array, we check whether it has the right metatable. Because Lua code cannot change the metatable of a userdatum, it cannot fake our code.
We also need a place to store this new metatable,
so that we can access it to create new arrays and
to check whether a given userdatum is an array.
As we saw earlier,
there are two common options for storing the metatable:
in the registry,
or as an upvalue for the functions in the library.
It is customary, in Lua,
to register any new C type into the registry,
using a type name as the index and the metatable as the value.
As with any other registry index,
we must choose a type name with care, to avoid clashes.
We will call this new type "LuaBook.array"
.
As usual, the auxiliary library offers some functions to help us here. The new auxiliary functions we will use are
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);The
luaL_newmetatable
function
creates a new table (to be used as a metatable),
leaves the new table in the top of the stack,
and associates the table and the given name in the registry.
It does a dual association:
It uses the name as a key to the table
and the table as a key to the name.
(This dual association allows faster implementations for
the other two functions.)
The luaL_getmetatable
function retrieves
the metatable associated with tname
from the registry.
Finally, luaL_checkudata
checks whether the object at the
given stack position is a userdatum with a metatable that matches
the given name.
It returns NULL
if the object does not have the correct metatable
(or if it is not a userdata);
otherwise, it returns the userdata address.
Now we can start our implementation. The first step it to change the function that opens the library. The new version must create a table to be used as the metatable for arrays:
int luaopen_array (lua_State *L) { luaL_newmetatable(L, "LuaBook.array"); luaL_openlib(L, "array", arraylib, 0); return 1; }
The next step is to change newarray
so that it sets
this metatable in all arrays that it creates:
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 */ }The
lua_setmetatable
function pops a table from the stack
and sets it as the metatable of the object at the given index.
In our case, this object is the new userdatum.
Finally, setarray
, getarray
, and getsize
have to check
whether they got a valid array as their first argument.
Because we want to raise an error in case of wrong arguments,
we define the following auxiliary function:
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; }Using
checkarray
,
the new definition for getsize
is straightforward:
static int getsize (lua_State *L) { NumArray *a = checkarray(L); lua_pushnumber(L, a->size); return 1; }
Because setarray
and getarray
also share code to check the
index as their second argument,
we factor out their common parts in the following function:
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]; }After the definition of
getelem
,
setarray
and getarray
are straightforward:
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; }Now, if you try something like
array.get(io.stdin, 10)
,
you will get a proper error message:
error: bad argument #1 to `getarray' (`array' expected)
Copyright © 2003-2004 Roberto Ierusalimschy. All rights reserved. |
![]() |
![]() |