print tracebacks and line numbers for Lua runtime errors

This commit is contained in:
Matt Bhagat-Conway 2023-03-03 08:31:59 -05:00 committed by Matt Bhagat-Conway
parent 192d077ada
commit 76e2d48e1b
No known key found for this signature in database
GPG Key ID: EB64FF36E7DA441A
2 changed files with 115 additions and 59 deletions

View File

@ -41,10 +41,10 @@ struct LuaScriptingContext final
bool has_way_function = false;
bool has_segment_function = false;
sol::function turn_function;
sol::function way_function;
sol::function node_function;
sol::function segment_function;
sol::protected_function turn_function;
sol::protected_function way_function;
sol::protected_function node_function;
sol::protected_function segment_function;
int api_version = 4;
sol::table profile_table;

View File

@ -91,6 +91,19 @@ struct to_lua_object : public boost::static_visitor<sol::object>
};
} // namespace
// Handle a lua error thrown in a protected function by printing the traceback and bubbling
// exception up to caller. Lua errors are generally unrecoverable, so this exception should not be
// caught but instead should terminate the process. The point of having this error handler rather
// than just using unprotected Lua functions which terminate the process automatically is that this
// function provides more useful error messages including Lua tracebacks and line numbers.
void handle_lua_error(sol::protected_function_result &luares)
{
sol::error luaerr = luares;
std::string msg = luaerr.what();
std::cerr << msg << std::endl;
throw util::exception("Lua error (see stderr for traceback)");
}
Sol2ScriptingEnvironment::Sol2ScriptingEnvironment(
const std::string &file_name,
const std::vector<boost::filesystem::path> &location_dependent_data_paths)
@ -232,7 +245,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"valid",
&osmium::Location::valid);
auto get_location_tag = [](auto &context, const auto &location, const char *key) {
auto get_location_tag = [](auto &context, const auto &location, const char *key)
{
if (context.location_dependent_data.empty())
return sol::object(context.state);
@ -259,7 +273,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"get_nodes",
[](const osmium::Way &way) { return sol::as_table(&way.nodes()); },
"get_location_tag",
[&context, &get_location_tag](const osmium::Way &way, const char *key) {
[&context, &get_location_tag](const osmium::Way &way, const char *key)
{
// HEURISTIC: use a single node (last) of the way to localize the way
// For more complicated scenarios a proper merging of multiple tags
// at one or many locations must be provided
@ -279,9 +294,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"version",
&osmium::Node::version,
"get_location_tag",
[&context, &get_location_tag](const osmium::Node &node, const char *key) {
return get_location_tag(context, node.location(), key);
});
[&context, &get_location_tag](const osmium::Node &node, const char *key)
{ return get_location_tag(context, node.location(), key); });
context.state.new_enum("traffic_lights",
"none",
@ -297,7 +311,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"ResultNode",
"traffic_lights",
sol::property([](const ExtractionNode &node) { return node.traffic_lights; },
[](ExtractionNode &node, const sol::object &obj) {
[](ExtractionNode &node, const sol::object &obj)
{
if (obj.is<bool>())
{
// The old approach of assigning a boolean traffic light
@ -348,7 +363,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
sol::property(&ExtractionWay::GetName, &ExtractionWay::SetName),
"ref", // backward compatibility
sol::property(&ExtractionWay::GetForwardRef,
[](ExtractionWay &way, const char *ref) {
[](ExtractionWay &way, const char *ref)
{
way.SetForwardRef(ref);
way.SetBackwardRef(ref);
}),
@ -407,7 +423,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
sol::property([](const ExtractionWay &way) { return way.access_turn_classification; },
[](ExtractionWay &way, int flag) { way.access_turn_classification = flag; }));
auto getTypedRefBySol = [](const sol::object &obj) -> ExtractionRelation::OsmIDTyped {
auto getTypedRefBySol = [](const sol::object &obj) -> ExtractionRelation::OsmIDTyped
{
if (obj.is<osmium::Way>())
{
osmium::Way *way = obj.as<osmium::Way *>();
@ -443,20 +460,19 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"get_value_by_key",
[](ExtractionRelation &rel, const char *key) -> const char * { return rel.GetAttr(key); },
"get_role",
[&getTypedRefBySol](ExtractionRelation &rel, const sol::object &obj) -> const char * {
return rel.GetRole(getTypedRefBySol(obj));
});
[&getTypedRefBySol](ExtractionRelation &rel, const sol::object &obj) -> const char *
{ return rel.GetRole(getTypedRefBySol(obj)); });
context.state.new_usertype<ExtractionRelationContainer>(
"ExtractionRelationContainer",
"get_relations",
[&getTypedRefBySol](ExtractionRelationContainer &cont, const sol::object &obj)
-> const ExtractionRelationContainer::RelationIDList & {
return cont.GetRelations(getTypedRefBySol(obj));
},
-> const ExtractionRelationContainer::RelationIDList &
{ return cont.GetRelations(getTypedRefBySol(obj)); },
"relation",
[](ExtractionRelationContainer &cont, const ExtractionRelation::OsmIDTyped &rel_id)
-> const ExtractionRelation & { return cont.GetRelationData(rel_id); });
[](ExtractionRelationContainer &cont,
const ExtractionRelation::OsmIDTyped &rel_id) -> const ExtractionRelation &
{ return cont.GetRelationData(rel_id); });
context.state.new_usertype<ExtractionSegment>("ExtractionSegment",
"source",
@ -472,10 +488,12 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
// Keep in mind .location is available only if .pbf is preprocessed to set the location with the
// ref using osmium command "osmium add-locations-to-ways"
context.state.new_usertype<osmium::NodeRef>(
"NodeRef", "id", &osmium::NodeRef::ref, "location", [](const osmium::NodeRef &nref) {
return nref.location();
});
context.state.new_usertype<osmium::NodeRef>("NodeRef",
"id",
&osmium::NodeRef::ref,
"location",
[](const osmium::NodeRef &nref)
{ return nref.location(); });
context.state.new_usertype<InternalExtractorEdge>("EdgeSource",
"source_coordinate",
@ -531,7 +549,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
util::Log() << "Using profile api version " << context.api_version;
// version-dependent parts of the api
auto initV2Context = [&]() {
auto initV2Context = [&]()
{
// clear global not used in v2
context.state["properties"] = sol::nullopt;
@ -550,10 +569,16 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
std::numeric_limits<TurnPenalty::value_type>::max());
// call initialize function
sol::function setup_function = function_table.value()["setup"];
sol::protected_function setup_function = function_table.value()["setup"];
if (!setup_function.valid())
throw util::exception("Profile must have an setup() function.");
sol::optional<sol::table> profile_table = setup_function();
auto setup_result = setup_function();
if (!setup_result.valid())
handle_lua_error(setup_result);
sol::optional<sol::table> profile_table = setup_result;
if (profile_table == sol::nullopt)
throw util::exception("Profile setup() must return a table.");
else
@ -616,13 +641,16 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
}
};
auto initialize_V3_extraction_turn = [&]() {
auto initialize_V3_extraction_turn = [&]()
{
context.state.new_usertype<ExtractionTurn>(
"ExtractionTurn",
"angle",
&ExtractionTurn::angle,
"turn_type",
sol::property([](const ExtractionTurn &turn) {
sol::property(
[](const ExtractionTurn &turn)
{
if (turn.number_of_roads > 2 || turn.source_mode != turn.target_mode ||
turn.is_u_turn)
return osrm::guidance::TurnType::Turn;
@ -630,7 +658,9 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
return osrm::guidance::TurnType::NoTurn;
}),
"direction_modifier",
sol::property([](const ExtractionTurn &turn) {
sol::property(
[](const ExtractionTurn &turn)
{
if (turn.is_u_turn)
return osrm::guidance::DirectionModifier::UTurn;
else
@ -845,10 +875,12 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
BOOST_ASSERT(context.properties.GetTrafficSignalPenalty() == 0);
// call source_function if present
sol::function source_function = context.state["source_function"];
sol::protected_function source_function = context.state["source_function"];
if (source_function.valid())
{
source_function();
auto luares = source_function();
if (!luares.valid())
handle_lua_error(luares);
}
break;
@ -961,10 +993,12 @@ Sol2ScriptingEnvironment::GetStringListFromFunction(const std::string &function_
auto &context = GetSol2Context();
BOOST_ASSERT(context.state.lua_state());
std::vector<std::string> strings;
sol::function function = context.state[function_name];
sol::protected_function function = context.state[function_name];
if (function.valid())
{
function(strings);
auto luares = function(strings);
if (!luares.valid())
handle_lua_error(luares);
}
return strings;
}
@ -1123,7 +1157,9 @@ void Sol2ScriptingEnvironment::ProcessTurn(ExtractionTurn &turn)
case 2:
if (context.has_turn_penalty_function)
{
context.turn_function(context.profile_table, std::ref(turn));
auto luares = context.turn_function(context.profile_table, std::ref(turn));
if (!luares.valid())
handle_lua_error(luares);
// Turn weight falls back to the duration value in deciseconds
// or uses the extracted unit-less weight value
@ -1138,7 +1174,9 @@ void Sol2ScriptingEnvironment::ProcessTurn(ExtractionTurn &turn)
case 1:
if (context.has_turn_penalty_function)
{
context.turn_function(std::ref(turn));
auto luares = context.turn_function(std::ref(turn));
if (!luares.valid())
handle_lua_error(luares);
// Turn weight falls back to the duration value in deciseconds
// or uses the extracted unit-less weight value
@ -1184,24 +1222,28 @@ void Sol2ScriptingEnvironment::ProcessSegment(ExtractionSegment &segment)
if (context.has_segment_function)
{
sol::protected_function_result luares;
switch (context.api_version)
{
case 4:
case 3:
case 2:
context.segment_function(context.profile_table, std::ref(segment));
luares = context.segment_function(context.profile_table, std::ref(segment));
break;
case 1:
context.segment_function(std::ref(segment));
luares = context.segment_function(std::ref(segment));
break;
case 0:
context.segment_function(std::ref(segment.source),
luares = context.segment_function(std::ref(segment.source),
std::ref(segment.target),
segment.distance,
segment.duration);
segment.weight = segment.duration; // back-compatibility fallback to duration
break;
}
if (!luares.valid())
handle_lua_error(luares);
}
}
@ -1211,20 +1253,27 @@ void LuaScriptingContext::ProcessNode(const osmium::Node &node,
{
BOOST_ASSERT(state.lua_state() != nullptr);
sol::protected_function_result luares;
// TODO check for api version, make sure luares is always set
switch (api_version)
{
case 4:
case 3:
luares =
node_function(profile_table, std::cref(node), std::ref(result), std::cref(relations));
break;
case 2:
node_function(profile_table, std::cref(node), std::ref(result));
luares = node_function(profile_table, std::cref(node), std::ref(result));
break;
case 1:
case 0:
node_function(std::cref(node), std::ref(result));
luares = node_function(std::cref(node), std::ref(result));
break;
}
if (!luares.valid())
handle_lua_error(luares);
}
void LuaScriptingContext::ProcessWay(const osmium::Way &way,
@ -1233,20 +1282,27 @@ void LuaScriptingContext::ProcessWay(const osmium::Way &way,
{
BOOST_ASSERT(state.lua_state() != nullptr);
sol::protected_function_result luares;
// TODO check for api version, make sure luares is always set
switch (api_version)
{
case 4:
case 3:
luares =
way_function(profile_table, std::cref(way), std::ref(result), std::cref(relations));
break;
case 2:
way_function(profile_table, std::cref(way), std::ref(result));
luares = way_function(profile_table, std::cref(way), std::ref(result));
break;
case 1:
case 0:
way_function(std::cref(way), std::ref(result));
luares = way_function(std::cref(way), std::ref(result));
break;
}
if (!luares.valid())
handle_lua_error(luares);
}
} // namespace osrm::extractor