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