LCOV - code coverage report
Current view: top level - AP_Scripting - lua_scripts.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 253 337 75.1 %
Date: 2026-09-09 11:02:08 Functions: 16 19 84.2 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : #include "AP_Scripting_config.h"
      17             : 
      18             : #if AP_SCRIPTING_ENABLED
      19             : 
      20             : #include "lua_scripts.h"
      21             : #include <AP_HAL/AP_HAL.h>
      22             : #include "AP_Scripting.h"
      23             : #include <AP_Logger/AP_Logger.h>
      24             : 
      25             : #include <AP_Scripting/lua_generated_bindings.h>
      26             : 
      27             : #define DISABLE_INTERRUPTS_FOR_SCRIPT_RUN 0
      28             : 
      29             : extern const AP_HAL::HAL& hal;
      30             : #define ENABLE_DEBUG_MODULE 0
      31             : 
      32             : bool lua_scripts::overtime;
      33             : char *lua_scripts::error_msg_buf;
      34             : HAL_Semaphore lua_scripts::error_msg_buf_sem;
      35             : uint8_t lua_scripts::print_error_count;
      36             : uint32_t lua_scripts::last_print_ms;
      37             : 
      38             : uint32_t lua_scripts::loaded_checksum;
      39             : uint32_t lua_scripts::running_checksum;
      40             : HAL_Semaphore lua_scripts::crc_sem;
      41             : 
      42             : // return string error message for error object at top of stack
      43           0 : static const char *get_error_object_message(lua_State *L) {
      44           0 :     const char *m = lua_tostring(L, -1);
      45           0 :     if (!m) { // error object is not stringifiable
      46           0 :         return "<error>";
      47             :     }
      48           0 :     return m;
      49             : }
      50             : 
      51          97 : lua_scripts::lua_scripts(const AP_Int32 &vm_steps, const AP_Int32 &heap_size, AP_Int8 &debug_options)
      52          97 :     : _vm_steps(vm_steps),
      53          97 :       _debug_options(debug_options)
      54             : {
      55          97 :     const bool allow_heap_expansion = !option_is_set(AP_Scripting::DebugOption::DISABLE_HEAP_EXPANSION);
      56          97 :     _heap.create(heap_size, 10, allow_heap_expansion, 20*1024);
      57          97 : }
      58             : 
      59          56 : lua_scripts::~lua_scripts() {
      60          56 :     _heap.destroy();
      61          56 : }
      62             : 
      63           1 : void lua_scripts::hook(lua_State *L, lua_Debug *ar) {
      64           1 :     lua_scripts::overtime = true;
      65             : 
      66             :     // we need to aggressively bail out as we are over time
      67             :     // so we will aggressively trap errors until we clear out
      68           1 :     lua_sethook(L, hook, LUA_MASKCOUNT, 1);
      69             : 
      70           1 :     luaL_error(L, "Exceeded CPU time");
      71           0 : }
      72             : 
      73         438 : void lua_scripts::print_error(MAV_SEVERITY severity) {
      74         438 :     error_msg_buf_sem.take_blocking();
      75         438 :     if (error_msg_buf == nullptr) {
      76         435 :         error_msg_buf_sem.give();
      77         435 :         return;
      78             :     }
      79           3 :     last_print_ms = AP_HAL::millis();
      80           3 :     GCS_SEND_TEXT(severity, "Lua: %s", error_msg_buf);
      81           3 :     error_msg_buf_sem.give();
      82             : }
      83             : 
      84           1 : void lua_scripts::set_and_print_new_error_message(MAV_SEVERITY severity, const char *fmt, ...) {
      85           1 :     error_msg_buf_sem.take_blocking();
      86             : 
      87             :     // reset buffer and print count
      88           1 :     print_error_count = 0;
      89           1 :     if (error_msg_buf) {
      90           0 :         _heap.deallocate(error_msg_buf);
      91           0 :         error_msg_buf = nullptr;
      92             :     }
      93             : 
      94             :     // generate va_list and create a copy
      95             :     va_list arg_list, arg_list_copy;
      96           1 :     va_start(arg_list, fmt);
      97           1 :     va_copy(arg_list_copy, arg_list);
      98             : 
      99             :     // dry run to work out the required length
     100           1 :     int len = hal.util->vsnprintf(nullptr, 0, fmt, arg_list_copy);
     101             : 
     102             :     // finished with copy
     103           1 :     va_end(arg_list_copy);
     104             : 
     105           1 :     if (len <= 0) {
     106             :         // nothing to print, something has gone wrong
     107           0 :         va_end(arg_list);
     108           0 :         error_msg_buf_sem.give();
     109           0 :         return;
     110             :     }
     111             : 
     112             :     // allocate buffer on scripting heap
     113           1 :     error_msg_buf = (char *)_heap.allocate(len+1);
     114           1 :     if (!error_msg_buf) {
     115             :         // allocation failed
     116           0 :         va_end(arg_list);
     117           0 :         error_msg_buf_sem.give();
     118           0 :         return;
     119             :     }
     120             : 
     121             :     // do actual print to buffer and clear va list
     122           1 :     hal.util->vsnprintf(error_msg_buf, len+1, fmt, arg_list);
     123           1 :     va_end(arg_list);
     124             : 
     125             :     // print to cosole and GCS
     126           1 :     DEV_PRINTF("Lua: %s\n", error_msg_buf);
     127             : 
     128           1 :     error_msg_buf_sem.give();
     129           1 :     print_error(severity);
     130             : }
     131             : 
     132             : // helper for print and log of runtime stats
     133       90481 : void lua_scripts::update_stats(const char *name, uint32_t run_time, int total_mem, int run_mem)
     134             : {
     135       90481 :     if (option_is_set(AP_Scripting::DebugOption::RUNTIME_MSG)) {
     136           0 :         GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Lua: Time: %u Mem: %d + %d",
     137             :                                             (unsigned int)run_time,
     138             :                                             (int)total_mem,
     139             :                                             (int)run_mem);
     140             :     }
     141             : #if HAL_LOGGING_ENABLED
     142       90481 :     if (option_is_set(AP_Scripting::DebugOption::LOG_RUNTIME)) {
     143          54 :         struct log_Scripting pkt {
     144             :             LOG_PACKET_HEADER_INIT(LOG_SCRIPTING_MSG),
     145          54 :             time_us      : AP_HAL::micros64(),
     146             :             name         : {},
     147             :             run_time     : run_time,
     148             :             total_mem    : total_mem,
     149             :             run_mem      : run_mem
     150          54 :         };
     151          54 :         const char * name_short = strrchr(name, '/');
     152          54 :         if ((strlen(name) > sizeof(pkt.name)) && (name_short != nullptr)) {
     153          54 :             strncpy_noterm(pkt.name, name_short+1, sizeof(pkt.name));
     154             :         } else {
     155           0 :             strncpy_noterm(pkt.name, name, sizeof(pkt.name));
     156             :         }
     157          54 :         AP::logger().WriteBlock(&pkt, sizeof(pkt));
     158             :     }
     159             : #endif // HAL_LOGGING_ENABLED
     160       90481 : }
     161             : 
     162          92 : bool lua_scripts::load_script(lua_State *L, script_info *new_script) {
     163          92 :     const char *filename = new_script->name;
     164             : 
     165          92 :     if (int error = luaL_loadfile(L, filename)) {
     166           0 :         switch (error) {
     167           0 :             case LUA_ERRSYNTAX:
     168           0 :                 set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Error: %s", get_error_object_message(L));
     169           0 :                 lua_pop(L, lua_gettop(L));
     170           0 :                 return false;
     171           0 :             case LUA_ERRMEM:
     172           0 :                 set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Insufficent memory loading %s", filename);
     173           0 :                 lua_pop(L, lua_gettop(L));
     174           0 :                 return false;
     175           0 :             case LUA_ERRFILE:
     176           0 :                 set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Unable to load the file: %s", get_error_object_message(L));
     177           0 :                 lua_pop(L, lua_gettop(L));
     178           0 :                 return false;
     179           0 :             default:
     180           0 :                 set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Unknown error (%d) loading %s", error, filename);
     181           0 :                 lua_pop(L, lua_gettop(L));
     182           0 :                 return false;
     183             :         }
     184             :     }
     185             : 
     186          92 :     const int loadMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
     187          92 :     const uint32_t loadStart = AP_HAL::micros();
     188             : 
     189          92 :     create_sandbox(L);
     190          92 :     lua_pushvalue(L, -1); // duplicate environment for reference below
     191          92 :     lua_setupvalue(L, -3, 1);
     192             : 
     193          92 :     const uint32_t loadEnd = AP_HAL::micros();
     194          92 :     const int endMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
     195             : 
     196          92 :     update_stats(filename, loadEnd-loadStart, endMem, loadMem);
     197             : 
     198          92 :     new_script->env_ref = luaL_ref(L, LUA_REGISTRYINDEX); // store reference to script's environment
     199          92 :     new_script->run_ref = luaL_ref(L, LUA_REGISTRYINDEX); // store reference to function to run
     200          92 :     new_script->next_run_ms = AP_HAL::millis64() - 1; // force the script to be stale
     201             : 
     202             :     // Get checksum of file
     203          92 :     uint32_t crc = 0;
     204          92 :     if (AP::FS().crc32(filename, crc)) {
     205             :         // Record crc of this script
     206          92 :         new_script->crc = crc;
     207             :         {
     208             :             // Apply crc to checksum of all scripts
     209          92 :             WITH_SEMAPHORE(crc_sem);
     210          92 :             loaded_checksum ^= crc;
     211          92 :             running_checksum ^= crc;
     212          92 :         }
     213             :     }
     214             : 
     215          92 :     return true;
     216             : }
     217             : 
     218          92 : void lua_scripts::create_sandbox(lua_State *L) {
     219          92 :     lua_newtable(L);
     220          92 :     luaopen_base_sandbox(L);
     221             : 
     222             : #if ENABLE_DEBUG_MODULE
     223             :     lua_pushstring(L, "debug");
     224             :     luaopen_debug(L);
     225             :     lua_settable(L, -3);
     226             : #endif
     227          92 :     lua_pushstring(L, "math");
     228          92 :     luaopen_math(L);
     229          92 :     lua_settable(L, -3);
     230          92 :     lua_pushstring(L, "table");
     231          92 :     luaopen_table(L);
     232          92 :     lua_settable(L, -3);
     233          92 :     lua_pushstring(L, "string");
     234          92 :     luaopen_string(L);
     235          92 :     lua_settable(L, -3);
     236          92 :     lua_pushstring(L, "io");
     237          92 :     luaopen_io(L);
     238          92 :     lua_settable(L, -3);
     239          92 :     lua_pushstring(L, "package");
     240          92 :     luaopen_package(L);
     241          92 :     lua_settable(L, -3);
     242             : 
     243          92 :     load_generated_sandbox(L);
     244          92 : }
     245             : 
     246          97 : void lua_scripts::load_all_scripts_in_dir(lua_State *L, const char *dirname) {
     247          97 :     if (dirname == nullptr) {
     248           0 :         return;
     249             :     }
     250          97 :     auto *d = AP::FS().opendir(dirname);
     251          97 :     if (d == nullptr) {
     252             :         // this disk_space check will return 0 if we don't have a real
     253             :         // filesystem (ie. no Posix or FatFs).  Do not warn in this case.
     254           0 :         if (AP::FS().disk_space(dirname) != 0) {
     255           0 :             GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Lua: open directory (%s) failed", dirname);
     256             :         }
     257           0 :         return;
     258             :     }
     259             : 
     260             :     // load anything that ends in .lua
     261         572 :     for (struct dirent *de=AP::FS().readdir(d); de; de=AP::FS().readdir(d)) {
     262         475 :         uint8_t length = strlen(de->d_name);
     263         475 :         if (length < 5) {
     264             :             // not long enough
     265         194 :             continue;
     266             :         }
     267             : 
     268         281 :         if ((de->d_name[0] == '.') || strncmp(&de->d_name[length-4], ".lua", 4)) {
     269             :             // starts with . (hidden file) or doesn't end in .lua
     270         189 :             continue;
     271             :         }
     272             : 
     273             :         // FIXME: because chunk name fetching is not working we are allocating and storing an extra string we shouldn't need to
     274          92 :         size_t size = strlen(dirname) + strlen(de->d_name) + 2;
     275          92 :         char * filename = (char *) _heap.allocate(size);
     276          92 :         script_info *script = (script_info *)_heap.allocate(sizeof(script_info));
     277          92 :         if ((filename == nullptr) || (script == nullptr)) {
     278             :             // unlikely to be out of memory for these, just ignore the script...
     279           0 :             _heap.deallocate(filename);
     280           0 :             _heap.deallocate(script);
     281           0 :             continue;
     282             :         }
     283          92 :         snprintf(filename, size, "%s/%s", dirname, de->d_name);
     284             : 
     285             :         // provisionally link the script_info into our list so it will be freed
     286             :         // if there is an uncaught Lua error during loading
     287          92 :         script->env_ref = LUA_NOREF;
     288          92 :         script->run_ref = LUA_NOREF;
     289          92 :         script->crc = 0; // ensure removing it has no effect on the CRC
     290          92 :         script->name = filename;
     291          92 :         script->next = scripts;
     292          92 :         scripts = script;
     293             : 
     294             :         // attempt to load the script, may raise Lua error
     295          92 :         bool success = load_script(L, script);
     296             :         // no Lua error, unlink the script_info so we can handle it properly
     297          92 :         scripts = script->next;
     298          92 :         script->next = nullptr;
     299             : 
     300          92 :         if (!success) { // discard if load failed
     301           0 :             _heap.deallocate(filename);
     302           0 :             _heap.deallocate(script);
     303           0 :             continue;
     304             :         }
     305          92 :         reschedule_script(script); // reschedule if load succeeded
     306             : 
     307             : #if HAL_LOGGER_FILE_CONTENTS_ENABLED
     308          92 :         if (!option_is_set(AP_Scripting::DebugOption::SUPPRESS_SCRIPT_LOG)) {
     309          92 :             AP::logger().log_file_content(filename);
     310             :         }
     311             : #endif
     312             :     }
     313          97 :     AP::FS().closedir(d);
     314             : }
     315             : 
     316       90393 : void lua_scripts::reset_loop_overtime(lua_State *L) {
     317       90393 :     overtime = false;
     318             :     // reset the hook to clear the counter
     319       90393 :     const int32_t vm_steps = MAX(_vm_steps, 1000);
     320       90393 :     lua_sethook(L, hook, LUA_MASKCOUNT, vm_steps);
     321       90393 : }
     322             : 
     323       90393 : void lua_scripts::run_next_script(lua_State *L) {
     324       90393 :     if (scripts == nullptr) {
     325             : #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     326           0 :         AP_HAL::panic("Lua: Attempted to run a script without any scripts queued");
     327             : #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     328             :         return;
     329             :     }
     330             : 
     331       90393 :     uint64_t start_time_ms = AP_HAL::millis64();
     332             :     // strip the selected script out of the list
     333       90393 :     script_info *script = scripts;
     334       90393 :     scripts = script->next;
     335             : 
     336             :     // reset the hook to clear the counter
     337       90393 :     reset_loop_overtime(L);
     338             : 
     339             :     // store top of stack so we can calculate the number of return values
     340       90393 :     int stack_top = lua_gettop(L);
     341             : 
     342             :     // pop the function to the top of the stack
     343       90393 :     lua_rawgeti(L, LUA_REGISTRYINDEX, script->run_ref);
     344             :     // set current environment for other users
     345       90393 :     AP::scripting()->set_current_env_ref(script->env_ref);
     346             : 
     347       90393 :     if(lua_pcall(L, 0, LUA_MULTRET, 0)) {
     348           1 :         if (overtime) {
     349             :             // script has consumed an excessive amount of CPU time
     350           1 :             set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s exceeded time limit", script->name);
     351             :         } else {
     352           0 :             set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s", get_error_object_message(L));
     353             :         }
     354           1 :         remove_script(L, script);
     355           1 :         lua_pop(L, 1);
     356           1 :         return;
     357             :     } else {
     358       90388 :         int returned = lua_gettop(L) - stack_top;
     359       90388 :         switch (returned) {
     360          11 :             case 0:
     361             :                 // no time to reschedule so bail out
     362          11 :                 remove_script(L, script);
     363          11 :                 break;
     364       90377 :             case 2:
     365             :                 {
     366             :                     // sanity check the return types
     367       90377 :                     if (lua_type(L, -1) != LUA_TNUMBER) {
     368           0 :                         set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s did not return a delay (0x%d)", script->name, lua_type(L, -1));
     369           0 :                         lua_pop(L, 2);
     370           0 :                         remove_script(L, script);
     371           0 :                         return;
     372             :                     }
     373       90377 :                     if (lua_type(L, -2) != LUA_TFUNCTION) {
     374           0 :                         set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s did not return a function (0x%d)", script->name, lua_type(L, -2));
     375           0 :                         lua_pop(L, 2);
     376           0 :                         remove_script(L, script);
     377           0 :                         return;
     378             :                     }
     379             : 
     380             :                     // types match the expectations, go ahead and reschedule
     381       90377 :                     script->next_run_ms = start_time_ms + (uint64_t)luaL_checknumber(L, -1);
     382       90377 :                     lua_pop(L, 1);
     383       90377 :                     luaL_unref(L, LUA_REGISTRYINDEX, script->run_ref);
     384             :                     // cannot cause error as we just made a free ref slot above
     385             :                     // so there won't be any need to allocate more
     386       90377 :                     script->run_ref = luaL_ref(L, LUA_REGISTRYINDEX);
     387       90377 :                     reschedule_script(script);
     388       90377 :                     break;
     389             :                 }
     390           0 :             default:
     391             :                 {
     392           0 :                     set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s returned bad result count (%d)", script->name, returned);
     393           0 :                     remove_script(L, script);
     394             :                     // pop all the results we got that we didn't expect
     395           0 :                     lua_pop(L, returned);
     396           0 :                     break;
     397             :                  }
     398             :          }
     399             :      }
     400             : }
     401             : 
     402          61 : void lua_scripts::remove_script(lua_State *L, script_info *script) {
     403          61 :     if (script == nullptr) {
     404           0 :         return;
     405             :     }
     406             : 
     407             :     // ensure that the script isn't in the loaded list for any reason
     408          61 :     if (scripts == nullptr) {
     409             :         // nothing to do, already not in the list
     410          50 :     } else if (scripts == script) {
     411          49 :         scripts = script->next;
     412             :     } else {
     413           2 :         for(script_info * current = scripts; current->next != nullptr; current = current->next) {
     414           1 :             if (current->next == script) {
     415           0 :                 current->next = script->next;
     416           0 :                 break;
     417             :             }
     418             :         }
     419             :     }
     420             : 
     421             :     {
     422             :         // Remove from running checksum
     423          61 :         WITH_SEMAPHORE(crc_sem);
     424          61 :         running_checksum ^= script->crc;
     425          61 :     }
     426             :     
     427          61 :     if (L != nullptr) {
     428             :         // state will be nullptr when we are tearing down
     429          12 :         luaL_unref(L, LUA_REGISTRYINDEX, script->env_ref);
     430          12 :         luaL_unref(L, LUA_REGISTRYINDEX, script->run_ref);
     431             :     }
     432          61 :     _heap.deallocate(script->name);
     433          61 :     _heap.deallocate(script);
     434             : }
     435             : 
     436       90469 : void lua_scripts::reschedule_script(script_info *script) {
     437       90469 :     if (script == nullptr) {
     438             : #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     439           0 :        AP_HAL::panic("Lua: Attempted to schedule a null pointer");
     440             : #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     441             :        return;
     442             :     }
     443             : 
     444       90469 :     script->next = nullptr;
     445       90469 :     if (scripts == nullptr) {
     446       85623 :         scripts = script;
     447       85623 :         return;
     448             :     }
     449             : 
     450        4846 :     uint64_t next_run_ms = script->next_run_ms;
     451             : 
     452        4846 :     if (scripts->next_run_ms > next_run_ms) {
     453          20 :         script->next = scripts;
     454          20 :         scripts = script;
     455          20 :         return;
     456             :     }
     457             : 
     458        4826 :     script_info *previous = scripts;
     459        4934 :     while (previous->next != nullptr) {
     460         118 :         if (previous->next->next_run_ms > next_run_ms) {
     461          10 :             script->next = previous->next;
     462          10 :             previous->next = script;
     463          10 :             return;
     464             :         }
     465         108 :         previous = previous->next;
     466             :     }
     467             : 
     468        4816 :     previous->next = script;
     469             : }
     470             : 
     471             : MultiHeap lua_scripts::_heap;
     472             : 
     473     3641915 : void *lua_scripts::alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
     474             :     (void)ud; /* not used */
     475     3641915 :     return _heap.change_size(ptr, osize, nsize);
     476             : }
     477             : 
     478          97 : void lua_scripts::run(void) {
     479          97 :     if (!_heap.available()) {
     480           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Lua: Unable to allocate a heap");
     481           0 :         return;
     482             :     }
     483             : 
     484          97 :     lua_State *L = lua_newstate(alloc, NULL);
     485          97 :     if (L == nullptr) {
     486           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Lua: Couldn't allocate a lua state");
     487           0 :         return;
     488             :     }
     489             :     // initialize the state with a pointer to us for ls_* callback trampolines
     490             :     // (see ls_object_from_state)
     491          97 :     *static_cast<lua_scripts**>(lua_getextraspace(L)) = this;
     492             : 
     493             :     // call main engine function in protected mode now that Lua itself is ready.
     494             :     // this catches any errors raised by the code between here and Lua scripts.
     495             :     // our current function must not use any Lua API which can raise an error!
     496          97 :     lua_pushcfunction(L, &ls_run_engine);
     497          97 :     if (lua_pcall(L, 0, 0, 0) != LUA_OK) { // no args, returns, or msg handler
     498           0 :         set_and_print_new_error_message(MAV_SEVERITY_CRITICAL,
     499             :             "Engine Error: %s", get_error_object_message(L));
     500             :     }
     501             :     // we are now finished with Lua, tear everything down
     502             : 
     503          56 :     lua_close(L); // shut down the state
     504          56 :     L = nullptr;
     505             : 
     506         105 :     while (scripts != nullptr) { // remove all scripts from the engine list
     507          49 :         remove_script(nullptr, scripts);
     508             :     }
     509             : 
     510             :     // free error message
     511          56 :     error_msg_buf_sem.take_blocking();
     512          56 :     if (error_msg_buf != nullptr) {
     513           1 :         _heap.deallocate(error_msg_buf);
     514           1 :         error_msg_buf = nullptr;
     515             :     }
     516          56 :     error_msg_buf_sem.give();
     517             : 
     518             :     // heap is now empty
     519             : }
     520             : 
     521          97 : int lua_scripts::run_engine(lua_State *L) {
     522             :     // run our scripting engine now that the Lua state is initialized. we are in
     523             :     // Lua protected mode and can safely call functions that may raise errors.
     524             : 
     525             :     // set up string metatable. we set up one for all scripts that no script has
     526             :     // access to, as it's impossible to set up one per-script and we don't want
     527             :     // any script to be able to mess with it.
     528          97 :     lua_pushliteral(L, "");  /* dummy string */
     529          97 :     lua_createtable(L, 0, 1);  /* table to be metatable for strings */
     530          97 :     luaopen_string(L);  /* get string library */
     531          97 :     lua_setfield(L, -2, "__index");  /* metatable.__index = string */
     532          97 :     lua_setmetatable(L, -2);  /* set table as metatable for strings */
     533          97 :     lua_pop(L, 1);  /* pop dummy string */
     534             : 
     535          97 :     if (option_is_set(AP_Scripting::DebugOption::RUNTIME_MSG)) {
     536           0 :         const int loaded_mem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
     537           0 :         GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Lua: State memory usage: %i\n", loaded_mem);
     538             :     }
     539             : 
     540             :     // Scan the filesystem in an appropriate manner and autostart scripts
     541             :     // Skip those directores disabled with SCR_DIR_DISABLE param
     542          97 :     uint16_t dir_disable = AP_Scripting::get_singleton()->get_disabled_dir();
     543          97 :     bool loaded = false;
     544          97 :     if ((dir_disable & uint16_t(AP_Scripting::SCR_DIR::SCRIPTS)) == 0) {
     545          97 :         load_all_scripts_in_dir(L, SCRIPTING_DIRECTORY);
     546          97 :         loaded = true;
     547             :     }
     548             : #ifdef HAL_HAVE_AP_ROMFS_EMBEDDED_LUA
     549             :     if ((dir_disable & uint16_t(AP_Scripting::SCR_DIR::ROMFS)) == 0) {
     550             :         load_all_scripts_in_dir(L, "@ROMFS/scripts");
     551             :         loaded = true;
     552             :     }
     553             : #endif
     554          97 :     if (!loaded) {
     555           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Lua: All directory's disabled see SCR_DIR_DISABLE");
     556             :     }
     557             : 
     558          97 :     uint32_t expansion_size = 0;
     559             : 
     560       91303 :     while (AP_Scripting::get_singleton()->should_run()) {
     561             : #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     562       91247 :         if (lua_gettop(L) != 0) {
     563           0 :             AP_HAL::panic("Lua: Stack should be empty before running scripts");
     564             :         }
     565             : #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     566             : 
     567       91247 :         if (scripts != nullptr) {
     568             : #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     569             :               // Sanity check that the scripts list is ordered correctly
     570       90424 :               script_info *sanity = scripts;
     571       95396 :               while (sanity->next != nullptr) {
     572        4972 :                   if (sanity->next_run_ms > sanity->next->next_run_ms) {
     573           0 :                       AP_HAL::panic("Lua: Script tasking order has been violated");
     574             :                   }
     575        4972 :                   sanity = sanity->next;
     576             :               }
     577             : #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
     578             : 
     579             :             // compute delay time
     580       90424 :             const uint64_t now_ms = AP_HAL::millis64();
     581       90424 :             if (now_ms < scripts->next_run_ms) {
     582       88812 :                 const uint32_t delay_ms = scripts->next_run_ms - now_ms;
     583       88812 :                 if (delay_ms > UINT16_MAX) {
     584             :                     // The delay method takes a uint16, so any longer delays must be done in sections (>65.535s)
     585           4 :                     hal.scheduler->delay(UINT16_MAX);
     586             : 
     587             :                     // Restart loop, this means delay is re-calculated to make up for any drift
     588             :                     // This also means `should_run` is re-checked allowing a possible stop or restart
     589           4 :                     continue;
     590             :                 }
     591       88808 :                 hal.scheduler->delay(delay_ms);
     592             :             }
     593             : 
     594       90393 :             if (option_is_set(AP_Scripting::DebugOption::RUNTIME_MSG)) {
     595           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Lua: Running %s", scripts->name);
     596             :             }
     597             :             // take a copy of the script name for the purposes of
     598             :             // logging statistics.  "scripts" may become invalid
     599             :             // during the "run_next_script" call, below.
     600       90393 :             char script_name[128+1] {};
     601       90393 :             strncpy_noterm(script_name, scripts->name, 128);
     602             : 
     603             : #if DISABLE_INTERRUPTS_FOR_SCRIPT_RUN
     604             :             void *istate = hal.scheduler->disable_interrupts_save();
     605             : #endif
     606             : 
     607       90393 :             const int startMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
     608       90393 :             const uint32_t loadEnd = AP_HAL::micros();
     609             : 
     610             :             // NOTE!  the base pointer of our scripts linked list,
     611             :             // *and all its contents* may become invalid as part of
     612             :             // "run_next_script"!  So do *NOT* attempt to access
     613             :             // anything that was in *scripts after this call.
     614       90393 :             run_next_script(L);
     615             : 
     616       90389 :             const uint32_t runEnd = AP_HAL::micros();
     617       90389 :             const int endMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
     618             : 
     619             : #if DISABLE_INTERRUPTS_FOR_SCRIPT_RUN
     620             :             hal.scheduler->restore_interrupts(istate);
     621             : #endif
     622             : 
     623       90389 :             update_stats(script_name, runEnd - loadEnd, endMem, endMem - startMem);
     624             : 
     625             : 
     626             :             // garbage collect after each script, this shouldn't matter, but seems to resolve a memory leak
     627       90389 :             lua_gc(L, LUA_GCCOLLECT, 0);
     628             : 
     629             :         } else {
     630         823 :             if (option_is_set(AP_Scripting::DebugOption::NO_SCRIPTS_TO_RUN)) {
     631           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Lua: No scripts to run");
     632             :             }
     633         823 :             hal.scheduler->delay(1000);
     634             :         }
     635             : 
     636             :         /*
     637             :           report a warning if SCR_HEAP_SIZE wasn't adequate and we
     638             :           expanded at runtime, so the user can fix it for future
     639             :           flights
     640             :          */
     641       91202 :         const uint32_t new_expansion_size = _heap.get_expansion_size();
     642       91202 :         if (new_expansion_size > expansion_size) {
     643           0 :             expansion_size = new_expansion_size;
     644           0 :             set_and_print_new_error_message(MAV_SEVERITY_WARNING, "Required SCR_HEAP_SIZE over %u", unsigned(expansion_size));
     645             :         }
     646             : 
     647             :         // re-print the latest error message every 10 seconds 10 times
     648       91202 :         const uint8_t error_prints = 10;
     649       91202 :         if ((print_error_count < error_prints) && (AP_HAL::millis() - last_print_ms > 10000)) {
     650             :             // note that we do not clear the buffer after we have finished printing, this allows it to be used for a pre-arm check
     651         437 :             print_error(MAV_SEVERITY_DEBUG);
     652         437 :             print_error_count++;
     653             :         }
     654             :     }
     655             : 
     656          56 :     return 0; // no results
     657             : }
     658             : 
     659             : // Return the file checksums of running and loaded scripts
     660           0 : uint32_t lua_scripts::get_loaded_checksum()
     661             : {
     662           0 :     WITH_SEMAPHORE(crc_sem);
     663           0 :     return loaded_checksum;
     664           0 : }
     665             : 
     666           0 : uint32_t lua_scripts::get_running_checksum()
     667             : {
     668           0 :     WITH_SEMAPHORE(crc_sem);
     669           0 :     return running_checksum;
     670           0 : }
     671             : 
     672             : #endif  // AP_SCRIPTING_ENABLED

Generated by: LCOV version 1.14