13#include "../error_func.h"
14#include "../fileio_func.h"
15#include "../string_func.h"
17#include "../settings_type.h"
19#include <../squirrel/sqpcheader.h>
20#include <../squirrel/sqvm.h>
21#include "../core/math_func.hpp"
22#include "../core/string_consumer.hpp"
24#include "../safeguards.h"
34 std::allocator<uint8_t> allocator;
47#ifdef SCRIPT_DEBUG_ALLOCATIONS
48 std::map<void *, size_t> allocations;
59 if (this->error_thrown)
return;
64 this->error_thrown =
true;
65 std::string msg = fmt::format(
"Maximum memory allocation exceeded by {} bytes when allocating {} bytes",
66 this->allocated_size + requested_size - this->allocation_limit, requested_size);
76 void *
DoAlloc(SQUnsignedInteger requested_size)
79 void *p = this->allocator.allocate(requested_size);
81 this->allocated_size += requested_size;
83#ifdef SCRIPT_DEBUG_ALLOCATIONS
84 assert(this->allocations.find(p) == this->allocations.end());
85 this->allocations[p] = requested_size;
88 }
catch (
const std::bad_alloc &) {
91 if (this->error_thrown) {
95 FatalError(
"Out of memory. Cannot allocate {} bytes", requested_size);
98 this->error_thrown =
true;
99 std::string msg = fmt::format(
"Out of memory. Cannot allocate {} bytes", requested_size);
76 void *
DoAlloc(SQUnsignedInteger requested_size) {
…}
107 void CheckLimit()
const
109 if (this->allocated_size > this->allocation_limit)
throw Script_FatalError(
"Maximum memory allocation exceeded");
114 assert(this->allocated_size == 0);
115 this->error_thrown =
false;
118 void *Malloc(SQUnsignedInteger size)
124 void *Realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
127 return this->Malloc(size);
130 this->Free(p, oldsize);
136 void *new_p = this->
DoAlloc(size);
137 std::copy_n(
static_cast<std::byte *
>(p), std::min(oldsize, size),
static_cast<std::byte *
>(new_p));
138 this->Free(p, oldsize);
143 void Free(
void *p, SQUnsignedInteger size)
145 if (p ==
nullptr)
return;
146 this->allocator.deallocate(
reinterpret_cast<uint8_t*
>(p), size);
147 this->allocated_size -= size;
149#ifdef SCRIPT_DEBUG_ALLOCATIONS
150 assert(this->allocations.at(p) == size);
151 this->allocations.erase(p);
158 if (this->allocation_limit == 0) this->allocation_limit =
SAFE_LIMIT;
163#ifdef SCRIPT_DEBUG_ALLOCATIONS
164 assert(this->allocations.empty());
171void *sq_vm_malloc(SQUnsignedInteger size) {
return _squirrel_allocator->Malloc(size); }
172void *sq_vm_realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) {
return _squirrel_allocator->Realloc(p, oldsize, size); }
173void sq_vm_free(
void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
177 assert(this->allocator !=
nullptr);
178 return this->allocator->GetAllocatedSize();
182void Squirrel::CompileError(HSQUIRRELVM vm, std::string_view desc, std::string_view source, SQInteger line, SQInteger column)
184 std::string msg = fmt::format(
"Error {}:{}/{}: {}", source, line, column, desc);
190 if (func ==
nullptr) {
191 Debug(misc, 0,
"[Squirrel] Compile error: {}", msg);
182void Squirrel::CompileError(HSQUIRRELVM vm, std::string_view desc, std::string_view source, SQInteger line, SQInteger column) {
…}
201 if (func ==
nullptr) {
202 fmt::print(stderr,
"{}", s);
211 SQPRINTFUNCTION pf = sq_getprintfunc(
vm);
215 std::string msg = fmt::format(
"Your script made an error: {}\n", error);
218 if (func ==
nullptr) {
219 fmt::print(stderr,
"{}", msg);
225 sqstd_printcallstack(
vm);
227 sq_setprintfunc(
vm, pf);
232 std::string_view view;
234 if (sq_gettop(
vm) >= 1) {
235 if (SQ_SUCCEEDED(sq_getstring(
vm, -1, view))) {
249 if (func ==
nullptr) {
256void Squirrel::AddMethod(std::string_view method_name, SQFUNCTION proc, std::string_view params,
void *userdata,
int size)
260 sq_pushstring(this->
vm, method_name);
263 void *ptr = sq_newuserdata(
vm, size);
264 std::copy_n(
static_cast<std::byte *
>(userdata), size,
static_cast<std::byte *
>(ptr));
267 sq_newclosure(this->
vm, proc, size != 0 ? 1 : 0);
268 if (!params.empty()) sq_setparamscheck(this->
vm, params.size(), params);
269 sq_setnativeclosurename(this->
vm, -1, method_name);
270 sq_newslot(this->
vm, -3, SQFalse);
256void Squirrel::AddMethod(std::string_view method_name, SQFUNCTION proc, std::string_view params,
void *userdata,
int size) {
…}
277 sq_pushstring(this->
vm, var_name);
278 sq_pushinteger(this->
vm, value);
279 sq_newslot(this->
vm, -3, SQTrue);
286 sq_pushstring(this->
vm, var_name);
287 sq_pushbool(this->
vm, value);
288 sq_newslot(this->
vm, -3, SQTrue);
295 sq_pushroottable(this->
vm);
296 sq_pushstring(this->
vm, class_name);
297 sq_newclass(this->
vm, SQFalse);
304 sq_pushroottable(this->
vm);
305 sq_pushstring(this->
vm, class_name);
306 sq_pushstring(this->
vm, parent_class);
307 if (SQ_FAILED(sq_get(this->
vm, -3))) {
308 Debug(misc, 0,
"[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
309 Debug(misc, 0,
"[squirrel] Make sure that '{}' exists before trying to define '{}'", parent_class, class_name);
312 sq_newclass(this->
vm, SQTrue);
319 sq_newslot(
vm, -3, SQFalse);
328 int top = sq_gettop(this->
vm);
330 sq_pushobject(this->
vm, instance);
332 sq_pushstring(this->
vm, method_name);
333 if (SQ_FAILED(sq_get(this->
vm, -2))) {
334 sq_settop(this->
vm, top);
337 sq_settop(this->
vm, top);
357 this->
crashed = !sq_resumecatch(this->
vm, suspend);
359 this->allocator->CheckLimit();
360 return this->
vm->_suspended != 0;
367 sq_resumeerror(this->
vm);
373 sq_collectgarbage(this->
vm);
380 this->allocator->CheckLimit();
385 SQInteger last_target = this->
vm->_suspended_target;
387 int top = sq_gettop(this->
vm);
389 sq_pushobject(this->
vm, instance);
391 sq_pushstring(this->
vm, method_name);
392 if (SQ_FAILED(sq_get(this->
vm, -2))) {
393 Debug(misc, 0,
"[squirrel] Could not find '{}' in the class", method_name);
394 sq_settop(this->
vm, top);
398 sq_pushobject(this->
vm, instance);
399 if (SQ_FAILED(sq_call(this->
vm, 1, ret ==
nullptr ? SQFalse : SQTrue, SQTrue, suspend)))
return false;
400 if (ret !=
nullptr) sq_getstackobj(
vm, -1, ret);
403 if (suspend == -1 || !this->
IsSuspended()) sq_settop(this->
vm, top);
405 this->
vm->_suspended_target = last_target;
410bool Squirrel::CallStringMethod(HSQOBJECT instance, std::string_view method_name, std::string *res,
int suspend)
413 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
416 if (!str.has_value())
return false;
422bool Squirrel::CallIntegerMethod(HSQOBJECT instance, std::string_view method_name,
int *res,
int suspend)
425 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
426 if (ret._type != OT_INTEGER)
return false;
431bool Squirrel::CallBoolMethod(HSQOBJECT instance, std::string_view method_name,
bool *res,
int suspend)
434 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
435 if (ret._type != OT_BOOL)
return false;
440 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name)
444 int oldtop = sq_gettop(
vm);
447 sq_pushroottable(
vm);
449 if (prepend_API_name) {
450 std::string prepended_class_name = fmt::format(
"{}{}", engine->
GetAPIName(), class_name);
451 sq_pushstring(
vm, prepended_class_name);
453 sq_pushstring(
vm, class_name);
456 if (SQ_FAILED(sq_get(
vm, -2))) {
457 Debug(misc, 0,
"[squirrel] Failed to find class by the name '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
458 sq_settop(
vm, oldtop);
463 if (SQ_FAILED(sq_createinstance(
vm, -1))) {
464 Debug(misc, 0,
"[squirrel] Failed to create instance for class '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
465 sq_settop(
vm, oldtop);
469 if (instance !=
nullptr) {
471 sq_getstackobj(
vm, -1, instance);
473 sq_addref(
vm, instance);
479 sq_setinstanceup(
vm, -1, real_instance);
480 if (release_hook !=
nullptr) sq_setreleasehook(
vm, -1, release_hook);
482 if (instance !=
nullptr) sq_settop(
vm, oldtop);
440 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name) {
…}
495 if (index < 0) index += sq_gettop(
vm) + 1;
497 std::string class_name = fmt::format(
"{}{}", engine->
GetAPIName(), tag);
498 sq_pushroottable(
vm);
499 sq_pushstring(
vm, class_name);
502 if (sq_instanceof(
vm) == SQTrue) {
504 SQUserPointer ptr =
nullptr;
505 if (SQ_SUCCEEDED(sq_getinstanceup(
vm, index, &ptr,
nullptr)))
return ptr;
507 throw sq_throwerror(
vm, fmt::format(
"parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
510Squirrel::Squirrel(std::string_view api_name) :
524 this->
vm = sq_open(1024);
528 sq_notifyallexceptions(this->
vm, _debug_script_level > 5);
533 sq_seterrorhandler(this->
vm);
536 sq_setforeignptr(this->
vm,
this);
538 sq_pushroottable(this->
vm);
542 sq_pushconsttable(this->
vm);
543 sq_setdelegate(this->
vm, -2);
554 size_t ReadInternal(std::span<char> buf)
556 size_t count = buf.size();
557 if (this->pos + count > this->size) {
558 count = this->size - this->pos;
560 if (count > 0) count = fread(buf.data(), 1, count, this->file);
566 SQFile(
FileHandle file,
size_t size) : file(std::move(file)), size(size), pos(0), consumer(buffer) {}
570 if (this->consumer.
GetBytesLeft() < min_size && this->pos < this->size) {
573 size_t buffer_size = this->buffer.size();
574 size_t read_size =
Align(min_size - buffer_size, 4096);
576 this->buffer.resize(buffer_size + read_size);
577 auto dest = std::span(this->buffer.data(), this->buffer.size()).subspan(buffer_size);
578 buffer_size += this->ReadInternal(dest);
579 this->buffer.resize(buffer_size);
583 return this->consumer;
586 size_t Read(
void *buf,
size_t max_size)
588 std::span<char> dest(
reinterpret_cast<char *
>(buf), max_size);
590 auto view = this->consumer.
Read(max_size);
591 std::copy(view.data(), view.data() + view.size(), dest.data());
592 size_t result_size = view.size();
594 if (result_size < max_size) {
596 result_size += this->ReadInternal(dest.subspan(result_size));
603static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
609static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
615static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
617 SQInteger ret =
reinterpret_cast<SQFile *
>(file)->Read(buf, size);
618 if (ret == 0)
return -1;
626 std::optional<FileHandle> file = std::nullopt;
631 }
else if (this->
GetAPIName().starts_with(
"GS")) {
638 if (!file.has_value()) {
639 return sq_throwerror(
vm,
"cannot open the file");
641 unsigned short bom = 0;
643 if (fread(&bom, 1,
sizeof(bom), *file) !=
sizeof(bom))
return sq_throwerror(
vm,
"cannot read the file");;
648 case SQ_BYTECODE_STREAM_TAG: {
649 if (fseek(*file, -2, SEEK_CUR) < 0) {
650 return sq_throwerror(
vm,
"cannot seek the file");
653 SQFile f(std::move(*file), size);
654 if (SQ_SUCCEEDED(sq_readclosure(
vm, _io_file_read, &f))) {
657 return sq_throwerror(
vm,
"Couldn't read bytecode");
663 return sq_throwerror(
vm,
"I/O error");
666 if (fread(&uc, 1,
sizeof(uc), *file) !=
sizeof(uc) || uc != 0xBF) {
667 return sq_throwerror(
vm,
"Unrecognized encoding");
669 func = _io_file_lexfeed_UTF8;
674 func = _io_file_lexfeed_ASCII;
676 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
677 return sq_throwerror(
vm,
"cannot seek the file");
682 SQFile f(std::move(*file), size);
683 if (SQ_SUCCEEDED(sq_compile(
vm, func, &f, filename.c_str(), printerror))) {
694 if (in_root) sq_pushroottable(
vm);
696 SQInteger ops_left =
vm->_ops_till_suspend;
698 if (SQ_SUCCEEDED(
LoadFile(
vm, script, SQTrue))) {
700 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQFalse, SQTrue, 100000))) {
703 vm->_ops_till_suspend = ops_left;
708 vm->_ops_till_suspend = ops_left;
709 Debug(misc, 0,
"[squirrel] Failed to compile '{}'", script);
728 sq_pushroottable(this->vm);
729 sq_pushnull(this->vm);
730 sq_setdelegate(this->vm, -2);
738 this->allocator->Reset();
747void Squirrel::InsertResult(
bool result)
751 sq_pushbool(this->vm, result);
753 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
758void Squirrel::InsertResult(
int result)
762 sq_pushinteger(this->vm, result);
764 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
771 vm->DecreaseOps(ops);
776 return this->vm->_suspended != 0;
792 return sq_can_suspend(this->vm);
797 return this->vm->_ops_till_suspend;
A throw-class that is given when the script made a fatal error.
void AddClassEnd()
Finishes adding a class to the global scope.
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, std::string_view tag)
Get the real-instance pointer.
void * global_pointer
Can be set by who ever initializes Squirrel.
void CollectGarbage()
Tell the VM to do a garbage collection run.
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
void Reset()
Completely reset the engine; start from scratch.
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
bool LoadScript(const std::string &script)
Load a script.
bool IsSuspended()
Did the squirrel code suspend or return normally.
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
void AddClassBegin(std::string_view class_name)
Adds a class to the global scope.
static SQInteger _RunError(HSQUIRRELVM vm)
The internal RunError handler.
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
void CrashOccurred()
Set the script status to crashed.
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
static void CompileError(HSQUIRRELVM vm, std::string_view desc, std::string_view source, SQInteger line, SQInteger column)
The CompileError handler.
static void RunError(HSQUIRRELVM vm, std::string_view error)
The RunError handler.
void Initialize()
Perform all initialization steps to create the engine.
bool crashed
True if the squirrel script made an error.
static void PrintFunc(HSQUIRRELVM vm, std::string_view s)
If a user runs 'print' inside a script, this function gets the params.
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
int overdrawn_ops
The amount of operations we have overdrawn.
static std::optional< std::string_view > ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
void Uninitialize()
Perform all the cleanups for the engine.
std::string_view GetAPIName()
Get the API name.
void AddMethod(std::string_view method_name, SQFUNCTION proc, std::string_view params={}, void *userdata=nullptr, int size=0)
Adds a function to the stack.
bool CallMethod(HSQOBJECT instance, std::string_view method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
void ResumeError()
Resume the VM with an error so it prints a stack trace.
static bool CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name=false)
Creates a class instance.
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
static void ErrorPrintFunc(HSQUIRRELVM vm, std::string_view s)
If an error has to be print, this function is called.
void AddConst(std::string_view var_name, int value)
Adds a const to the stack.
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
Parse data from a string / buffer.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
size_type GetBytesLeft() const noexcept
Get number of bytes left to read.
std::optional< uint8_t > TryReadUint8()
Try to read binary uint8, and then advance reader.
std::string_view Read(size_type len)
Read the next 'len' bytes, and advance reader.
size_type GetBytesRead() const noexcept
Get number of already read bytes.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
@ AI_DIR
Subdirectory for all AI files.
@ GAME_DIR
Subdirectory for all game scripts.
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
The definition of Script_FatalError.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
void squirrel_register_global_std(Squirrel *engine)
Register all standard functions that are available on first startup.
defines the Squirrel Standard Function class
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
ScriptSettings script
settings for scripts
static const size_t SAFE_LIMIT
128 MiB, a safe choice for almost any situation
bool error_thrown
Whether the error has already been thrown, so to not throw secondary errors in the handling of the al...
size_t allocation_limit
Maximum this allocator may use before allocations fail.
void * DoAlloc(SQUnsignedInteger requested_size)
Internal helper to allocate the given amount of bytes.
void CheckAllocationAllowed(size_t requested_size)
Checks whether an allocation is allowed by the memory limit set for the script.
size_t allocated_size
Sum of allocated data size.
uint32_t script_max_memory_megabytes
limit on memory a single script instance may have allocated