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_way_function = false;
bool has_segment_function = false; bool has_segment_function = false;
sol::function turn_function; sol::protected_function turn_function;
sol::function way_function; sol::protected_function way_function;
sol::function node_function; sol::protected_function node_function;
sol::function segment_function; sol::protected_function segment_function;
int api_version = 4; int api_version = 4;
sol::table profile_table; sol::table profile_table;

View File

@ -91,6 +91,19 @@ struct to_lua_object : public boost::static_visitor<sol::object>
}; };
} // namespace } // 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( Sol2ScriptingEnvironment::Sol2ScriptingEnvironment(
const std::string &file_name, const std::string &file_name,
const std::vector<boost::filesystem::path> &location_dependent_data_paths) const std::vector<boost::filesystem::path> &location_dependent_data_paths)
@ -232,7 +245,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"valid", "valid",
&osmium::Location::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()) if (context.location_dependent_data.empty())
return sol::object(context.state); return sol::object(context.state);
@ -259,7 +273,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"get_nodes", "get_nodes",
[](const osmium::Way &way) { return sol::as_table(&way.nodes()); }, [](const osmium::Way &way) { return sol::as_table(&way.nodes()); },
"get_location_tag", "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 // HEURISTIC: use a single node (last) of the way to localize the way
// For more complicated scenarios a proper merging of multiple tags // For more complicated scenarios a proper merging of multiple tags
// at one or many locations must be provided // at one or many locations must be provided
@ -279,9 +294,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"version", "version",
&osmium::Node::version, &osmium::Node::version,
"get_location_tag", "get_location_tag",
[&context, &get_location_tag](const osmium::Node &node, const char *key) { [&context, &get_location_tag](const osmium::Node &node, const char *key)
return get_location_tag(context, node.location(), key); { return get_location_tag(context, node.location(), key); });
});
context.state.new_enum("traffic_lights", context.state.new_enum("traffic_lights",
"none", "none",
@ -297,7 +311,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"ResultNode", "ResultNode",
"traffic_lights", "traffic_lights",
sol::property([](const ExtractionNode &node) { return node.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>()) if (obj.is<bool>())
{ {
// The old approach of assigning a boolean traffic light // The old approach of assigning a boolean traffic light
@ -348,7 +363,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
sol::property(&ExtractionWay::GetName, &ExtractionWay::SetName), sol::property(&ExtractionWay::GetName, &ExtractionWay::SetName),
"ref", // backward compatibility "ref", // backward compatibility
sol::property(&ExtractionWay::GetForwardRef, sol::property(&ExtractionWay::GetForwardRef,
[](ExtractionWay &way, const char *ref) { [](ExtractionWay &way, const char *ref)
{
way.SetForwardRef(ref); way.SetForwardRef(ref);
way.SetBackwardRef(ref); way.SetBackwardRef(ref);
}), }),
@ -407,7 +423,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
sol::property([](const ExtractionWay &way) { return way.access_turn_classification; }, sol::property([](const ExtractionWay &way) { return way.access_turn_classification; },
[](ExtractionWay &way, int flag) { way.access_turn_classification = flag; })); [](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>()) if (obj.is<osmium::Way>())
{ {
osmium::Way *way = obj.as<osmium::Way *>(); osmium::Way *way = obj.as<osmium::Way *>();
@ -443,20 +460,19 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
"get_value_by_key", "get_value_by_key",
[](ExtractionRelation &rel, const char *key) -> const char * { return rel.GetAttr(key); }, [](ExtractionRelation &rel, const char *key) -> const char * { return rel.GetAttr(key); },
"get_role", "get_role",
[&getTypedRefBySol](ExtractionRelation &rel, const sol::object &obj) -> const char * { [&getTypedRefBySol](ExtractionRelation &rel, const sol::object &obj) -> const char *
return rel.GetRole(getTypedRefBySol(obj)); { return rel.GetRole(getTypedRefBySol(obj)); });
});
context.state.new_usertype<ExtractionRelationContainer>( context.state.new_usertype<ExtractionRelationContainer>(
"ExtractionRelationContainer", "ExtractionRelationContainer",
"get_relations", "get_relations",
[&getTypedRefBySol](ExtractionRelationContainer &cont, const sol::object &obj) [&getTypedRefBySol](ExtractionRelationContainer &cont, const sol::object &obj)
-> const ExtractionRelationContainer::RelationIDList & { -> const ExtractionRelationContainer::RelationIDList &
return cont.GetRelations(getTypedRefBySol(obj)); { return cont.GetRelations(getTypedRefBySol(obj)); },
},
"relation", "relation",
[](ExtractionRelationContainer &cont, const ExtractionRelation::OsmIDTyped &rel_id) [](ExtractionRelationContainer &cont,
-> const ExtractionRelation & { return cont.GetRelationData(rel_id); }); const ExtractionRelation::OsmIDTyped &rel_id) -> const ExtractionRelation &
{ return cont.GetRelationData(rel_id); });
context.state.new_usertype<ExtractionSegment>("ExtractionSegment", context.state.new_usertype<ExtractionSegment>("ExtractionSegment",
"source", "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 // 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" // ref using osmium command "osmium add-locations-to-ways"
context.state.new_usertype<osmium::NodeRef>( context.state.new_usertype<osmium::NodeRef>("NodeRef",
"NodeRef", "id", &osmium::NodeRef::ref, "location", [](const osmium::NodeRef &nref) { "id",
return nref.location(); &osmium::NodeRef::ref,
}); "location",
[](const osmium::NodeRef &nref)
{ return nref.location(); });
context.state.new_usertype<InternalExtractorEdge>("EdgeSource", context.state.new_usertype<InternalExtractorEdge>("EdgeSource",
"source_coordinate", "source_coordinate",
@ -531,7 +549,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
util::Log() << "Using profile api version " << context.api_version; util::Log() << "Using profile api version " << context.api_version;
// version-dependent parts of the api // version-dependent parts of the api
auto initV2Context = [&]() { auto initV2Context = [&]()
{
// clear global not used in v2 // clear global not used in v2
context.state["properties"] = sol::nullopt; context.state["properties"] = sol::nullopt;
@ -550,10 +569,16 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
std::numeric_limits<TurnPenalty::value_type>::max()); std::numeric_limits<TurnPenalty::value_type>::max());
// call initialize function // call initialize function
sol::function setup_function = function_table.value()["setup"]; sol::protected_function setup_function = function_table.value()["setup"];
if (!setup_function.valid()) if (!setup_function.valid())
throw util::exception("Profile must have an setup() function."); 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) if (profile_table == sol::nullopt)
throw util::exception("Profile setup() must return a table."); throw util::exception("Profile setup() must return a table.");
else else
@ -616,26 +641,31 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
} }
}; };
auto initialize_V3_extraction_turn = [&]() { auto initialize_V3_extraction_turn = [&]()
{
context.state.new_usertype<ExtractionTurn>( context.state.new_usertype<ExtractionTurn>(
"ExtractionTurn", "ExtractionTurn",
"angle", "angle",
&ExtractionTurn::angle, &ExtractionTurn::angle,
"turn_type", "turn_type",
sol::property([](const ExtractionTurn &turn) { sol::property(
if (turn.number_of_roads > 2 || turn.source_mode != turn.target_mode || [](const ExtractionTurn &turn)
turn.is_u_turn) {
return osrm::guidance::TurnType::Turn; if (turn.number_of_roads > 2 || turn.source_mode != turn.target_mode ||
else turn.is_u_turn)
return osrm::guidance::TurnType::NoTurn; return osrm::guidance::TurnType::Turn;
}), else
return osrm::guidance::TurnType::NoTurn;
}),
"direction_modifier", "direction_modifier",
sol::property([](const ExtractionTurn &turn) { sol::property(
if (turn.is_u_turn) [](const ExtractionTurn &turn)
return osrm::guidance::DirectionModifier::UTurn; {
else if (turn.is_u_turn)
return osrm::guidance::DirectionModifier::Straight; return osrm::guidance::DirectionModifier::UTurn;
}), else
return osrm::guidance::DirectionModifier::Straight;
}),
"has_traffic_light", "has_traffic_light",
&ExtractionTurn::has_traffic_light, &ExtractionTurn::has_traffic_light,
"weight", "weight",
@ -845,10 +875,12 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context)
BOOST_ASSERT(context.properties.GetTrafficSignalPenalty() == 0); BOOST_ASSERT(context.properties.GetTrafficSignalPenalty() == 0);
// call source_function if present // 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()) if (source_function.valid())
{ {
source_function(); auto luares = source_function();
if (!luares.valid())
handle_lua_error(luares);
} }
break; break;
@ -961,10 +993,12 @@ Sol2ScriptingEnvironment::GetStringListFromFunction(const std::string &function_
auto &context = GetSol2Context(); auto &context = GetSol2Context();
BOOST_ASSERT(context.state.lua_state()); BOOST_ASSERT(context.state.lua_state());
std::vector<std::string> strings; std::vector<std::string> strings;
sol::function function = context.state[function_name]; sol::protected_function function = context.state[function_name];
if (function.valid()) if (function.valid())
{ {
function(strings); auto luares = function(strings);
if (!luares.valid())
handle_lua_error(luares);
} }
return strings; return strings;
} }
@ -1123,7 +1157,9 @@ void Sol2ScriptingEnvironment::ProcessTurn(ExtractionTurn &turn)
case 2: case 2:
if (context.has_turn_penalty_function) 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 // Turn weight falls back to the duration value in deciseconds
// or uses the extracted unit-less weight value // or uses the extracted unit-less weight value
@ -1138,7 +1174,9 @@ void Sol2ScriptingEnvironment::ProcessTurn(ExtractionTurn &turn)
case 1: case 1:
if (context.has_turn_penalty_function) 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 // Turn weight falls back to the duration value in deciseconds
// or uses the extracted unit-less weight value // or uses the extracted unit-less weight value
@ -1184,24 +1222,28 @@ void Sol2ScriptingEnvironment::ProcessSegment(ExtractionSegment &segment)
if (context.has_segment_function) if (context.has_segment_function)
{ {
sol::protected_function_result luares;
switch (context.api_version) switch (context.api_version)
{ {
case 4: case 4:
case 3: case 3:
case 2: case 2:
context.segment_function(context.profile_table, std::ref(segment)); luares = context.segment_function(context.profile_table, std::ref(segment));
break; break;
case 1: case 1:
context.segment_function(std::ref(segment)); luares = context.segment_function(std::ref(segment));
break; break;
case 0: case 0:
context.segment_function(std::ref(segment.source), luares = context.segment_function(std::ref(segment.source),
std::ref(segment.target), std::ref(segment.target),
segment.distance, segment.distance,
segment.duration); segment.duration);
segment.weight = segment.duration; // back-compatibility fallback to duration segment.weight = segment.duration; // back-compatibility fallback to duration
break; 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); 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) switch (api_version)
{ {
case 4: case 4:
case 3: case 3:
node_function(profile_table, std::cref(node), std::ref(result), std::cref(relations)); luares =
node_function(profile_table, std::cref(node), std::ref(result), std::cref(relations));
break; break;
case 2: case 2:
node_function(profile_table, std::cref(node), std::ref(result)); luares = node_function(profile_table, std::cref(node), std::ref(result));
break; break;
case 1: case 1:
case 0: case 0:
node_function(std::cref(node), std::ref(result)); luares = node_function(std::cref(node), std::ref(result));
break; break;
} }
if (!luares.valid())
handle_lua_error(luares);
} }
void LuaScriptingContext::ProcessWay(const osmium::Way &way, void LuaScriptingContext::ProcessWay(const osmium::Way &way,
@ -1233,20 +1282,27 @@ void LuaScriptingContext::ProcessWay(const osmium::Way &way,
{ {
BOOST_ASSERT(state.lua_state() != nullptr); 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) switch (api_version)
{ {
case 4: case 4:
case 3: case 3:
way_function(profile_table, std::cref(way), std::ref(result), std::cref(relations)); luares =
way_function(profile_table, std::cref(way), std::ref(result), std::cref(relations));
break; break;
case 2: case 2:
way_function(profile_table, std::cref(way), std::ref(result)); luares = way_function(profile_table, std::cref(way), std::ref(result));
break; break;
case 1: case 1:
case 0: case 0:
way_function(std::cref(way), std::ref(result)); luares = way_function(std::cref(way), std::ref(result));
break; break;
} }
if (!luares.valid())
handle_lua_error(luares);
} }
} // namespace osrm::extractor } // namespace osrm::extractor