This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.
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. |