/trunk/engine/Script/Core.cpp – HoverRace

root/trunk/engine/Script/Core.cpp @ 915

Revision 915, 9.2 KB (checked in by zoogie, 6 months ago)

PrintStack(): Handle userdata and flush output stream.

  • Property svn:eol-style set to native
Line 
1
2// Core.cpp
3// Scripting support.
4//
5// Copyright (c) 2009 Michael Imamura.
6//
7// Licensed under GrokkSoft HoverRace SourceCode License v1.0(the "License");
8// you may not use this file except in compliance with the License.
9//
10// A copy of the license should have been attached to the package from which
11// you have taken this file. If you can not find the license you can not use
12// this file.
13//
14//
15// The author makes no representations about the suitability of
16// this software for any purpose.  It is provided "as is" "AS IS",
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
18// implied.
19//
20// See the License for the specific language governing permissions
21// and limitations under the License.
22
23#include "StdAfx.h"
24
25#include <iostream>
26
27// The X headers define "Bool" with breaks Boost Foreach.
28// This workaround is only required for Boost < 1.40.
29#if !defined(_WIN32) && defined(Bool)
30#   undef Bool
31#endif
32#include <boost/foreach.hpp>
33
34#include "../Util/OS.h"
35
36#include "Core.h"
37
38using namespace HoverRace::Script;
39using HoverRace::Util::OS;
40
41#define REG_LUA_LIB(st, name, fn) \
42    lua_pushcfunction((st), (fn)); \
43    lua_pushstring((st), (name)); \
44    lua_call((st), 1, 0)
45
46#define UNDEF_LUA_GLOBAL(state, name) \
47    lua_pushnil(state); \
48    lua_setglobal((state), (name))
49
50#define DISALLOW_LUA_GLOBAL(state, name) \
51    lua_pushstring((state), (name)); \
52    lua_pushcclosure((state), Core::LSandboxedFunction, 1); \
53    lua_setglobal((state), (name))
54
55Core::Core()
56{
57    state = luaL_newstate();
58
59    //TODO: Set panic handler.
60
61    Reset();
62}
63
64Core::~Core()
65{
66    lua_close(state);
67}
68
69/**
70 * Reset changes to the global environment.
71 * This is used for fixing accidental changes to globals.
72 * Note that this will effectively deactivate the security sandbox.
73 * Call ActivateSandbox() to reactivate if necessary.
74 * The state returned by GetState() is otherwise unchanged.
75 */
76void Core::Reset()
77{
78    // Register a "safe" set of standard libraries.
79    REG_LUA_LIB(state, "", luaopen_base);
80    REG_LUA_LIB(state, LUA_MATHLIBNAME, luaopen_math);
81    REG_LUA_LIB(state, LUA_STRLIBNAME, luaopen_string);
82    REG_LUA_LIB(state, LUA_TABLIBNAME, luaopen_table);
83
84    // Override the print function so we can reroute the output.
85    lua_pushlightuserdata(state, this);
86    lua_pushcclosure(state, Core::LPrint, 1);
87    lua_setglobal(state, "print");
88
89    // Remove the metatable protection from the global table.
90    // We don't need to do the same to the string metatable, since
91    // luaopen_string handles that for us.
92    lua_pushvalue(state, LUA_GLOBALSINDEX);
93    lua_pushnil(state);
94    lua_setmetatable(state, 1);
95    lua_pop(state, 1);
96}
97
98/**
99 * Activate the security sandbox.
100 * This guards against methods a script may use to access the filesystem
101 * or break out of the sandbox.
102 */
103void Core::ActivateSandbox()
104{
105    if (!lua_checkstack(state, 2))
106        throw ScriptExn("Out of stack space.");
107
108    // Create a reusable metatable protector.
109    // This prevents modification of the metatable.
110    lua_newtable(state);
111    lua_pushboolean(state, 1);
112    lua_setfield(state, -2, "__metatable");
113    int metatableProtector = luaL_ref(state, LUA_REGISTRYINDEX);
114
115    // Protect the metatable for the global table.
116    // We do this instead of just destroying/replacing the "_G" global so that
117    // users can still query the table for a list of keys, etc.
118    lua_pushvalue(state, LUA_GLOBALSINDEX);
119    lua_rawgeti(state, LUA_REGISTRYINDEX, metatableProtector);
120    lua_setmetatable(state, -2);
121    lua_pop(state, 1);
122
123    // Protect the shared metatable for strings.
124    lua_pushstring(state, "");
125    lua_getmetatable(state, -1);
126    lua_pushboolean(state, 1);
127    lua_setfield(state, -2, "__metatable");
128    lua_pop(state, 2);
129
130    // Clean up the registry.
131    luaL_unref(state, LUA_REGISTRYINDEX, metatableProtector);
132
133    // Mostly based on the list at: http://lua-users.org/wiki/SandBoxes
134    DISALLOW_LUA_GLOBAL(state, "collectgarbage");
135    DISALLOW_LUA_GLOBAL(state, "dofile");
136    DISALLOW_LUA_GLOBAL(state, "getfenv");
137    DISALLOW_LUA_GLOBAL(state, "load");
138    DISALLOW_LUA_GLOBAL(state, "loadfile");
139    DISALLOW_LUA_GLOBAL(state, "loadstring");
140    DISALLOW_LUA_GLOBAL(state, "module");
141    DISALLOW_LUA_GLOBAL(state, "rawequal");
142    DISALLOW_LUA_GLOBAL(state, "rawget");
143    DISALLOW_LUA_GLOBAL(state, "rawset");
144    DISALLOW_LUA_GLOBAL(state, "require");
145    DISALLOW_LUA_GLOBAL(state, "setfenv");
146}
147
148/**
149 * Redirect output to a stream.
150 * @param out The output stream (wrapped in a shared pointer).
151 *            May be @c NULL to use the system default.
152 * @return A handle for removing the stream later.
153 */
154Core::OutHandle Core::AddOutput(boost::shared_ptr<std::ostream> out)
155{
156    outs.push_back(out);
157    return --(outs.end());
158}
159
160void Core::RemoveOutput(const OutHandle &handle)
161{
162    outs.erase(handle);
163}
164
165/**
166 * Retrieve the full scripting version string (name and version).
167 * @return The string (never empty).
168 */
169std::string Core::GetVersionString() const
170{
171    return LUA_VERSION;
172}
173
174/**
175 * Compile a chunk of code.
176 * Upon successful execution, the compiled chunk will be pushed to the stack.
177 * @param chunk The code to compile.
178 * @throw IncompleteExn If the code does not complete a statement; i.e.,
179 *                      expecting more tokens.  Callers can catch this
180 *                      to keep reading more data to finish the statement.
181 * @throw ScriptExn The code failed to compile.
182 */
183void Core::Compile(const std::string &chunk)
184{
185    int status = luaL_loadbuffer(state, chunk.c_str(), chunk.length(), "=lua");
186    if (status != 0) {
187        std::string msg = PopError();
188        if (msg.find("<eof>") != std::string::npos) {
189            // Incomplete chunk.
190            // Callers can use this to keep reading more data.
191            throw IncompleteExn(msg);
192        }
193        else {
194            // Other compilation error.
195            throw ScriptExn(msg);
196        }
197    }
198}
199
200/**
201 * Pop a function off the stack and execute it, printing any return values.
202 * @throw ScriptExn The code signaled an error while executing.
203 */
204void Core::CallAndPrint()
205{
206    int initStack = lua_gettop(state);
207    if (initStack == 0) {
208        throw ScriptExn("No function on stack.");
209    }
210    --initStack;
211
212    // Execute the chunk.
213    int status = lua_pcall(state, 0, LUA_MULTRET, 0);
214    if (status != 0) {
215        throw ScriptExn(PopError());
216    }
217
218    int numReturns = lua_gettop(state) - initStack;
219
220    // If there were any return values, pass them to print().
221    if (numReturns > 0) {
222        lua_getglobal(state, "print");
223        lua_insert(state, initStack + 1);
224        status = lua_pcall(state, numReturns, 0, 0);
225        if (status != 0) {
226            throw ScriptExn(PopError());
227        }
228    }
229}
230
231/**
232 * Compile and execute a chunk of code.
233 * @param chunk The code to execute.
234 * @throw IncompleteExn If the code does not complete a statement; i.e.,
235 *                      expecting more tokens.  Callers can catch this
236 *                      to keep reading more data to finish the statement.
237 * @throw ScriptExn The code either failed to compile or signaled an error
238 *                  while executing.
239 */
240void Core::Execute(const std::string &chunk)
241{
242    Compile(chunk);
243    CallAndPrint();
244}
245
246void Core::PrintStack()
247{
248    int num = lua_gettop(state);
249    std::ostringstream oss;
250    for (int i = 1; i <= num; ++i) {
251        oss << i << ": ";
252        int type = lua_type(state, i);
253        switch (type) {
254            case LUA_TBOOLEAN: oss << "bool<" << (lua_toboolean(state, i) ? "true" : "false") << '>'; break;
255            case LUA_TFUNCTION: oss << "function"; break;
256            case LUA_TLIGHTUSERDATA: oss << "lightuserdata"; break;
257            case LUA_TNIL: oss << "nil"; break;
258            case LUA_TNUMBER: oss << "number<" << lua_tonumber(state, i) << '>'; break;
259            case LUA_TSTRING: oss << "string<" << lua_tostring(state, i) << '>'; break;
260            case LUA_TTABLE: oss << "table"; break;
261            case LUA_TTHREAD: oss << "thread"; break;
262            case LUA_TUSERDATA: oss << "userdata"; break;
263            default: oss << "unknown type: " << type; break;
264        }
265        oss << std::endl;
266    }
267    std::string s = oss.str();
268    BOOST_FOREACH(boost::shared_ptr<std::ostream> &out, outs) {
269        *out << s << std::flush;
270    }
271}
272
273/**
274 * Pop the error message off the stack.
275 * Only call this if you KNOW that there is an error on the top of the stack.
276 * @return The error as a string.
277 */
278std::string Core::PopError()
279{
280    size_t len;
281    const char *s = lua_tolstring(state, -1, &len);
282    std::string msg(s, len);
283    lua_pop(state, 1);
284    return msg;
285}
286
287int Core::LPrint(lua_State *state)
288{
289    Core *self = static_cast<Core*>(lua_touserdata(state, lua_upvalueindex(1)));
290
291    int numParams = lua_gettop(state);
292
293    // Note: This aims to be pretty close to the print() function from the Lua
294    //       base lib, including allowing the global tostring() to be
295    //       replaced (e.g. to support additional types).
296
297    lua_getglobal(state, "tostring");
298
299    for (int i = 1; i <= numParams; ++i) {
300        // Call the global "tostring" function
301        lua_pushvalue(state, -1);
302        lua_pushvalue(state, i);
303        lua_call(state, 1, 1);
304
305        // Get the result.
306        const char *s = lua_tostring(state, -1);
307        if (s == NULL) {
308            // tostring() returned a non-string result.
309            // This can happen if the tostring function is replaced.
310            ASSERT(false);
311            lua_pop(state, 1);
312            continue;
313        }
314        BOOST_FOREACH(boost::shared_ptr<std::ostream> &oss, self->outs) {
315            if (i > 1) *oss << '\t';
316            *oss << s;
317        }
318        lua_pop(state, 1);
319    }
320    BOOST_FOREACH(boost::shared_ptr<std::ostream> &oss, self->outs) {
321        *oss << std::endl;
322    }
323
324    return 0;
325}
326
327int Core::LSandboxedFunction(lua_State *state)
328{
329    const char *name = lua_tostring(state, lua_upvalueindex(1));
330    return luaL_error(state, "Disallowed access to protected function: %s", name);
331}
Note: See TracBrowser for help on using the browser.