1 // Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
2 //
3 // Permission to use, copy, modify, and distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 //
15 // Aegisub Project http://www.aegisub.org/
16
17 #include "libaegisub/lua/utils.h"
18
19 #include "libaegisub/format.h"
20 #include "libaegisub/log.h"
21
22 #include <boost/algorithm/string/join.hpp>
23 #include <boost/algorithm/string/predicate.hpp>
24 #include <boost/range/adaptor/reversed.hpp>
25 #include <boost/regex.hpp>
26
27 #ifdef _MSC_VER
28 // Disable warnings for noreturn functions having return types
29 #pragma warning(disable: 4645 4646)
30 #endif
31
32 namespace agi { namespace lua {
get_string_or_default(lua_State * L,int idx)33 std::string get_string_or_default(lua_State *L, int idx) {
34 size_t len = 0;
35 const char *str = lua_tolstring(L, idx, &len);
36 if (!str)
37 return "<not a string>";
38 return std::string(str, len);
39 }
40
get_string(lua_State * L,int idx)41 std::string get_string(lua_State *L, int idx) {
42 size_t len = 0;
43 const char *str = lua_tolstring(L, idx, &len);
44 return std::string(str ? str : "", len);
45 }
46
get_global_string(lua_State * L,const char * name)47 std::string get_global_string(lua_State *L, const char *name) {
48 lua_getglobal(L, name);
49 std::string ret;
50 if (lua_isstring(L, -1))
51 ret = lua_tostring(L, -1);
52 lua_pop(L, 1);
53 return ret;
54 }
55
check_string(lua_State * L,int idx)56 std::string check_string(lua_State *L, int idx) {
57 size_t len = 0;
58 const char *str = lua_tolstring(L, idx, &len);
59 if (!str) typerror(L, idx, "string");
60 return std::string(str, len);
61 }
62
check_int(lua_State * L,int idx)63 int check_int(lua_State *L, int idx) {
64 auto v = lua_tointeger(L, idx);
65 if (v == 0 && !lua_isnumber(L, idx))
66 typerror(L, idx, "number");
67 return v;
68 }
69
check_uint(lua_State * L,int idx)70 size_t check_uint(lua_State *L, int idx) {
71 auto v = lua_tointeger(L, idx);
72 if (v == 0 && !lua_isnumber(L, idx))
73 typerror(L, idx, "number");
74 if (v < 0)
75 argerror(L, idx, "must be >= 0");
76 return static_cast<size_t>(v);
77 }
78
check_udata(lua_State * L,int idx,const char * mt)79 void *check_udata(lua_State *L, int idx, const char *mt) {
80 void *p = lua_touserdata(L, idx);
81 if (!p) typerror(L, idx, mt);
82 if (!lua_getmetatable(L, idx)) typerror(L, idx, mt);
83
84 lua_getfield(L, LUA_REGISTRYINDEX, mt);
85 if (!lua_rawequal(L, -1, -2)) typerror(L, idx, mt);
86
87 lua_pop(L, 2);
88 return p;
89 }
90
moon_line(lua_State * L,int lua_line,std::string const & file)91 static int moon_line(lua_State *L, int lua_line, std::string const& file) {
92 if (luaL_dostring(L, "return require 'moonscript.line_tables'")) {
93 lua_pop(L, 1); // pop error message
94 return lua_line;
95 }
96
97 push_value(L, file);
98 lua_rawget(L, -2);
99
100 if (!lua_istable(L, -1)) {
101 lua_pop(L, 2);
102 return lua_line;
103 }
104
105 lua_rawgeti(L, -1, lua_line);
106 if (!lua_isnumber(L, -1)) {
107 lua_pop(L, 3);
108 return lua_line;
109 }
110
111 auto char_pos = static_cast<size_t>(lua_tonumber(L, -1));
112 lua_pop(L, 3);
113
114 // The moonscript line tables give us a character offset into the file,
115 // so now we need to map that to a line number
116 lua_getfield(L, LUA_REGISTRYINDEX, ("raw moonscript: " + file).c_str());
117 if (!lua_isstring(L, -1)) {
118 lua_pop(L, 1);
119 return lua_line;
120 }
121
122 size_t moon_len;
123 auto moon = lua_tolstring(L, -1, &moon_len);
124 return std::count(moon, moon + std::min(moon_len, char_pos), '\n') + 1;
125 }
126
add_stack_trace(lua_State * L)127 int add_stack_trace(lua_State *L) {
128 int level = 1;
129 if (lua_isnumber(L, 2)) {
130 level = (int)lua_tointeger(L, 2);
131 lua_pop(L, 1);
132 }
133
134 const char *err = lua_tostring(L, 1);
135 if (!err) return 1;
136
137 std::string message = err;
138 if (lua_gettop(L))
139 lua_pop(L, 1);
140
141 // Strip the location from the error message since it's redundant with
142 // the stack trace
143 boost::regex location(R"(^\[string ".*"\]:[0-9]+: )");
144 message = regex_replace(message, location, "", boost::format_first_only);
145
146 std::vector<std::string> frames;
147 frames.emplace_back(std::move(message));
148
149 lua_Debug ar;
150 while (lua_getstack(L, level++, &ar)) {
151 lua_getinfo(L, "Snl", &ar);
152
153 if (ar.what[0] == 't')
154 frames.emplace_back("(tail call)");
155 else {
156 bool is_moon = false;
157 std::string file = ar.source;
158 if (file == "=[C]")
159 file = "<C function>";
160 else if (boost::ends_with(file, ".moon"))
161 is_moon = true;
162
163 auto real_line = [&](int line) {
164 return is_moon ? moon_line(L, line, file) : line;
165 };
166
167 std::string function = ar.name ? ar.name : "";
168 if (*ar.what == 'm')
169 function = "<main>";
170 else if (*ar.what == 'C')
171 function = '?';
172 else if (!*ar.namewhat)
173 function = agi::format("<anonymous function at lines %d-%d>", real_line(ar.linedefined), real_line(ar.lastlinedefined - 1));
174
175 frames.emplace_back(agi::format(" File \"%s\", line %d\n%s", file, real_line(ar.currentline), function));
176 }
177 }
178
179 push_value(L, join(frames | boost::adaptors::reversed, "\n"));
180
181 return 1;
182 }
183
error(lua_State * L,const char * fmt,...)184 int BOOST_NORETURN error(lua_State *L, const char *fmt, ...) {
185 va_list argp;
186 va_start(argp, fmt);
187 luaL_where(L, 1);
188 lua_pushvfstring(L, fmt, argp);
189 va_end(argp);
190 lua_concat(L, 2);
191 throw error_tag();
192 }
193
argerror(lua_State * L,int narg,const char * extramsg)194 int BOOST_NORETURN argerror(lua_State *L, int narg, const char *extramsg) {
195 lua_Debug ar;
196 if (!lua_getstack(L, 0, &ar))
197 error(L, "bad argument #%d (%s)", narg, extramsg);
198 lua_getinfo(L, "n", &ar);
199 if (strcmp(ar.namewhat, "method") == 0 && --narg == 0)
200 error(L, "calling '%s' on bad self (%s)", ar.name, extramsg);
201 if (!ar.name) ar.name = "?";
202 error(L, "bad argument #%d to '%s' (%s)",
203 narg, ar.name, extramsg);
204 }
205
typerror(lua_State * L,int narg,const char * tname)206 int BOOST_NORETURN typerror(lua_State *L, int narg, const char *tname) {
207 const char *msg = lua_pushfstring(L, "%s expected, got %s",
208 tname, luaL_typename(L, narg));
209 argerror(L, narg, msg);
210 }
211
argcheck(lua_State * L,bool cond,int narg,const char * msg)212 void argcheck(lua_State *L, bool cond, int narg, const char *msg) {
213 if (!cond) argerror(L, narg, msg);
214 }
215
216 #ifdef _DEBUG
check_stack(int additional)217 void LuaStackcheck::check_stack(int additional) {
218 int top = lua_gettop(L);
219 if (top - additional != startstack) {
220 LOG_D("automation/lua") << "lua stack size mismatch.";
221 dump();
222 assert(top - additional == startstack);
223 }
224 }
225
dump()226 void LuaStackcheck::dump() {
227 int top = lua_gettop(L);
228 LOG_D("automation/lua/stackdump") << "--- dumping lua stack...";
229 for (int i = top; i > 0; i--) {
230 lua_pushvalue(L, i);
231 std::string type(lua_typename(L, lua_type(L, -1)));
232 if (lua_isstring(L, i))
233 LOG_D("automation/lua/stackdump") << type << ": " << lua_tostring(L, -1);
234 else
235 LOG_D("automation/lua/stackdump") << type;
236 lua_pop(L, 1);
237 }
238 LOG_D("automation/lua") << "--- end dump";
239 }
240 #endif
241
242 }
243 }
244