Add a generic way to throw exceptions through Lua.

This changes add a new exception class that allows an exception to be
thrown through Lua, which will be used to let the VALIDATE macro work
as wanted again.

The code is not yet documented properly for two reasons:

 1. Crab_ might want to work to make Lua really exception safe.

 2. The documentation needs to refer to this commit, causing a
    chicken and egg problem.

If this code remains I'll add the documentation.
This commit is contained in:
Mark de Wever 2011-02-19 14:17:52 +00:00
parent 5606ca0319
commit d6512a0ef5
6 changed files with 184 additions and 5 deletions

View file

@ -237,6 +237,7 @@ endif(SVNVERSION_EXECUTABLE)
########### libwesnoth-lua ###############
set(libwesnoth-lua_STAT_SRC
lua_jailbreak_exception.cpp
lua/lapi.c
lua/lcode.c
lua/ldebug.c

View file

@ -1,6 +1,7 @@
# vi: syntax=python:et:ts=4
Import("env")
lua_sources = Split("""
../lua_jailbreak_exception.cpp
lapi.c
lcode.c
ldebug.c

View file

@ -8,6 +8,8 @@
#ifndef lconfig_h
#define lconfig_h
#include "lua_jailbreak_exception.hpp"
#include <limits.h>
#include <stddef.h>
@ -613,11 +615,22 @@ union luai_Cast { double l_d; long l_l; };
#if defined(__cplusplus)
/* C++ exceptions */
#define LUAI_THROW(L,c) throw(c)
#define LUAI_TRY(L,c,a) try { \
try { a } catch(const std::exception &e) \
{ lua_pushstring(L, e.what()); luaG_errormsg(L); throw; } \
} catch(...) \
{ if ((c)->status == 0) (c)->status = -1; }
#define LUAI_TRY(L,c,a) \
try { \
try { \
a \
} catch(const tlua_jailbreak_exception &e) { \
e.store(); \
throw; \
} catch(const std::exception &e) { \
lua_pushstring(L, e.what()); \
luaG_errormsg(L); \
throw; \
} \
} catch(...) { \
if((c)->status == 0) \
(c)->status = -1;\
}
#define luai_jmpbuf int /* dummy variable */
#elif defined(LUA_USE_ULONGJMP)

View file

@ -0,0 +1,64 @@
/* $Id$ */
/*
Copyright (C) 2011 by Mark de Wever <koraq@xs4all.nl>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "lua_jailbreak_exception.hpp"
#include <cassert>
#include <cstring> // Needed for NULL.
tlua_jailbreak_exception *tlua_jailbreak_exception::jailbreak_exception = NULL;
void tlua_jailbreak_exception::store() const throw()
{
/*
* It should not be possible to call this function with an exception still
* pending. It could happen if the code doesn't call
* tlua_jailbreak_exception::rethrow() or a logic error in the code.
*/
assert(!jailbreak_exception);
jailbreak_exception = this->clone();
}
void tlua_jailbreak_exception::rethrow()
{
if(!jailbreak_exception) {
return;
}
/*
* We need to call tlua_jailbreak_exception::clear() after the exception
* is thrown. The most straightforward approach would be a small helper
* class calling clear in its destructor, but alas g++ then compains about
* an unused variable. Since we're sure there will be something thrown use
* that fact to make sure the the function is called.
*/
try {
jailbreak_exception->execute();
} catch(...) {
clear();
throw;
}
/* We never should reach this point. */
assert(false);
}
void tlua_jailbreak_exception::clear() throw()
{
delete jailbreak_exception;
jailbreak_exception = NULL;
}

View file

@ -0,0 +1,98 @@
/* $Id$ */
/*
Copyright (C) 2011 by Mark de Wever <koraq@xs4all.nl>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef LUA_JAILBREAK_EXCEPTION
#define LUA_JAILBREAK_EXCEPTION
/**
* Base class for exceptions that want to be thrown 'through' lua.
*
* Classes inheriting from this class need to use the @ref
* IMPLEMENT_LUA_JAILBREAK_EXCEPTION macro in the class definition.
*/
class tlua_jailbreak_exception
{
public:
virtual ~tlua_jailbreak_exception() throw() {}
/** Stores a copy the current exception to be rethrown. */
void store() const throw();
/**
* Rethrows the stored exception.
*
* It is safe to call this function is no exception is stored.
*/
static void rethrow();
protected:
/** The exception to be rethrown. */
static tlua_jailbreak_exception* jailbreak_exception;
private:
/** Clears the current exception. */
static void clear() throw();
/**
* Creates a copy of the current exception.
*
* The copy is allocated with @p new and is implemented by the @ref
* IMPLEMENT_LUA_JAILBREAK_EXCEPTION macro.
*
* @note it's implemented by the subclass to avoid slicing.
*
* @returns A pointer to a copy of the class on the heap.
*/
virtual tlua_jailbreak_exception* clone() const = 0;
/**
* Executes the exception.
*
* Throws a copy of the stored @ref jailbreak_exception. The caller is
* responsible for clearing @ref jailbreak_exception after the throw.
* The function is implemented by the @ref
* IMPLEMENT_LUA_JAILBREAK_EXCEPTION macro.
*
* @note it's implemented by the subclass to avoid slicing.
*
* @pre jailbreak_exception != NULL
*/
virtual void execute() = 0;
};
/**
* Helper macro for classes deriving from @ref tlua_jailbreak_exception.
*
* @ref tlua_jailbreak_exception has several pure virtual functions, this
* macro implements them properly. This macro needs to be placed in the
* definition of the most derived class, which uses @ref
* tlua_jailbreak_exception as baseclass.
*
* @param type The type of the class whc
*/
#define IMPLEMENT_LUA_JAILBREAK_EXCEPTION(type) \
\
virtual type* clone() const { return new type(*this); } \
\
virtual void execute() \
{ \
type exception(dynamic_cast<type&>(*jailbreak_exception)); \
throw exception; \
}
#endif

View file

@ -42,6 +42,7 @@
#include "game_display.hpp"
#include "gamestatus.hpp"
#include "log.hpp"
#include "lua_jailbreak_exception.hpp"
#include "map.hpp"
#include "pathfind/pathfind.hpp"
#include "pathfind/teleport.hpp"
@ -430,6 +431,7 @@ bool luaW_pcall(lua_State *L
// Call the function.
int res = lua_pcall(L, nArgs, nRets, -2 - nArgs);
tlua_jailbreak_exception::rethrow();
game::exception::rethrow();
if (res)