• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ArkScript-lang / Ark / 11195812882

05 Oct 2024 07:36PM UTC coverage: 75.225% (+0.007%) from 75.218%
11195812882

push

github

SuperFola
refactor: get the value 'fail_on_exception' from the feature flags instead of a boolean argument

12 of 12 new or added lines in 2 files covered. (100.0%)

162 existing lines in 2 files now uncovered.

4849 of 6446 relevant lines covered (75.22%)

9637.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.17
/src/arkreactor/VM/VM.cpp
1
#include <Ark/VM/VM.hpp>
2

3
#include <utility>
4
#include <numeric>
5
#include <limits>
6
#include <ranges>
7
#include <fmt/core.h>
8
#include <fmt/color.h>
9

10
#include <Ark/Files.hpp>
11
#include <Ark/Utils.hpp>
12
#include <Ark/TypeChecker.hpp>
13
#include <Ark/Compiler/Instructions.hpp>
14

15
struct mapping
16
{
17
    char* name;
18
    Ark::Value (*value)(std::vector<Ark::Value>&, Ark::VM*);
19
};
20

21
namespace Ark
22
{
23
    using namespace internal;
24

25
    VM::VM(State& state) noexcept :
78✔
26
        m_state(state), m_exit_code(0), m_running(false)
26✔
27
    {
26✔
28
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
26✔
29
    }
26✔
30

31
    void VM::init() noexcept
27✔
32
    {
27✔
33
        ExecutionContext& context = *m_execution_contexts.back();
27✔
34
        for (const auto& c : m_execution_contexts)
54✔
35
        {
36
            c->ip = 0;
27✔
37
            c->pp = 0;
27✔
38
            c->sp = 0;
27✔
39
        }
27✔
40

41
        context.sp = 0;
27✔
42
        context.fc = 1;
27✔
43

44
        m_shared_lib_objects.clear();
27✔
45
        context.stacked_closure_scopes.clear();
27✔
46
        context.stacked_closure_scopes.emplace_back(nullptr);
27✔
47

48
        context.saved_scope.reset();
27✔
49
        m_exit_code = 0;
27✔
50

51
        context.locals.clear();
27✔
52
        context.locals.emplace_back();
27✔
53

54
        // loading bound stuff
55
        // put them in the global frame if we can, aka the first one
56
        for (const auto& [sym_id, value] : m_state.m_binded)
35✔
57
        {
58
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
4✔
59
            if (it != m_state.m_symbols.end())
4✔
60
                context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
4✔
61
        }
4✔
62
    }
27✔
63

64
    Value& VM::operator[](const std::string& name) noexcept
8✔
65
    {
8✔
66
        // find id of object
67
        const auto it = std::ranges::find(m_state.m_symbols, name);
8✔
68
        if (it == m_state.m_symbols.end())
8✔
69
        {
70
            m_no_value = Builtins::nil;
×
71
            return m_no_value;
×
72
        }
73

74
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
8✔
75
        if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
8✔
76
        {
77
            ExecutionContext& context = *m_execution_contexts.front();
8✔
78

79
            const auto id = static_cast<uint16_t>(dist);
8✔
80
            Value* var = findNearestVariable(id, context);
8✔
81
            if (var != nullptr)
8✔
82
                return *var;
8✔
83
        }
8✔
84

85
        m_no_value = Builtins::nil;
×
86
        return m_no_value;
×
87
    }
8✔
88

89
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
×
90
    {
×
91
        namespace fs = std::filesystem;
92

93
        const std::string file = m_state.m_constants[id].stringRef();
×
94

95
        std::string path = file;
×
96
        // bytecode loaded from file
97
        if (m_state.m_filename != ARK_NO_NAME_FILE)
×
98
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
×
99

100
        std::shared_ptr<SharedLibrary> lib;
×
101
        // if it exists alongside the .arkc file
102
        if (Utils::fileExists(path))
×
103
            lib = std::make_shared<SharedLibrary>(path);
×
104
        else
105
        {
106
            for (auto const& v : m_state.m_libenv)
×
107
            {
108
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
×
109

110
                // if it's already loaded don't do anything
111
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
×
112
                        return (val->path() == path || val->path() == lib_path);
×
113
                    }) != m_shared_lib_objects.end())
×
114
                    return;
×
115

116
                // check in lib_path
117
                if (Utils::fileExists(lib_path))
×
118
                {
119
                    lib = std::make_shared<SharedLibrary>(lib_path);
×
120
                    break;
×
121
                }
122
            }
×
123
        }
124

125
        if (!lib)
×
126
        {
127
            auto lib_path = std::accumulate(
×
128
                std::next(m_state.m_libenv.begin()),
×
129
                m_state.m_libenv.end(),
×
130
                m_state.m_libenv[0].string(),
×
131
                [](const std::string& a, const fs::path& b) -> std::string {
×
132
                    return a + "\n\t- " + b.string();
×
133
                });
×
134
            throwVMError(
×
135
                ErrorKind::Module,
136
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
137
        }
×
138

139
        m_shared_lib_objects.emplace_back(lib);
×
140

141
        // load the mapping from the dynamic library
142
        try
143
        {
144
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
×
145
            // load the mapping data
146
            std::size_t i = 0;
×
147
            while (map[i].name != nullptr)
×
148
            {
149
                // put it in the global frame, aka the first one
150
                auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
151
                if (it != m_state.m_symbols.end())
×
152
                    context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
×
153

154
                ++i;
×
155
            }
×
156
        }
×
157
        catch (const std::system_error& e)
158
        {
159
            throwVMError(
×
160
                ErrorKind::Module,
161
                fmt::format(
×
162
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
163
                    file, e.what()));
×
164
        }
×
165
    }
26✔
166

167
    void VM::exit(const int code) noexcept
×
168
    {
×
169
        m_exit_code = code;
×
170
        m_running = false;
×
171
    }
×
172

173
    ExecutionContext* VM::createAndGetContext()
6✔
174
    {
6✔
175
        const std::lock_guard lock(m_mutex);
6✔
176

177
        m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
6✔
178
        ExecutionContext* ctx = m_execution_contexts.back().get();
6✔
179
        ctx->stacked_closure_scopes.emplace_back(nullptr);
6✔
180

181
        ctx->locals.reserve(m_execution_contexts.front()->locals.size());
6✔
182
        for (const auto& local : m_execution_contexts.front()->locals)
20✔
183
            ctx->locals.push_back(local);
14✔
184

185
        return ctx;
6✔
186
    }
6✔
187

188
    void VM::deleteContext(ExecutionContext* ec)
5✔
189
    {
5✔
190
        const std::lock_guard lock(m_mutex);
5✔
191

192
        const auto it =
5✔
193
            std::ranges::remove_if(
10✔
194
                m_execution_contexts,
5✔
195
                [ec](const std::unique_ptr<ExecutionContext>& ctx) {
21✔
196
                    return ctx.get() == ec;
16✔
197
                })
198
                .begin();
5✔
199
        m_execution_contexts.erase(it);
5✔
200
    }
5✔
201

202
    Future* VM::createFuture(std::vector<Value>& args)
6✔
203
    {
6✔
204
        ExecutionContext* ctx = createAndGetContext();
6✔
205

206
        // doing this after having created the context
207
        // because the context uses the mutex and we don't want a deadlock
208
        const std::lock_guard lock(m_mutex);
6✔
209
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
6✔
210

211
        return m_futures.back().get();
6✔
212
    }
6✔
213

214
    void VM::deleteFuture(Future* f)
×
UNCOV
215
    {
×
216
        const std::lock_guard lock(m_mutex);
×
217

218
        const auto it =
×
UNCOV
219
            std::ranges::remove_if(
×
220
                m_futures,
×
221
                [f](const std::unique_ptr<Future>& future) {
×
222
                    return future.get() == f;
×
223
                })
224
                .begin();
×
225
        m_futures.erase(it);
×
UNCOV
226
    }
×
227

UNCOV
228
    bool VM::forceReloadPlugins() const
×
229
    {
×
230
        // load the mapping from the dynamic library
231
        try
232
        {
233
            for (const auto& shared_lib : m_shared_lib_objects)
×
234
            {
UNCOV
235
                const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
×
236
                // load the mapping data
237
                std::size_t i = 0;
×
238
                while (map[i].name != nullptr)
×
239
                {
240
                    // put it in the global frame, aka the first one
241
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
UNCOV
242
                    if (it != m_state.m_symbols.end())
×
243
                        m_execution_contexts[0]->locals[0].push_back(
×
244
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
245
                            Value(map[i].value));
×
246

247
                    ++i;
×
248
                }
×
UNCOV
249
            }
×
250

251
            return true;
×
252
        }
×
253
        catch (const std::system_error&)
254
        {
UNCOV
255
            return false;
×
UNCOV
256
        }
×
UNCOV
257
    }
×
258

259
    int VM::run(const bool fail_with_exception)
27✔
260
    {
27✔
261
        init();
27✔
262
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
27✔
263
        return m_exit_code;
27✔
264
    }
265

266
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
45✔
267
    {
45✔
268
#if ARK_USE_COMPUTED_GOTOS
269
#    define TARGET(op) TARGET_##op:
270
#    define DISPATCH_GOTO()            \
271
        _Pragma("GCC diagnostic push") \
272
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
273
        _Pragma("GCC diagnostic pop")
274
#    define GOTO_HALT() goto dispatch_end
275
#else
276
#    define TARGET(op) case op:
277
#    define DISPATCH_GOTO() goto dispatch_opcode
278
#    define GOTO_HALT() break
279
#endif
280

281
#define NEXTOPARG()                                                                      \
282
    do                                                                                   \
283
    {                                                                                    \
284
        padding = m_state.m_pages[context.pp][context.ip];                               \
285
        inst = m_state.m_pages[context.pp][context.ip + 1];                              \
286
        arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
287
                                    m_state.m_pages[context.pp][context.ip + 3]);        \
288
        context.ip += 4;                                                                 \
289
    } while (false)
290
#define DISPATCH() \
291
    NEXTOPARG();   \
292
    DISPATCH_GOTO();
293

294
#if ARK_USE_COMPUTED_GOTOS
295
#    pragma GCC diagnostic push
296
#    pragma GCC diagnostic ignored "-Wpedantic"
297
            const std::array opcode_targets = {
45✔
298
                &&TARGET_NOP,
299
                &&TARGET_LOAD_SYMBOL,
300
                &&TARGET_LOAD_CONST,
301
                &&TARGET_POP_JUMP_IF_TRUE,
302
                &&TARGET_STORE,
303
                &&TARGET_SET_VAL,
304
                &&TARGET_POP_JUMP_IF_FALSE,
305
                &&TARGET_JUMP,
306
                &&TARGET_RET,
307
                &&TARGET_HALT,
308
                &&TARGET_CALL,
309
                &&TARGET_CAPTURE,
310
                &&TARGET_BUILTIN,
311
                &&TARGET_DEL,
312
                &&TARGET_MAKE_CLOSURE,
1✔
313
                &&TARGET_GET_FIELD,
314
                &&TARGET_PLUGIN,
1✔
315
                &&TARGET_LIST,
316
                &&TARGET_APPEND,
1✔
317
                &&TARGET_CONCAT,
318
                &&TARGET_APPEND_IN_PLACE,
319
                &&TARGET_CONCAT_IN_PLACE,
320
                &&TARGET_POP_LIST,
321
                &&TARGET_POP_LIST_IN_PLACE,
322
                &&TARGET_POP,
323
                &&TARGET_DUP,
324
                &&TARGET_ADD,
325
                &&TARGET_SUB,
326
                &&TARGET_MUL,
327
                &&TARGET_DIV,
328
                &&TARGET_GT,
329
                &&TARGET_LT,
330
                &&TARGET_LE,
331
                &&TARGET_GE,
332
                &&TARGET_NEQ,
333
                &&TARGET_EQ,
334
                &&TARGET_LEN,
335
                &&TARGET_EMPTY,
336
                &&TARGET_TAIL,
337
                &&TARGET_HEAD,
338
                &&TARGET_ISNIL,
339
                &&TARGET_ASSERT,
340
                &&TARGET_TO_NUM,
341
                &&TARGET_TO_STR,
342
                &&TARGET_AT,
343
                &&TARGET_MOD,
344
                &&TARGET_TYPE,
345
                &&TARGET_HASFIELD,
346
                &&TARGET_NOT,
347
            };
348
#    pragma GCC diagnostic pop
349
#endif
350

351
        try
352
        {
353
            [[maybe_unused]] uint8_t padding = 0;
45✔
354
            uint8_t inst = 0;
45✔
355
            uint16_t arg = 0;
45✔
356
            m_running = true;
45✔
357

358
            DISPATCH();
45✔
359
            {
360
#if !ARK_USE_COMPUTED_GOTOS
361
            dispatch_opcode:
362
                switch (inst)
363
#endif
364
                {
×
365
#pragma region "Instructions"
366
                    TARGET(NOP)
367
                    {
UNCOV
368
                        DISPATCH();
×
369
                    }
22,149✔
370

371
                    TARGET(LOAD_SYMBOL)
372
                    {
373
                        context.last_symbol = arg;
22,149✔
374
                        if (Value* var = findNearestVariable(context.last_symbol, context); var != nullptr) [[likely]]
22,149✔
375
                        {
376
                            // push internal reference, shouldn't break anything so far, unless it's already a ref
377
                            if (var->valueType() == ValueType::Reference)
22,148✔
378
                                push(var->reference(), context);
350✔
379
                            else
380
                                push(var, context);
21,798✔
381
                        }
22,155✔
382
                        else [[unlikely]]
383
                            throwVMError(ErrorKind::Scope, fmt::format("Unbound variable `{}'", m_state.m_symbols[context.last_symbol]));
1✔
384
                        DISPATCH();
22,155✔
385
                    }
7,467✔
386

387
                    TARGET(LOAD_CONST)
388
                    {
389
                        push(&(m_state.m_constants[arg]), context);
7,467✔
390
                        DISPATCH();
7,448✔
391
                    }
579✔
392

393
                    TARGET(POP_JUMP_IF_TRUE)
394
                    {
395
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
820✔
396
                            context.ip = arg * 4;  // instructions are 4 bytes
241✔
397
                        DISPATCH();
579✔
398
                    }
4,574✔
399

400
                    TARGET(STORE)
401
                    {
402
                        {
403
                            Value val = *popAndResolveAsPtr(context);
4,574✔
404
                            // avoid adding the pair (id, _) multiple times, with different values
405
                            Value* local = context.locals.back()[arg];
4,574✔
406
                            if (local == nullptr) [[likely]]
4,574✔
407
                                context.locals.back().push_back(arg, val);
4,511✔
408
                            else
409
                                *local = val;
63✔
410
                        }
4,574✔
411

412
                        DISPATCH();
4,574✔
413
                    }
4,435✔
414

415
                    TARGET(SET_VAL)
416
                    {
417
                        {
418
                            Value val = *popAndResolveAsPtr(context);
4,435✔
419
                            if (Value* var = findNearestVariable(arg, context); var != nullptr) [[likely]]
4,435✔
420
                            {
421
                                if (var->valueType() == ValueType::Reference)
4,434✔
422
                                    *var->reference() = val;
385✔
423
                                else [[likely]]
424
                                    *var = val;
4,049✔
425
                            }
4,434✔
426
                            else
427
                                throwVMError(ErrorKind::Scope, fmt::format("Unbound variable `{}', can not change its value to {}", m_state.m_symbols[arg], val.toString(*this)));
1✔
428
                        }
4,435✔
429
                        DISPATCH();
4,434✔
430
                    }
2,058✔
431

432
                    TARGET(POP_JUMP_IF_FALSE)
433
                    {
434
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
2,076✔
435
                            context.ip = arg * 4;  // instructions are 4 bytes
18✔
436
                        DISPATCH();
2,058✔
437
                    }
2,371✔
438

439
                    TARGET(JUMP)
440
                    {
441
                        context.ip = arg * 4;  // instructions are 4 bytes
2,371✔
442
                        DISPATCH();
2,371✔
443
                    }
719✔
444

445
                    TARGET(RET)
446
                    {
447
                        {
448
                            Value ip_or_val = *popAndResolveAsPtr(context);
719✔
449
                            // no return value on the stack
450
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
719✔
451
                            {
452
                                context.ip = ip_or_val.pageAddr();
664✔
453
                                // we always push PP then IP, thus the next value
454
                                // MUST be the page pointer
455
                                context.pp = pop(context)->pageAddr();
664✔
456

457
                                returnFromFuncCall(context);
664✔
458
                                push(Builtins::nil, context);
664✔
459
                            }
664✔
460
                            // value on the stack
461
                            else [[likely]]
462
                            {
463
                                Value* ip;
464
                                do
55✔
465
                                {
466
                                    ip = popAndResolveAsPtr(context);
55✔
467
                                } while (ip->valueType() != ValueType::InstPtr);
55✔
468

469
                                context.ip = ip->pageAddr();
55✔
470
                                context.pp = pop(context)->pageAddr();
55✔
471

472
                                returnFromFuncCall(context);
55✔
473
                                push(std::move(ip_or_val), context);
55✔
474
                            }
475

476
                            if (context.fc <= untilFrameCount)
719✔
477
                                GOTO_HALT();
6✔
478
                        }
719✔
479

480
                        DISPATCH();
713✔
481
                    }
6✔
482

483
                    TARGET(HALT)
484
                    {
485
                        m_running = false;
6✔
486
                        GOTO_HALT();
6✔
487
                    }
4,951✔
488

489
                    TARGET(CALL)
490
                    {
491
                        // stack pointer + 2 because we push IP and PP
492
                        if (context.sp + 2u >= VMStackSize) [[unlikely]]
4,951✔
493
                            throwVMError(
1✔
494
                                ErrorKind::VM,
495
                                fmt::format(
2✔
496
                                    "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
1✔
497
                                    m_state.m_symbols[context.last_symbol]));
1✔
498
                        call(context, arg);
4,950✔
499
                        if (!m_running)
4,939✔
UNCOV
500
                            GOTO_HALT();
×
501
                        DISPATCH();
4,939✔
502
                    }
136✔
503

504
                    TARGET(CAPTURE)
505
                    {
506
                        if (!context.saved_scope)
136✔
507
                            context.saved_scope = Scope();
17✔
508

509
                        Value* ptr = (context.locals.back())[arg];
136✔
510
                        if (!ptr)
136✔
UNCOV
511
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
512
                        else
513
                        {
514
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
136✔
515
                            context.saved_scope.value().push_back(arg, *ptr);
136✔
516
                        }
517

518
                        DISPATCH();
136✔
519
                    }
272✔
520

521
                    TARGET(BUILTIN)
522
                    {
523
                        push(Builtins::builtins[arg].second, context);
272✔
524
                        DISPATCH();
272✔
525
                    }
3✔
526

527
                    TARGET(DEL)
528
                    {
529
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
3✔
530
                        {
531
                            if (var->valueType() == ValueType::User)
2✔
UNCOV
532
                                var->usertypeRef().del();
×
533
                            *var = Value();
2✔
534
                            DISPATCH();
2✔
535
                        }
536

537
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
UNCOV
538
                        DISPATCH();
×
539
                    }
17✔
540

541
                    TARGET(MAKE_CLOSURE)
542
                    {
543
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
17✔
544
                        context.saved_scope.reset();
17✔
545
                        DISPATCH();
17✔
546
                    }
754✔
547

548
                    TARGET(GET_FIELD)
549
                    {
550
                        Value* var = popAndResolveAsPtr(context);
754✔
551
                        if (var->valueType() != ValueType::Closure)
754✔
552
                        {
553
                            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
554
                                throwVMError(
1✔
555
                                    ErrorKind::Type,
556
                                    fmt::format(
4✔
557
                                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
558
                                        m_state.m_symbols[context.last_symbol],
1✔
559
                                        types_to_str[static_cast<std::size_t>(var->valueType())],
1✔
560
                                        m_state.m_symbols[arg]));
1✔
561
                            else
562
                                throwVMError(ErrorKind::Type,
×
563
                                             fmt::format(
×
UNCOV
564
                                                 "{} is not a Closure, can not get the field `{}' from it",
×
UNCOV
565
                                                 types_to_str[static_cast<std::size_t>(var->valueType())],
×
UNCOV
566
                                                 m_state.m_symbols[arg]));
×
UNCOV
567
                        }
×
568

569
                        if (Value* field = var->refClosure().refScope()[arg]; field != nullptr)
753✔
570
                        {
571
                            // check for CALL instruction
572
                            // doing a +1 on the IP to read the instruction because context.ip is already on the next instruction word (the padding)
573
                            if (context.ip + 1 < m_state.m_pages[context.pp].size() && m_state.m_pages[context.pp][context.ip + 1] == CALL)
752✔
574
                                push(Value(Closure(var->refClosure().scopePtr(), field->pageAddr())), context);
386✔
575
                            else
576
                                push(field, context);
366✔
577
                        }
752✔
578
                        else
579
                            throwVMError(ErrorKind::Scope, fmt::format("`{}' isn't in the closure environment: {}", m_state.m_symbols[arg], var->refClosure().toString(*this)));
1✔
580
                        DISPATCH();
752✔
581
                    }
×
582

583
                    TARGET(PLUGIN)
584
                    {
UNCOV
585
                        loadPlugin(arg, context);
×
UNCOV
586
                        DISPATCH();
×
587
                    }
111✔
588

589
                    TARGET(LIST)
590
                    {
591
                        {
592
                            Value l(ValueType::List);
111✔
593
                            if (arg != 0)
111✔
594
                                l.list().reserve(arg);
75✔
595

596
                            for (uint16_t i = 0; i < arg; ++i)
350✔
597
                                l.push_back(*popAndResolveAsPtr(context));
239✔
598
                            push(std::move(l), context);
111✔
599
                        }
111✔
600
                        DISPATCH();
111✔
601
                    }
9✔
602

603
                    TARGET(APPEND)
604
                    {
605
                        {
606
                            Value* list = popAndResolveAsPtr(context);
9✔
607
                            if (list->valueType() != ValueType::List)
9✔
UNCOV
608
                                types::generateError(
×
UNCOV
609
                                    "append",
×
UNCOV
610
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
611
                                    { *list });
×
612

613
                            const auto size = static_cast<uint16_t>(list->constList().size());
9✔
614

615
                            Value obj { *list };
9✔
616
                            obj.list().reserve(size + arg);
9✔
617

618
                            for (uint16_t i = 0; i < arg; ++i)
18✔
619
                                obj.push_back(*popAndResolveAsPtr(context));
9✔
620
                            push(std::move(obj), context);
9✔
621
                        }
9✔
622
                        DISPATCH();
9✔
623
                    }
3✔
624

625
                    TARGET(CONCAT)
626
                    {
627
                        {
628
                            Value* list = popAndResolveAsPtr(context);
3✔
629
                            if (list->valueType() != ValueType::List)
3✔
UNCOV
630
                                types::generateError(
×
UNCOV
631
                                    "concat",
×
UNCOV
632
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
633
                                    { *list });
×
634

635
                            Value obj { *list };
3✔
636

637
                            for (uint16_t i = 0; i < arg; ++i)
6✔
638
                            {
639
                                Value* next = popAndResolveAsPtr(context);
3✔
640

641
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
3✔
UNCOV
642
                                    types::generateError(
×
UNCOV
643
                                        "concat",
×
UNCOV
644
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
UNCOV
645
                                        { *list, *next });
×
646

647
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
3✔
648
                            }
3✔
649
                            push(std::move(obj), context);
3✔
650
                        }
3✔
651
                        DISPATCH();
3✔
652
                    }
35✔
653

654
                    TARGET(APPEND_IN_PLACE)
655
                    {
656
                        Value* list = popAndResolveAsPtr(context);
35✔
657

658
                        if (list->valueType() != ValueType::List)
35✔
UNCOV
659
                            types::generateError(
×
UNCOV
660
                                "append!",
×
UNCOV
661
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
662
                                { *list });
×
663

664
                        for (uint16_t i = 0; i < arg; ++i)
70✔
665
                            list->push_back(*popAndResolveAsPtr(context));
35✔
666
                        DISPATCH();
35✔
667
                    }
1✔
668

669
                    TARGET(CONCAT_IN_PLACE)
670
                    {
671
                        Value* list = popAndResolveAsPtr(context);
1✔
672

673
                        if (list->valueType() != ValueType::List)
1✔
UNCOV
674
                            types::generateError(
×
UNCOV
675
                                "concat",
×
UNCOV
676
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
677
                                { *list });
×
678

679
                        for (uint16_t i = 0; i < arg; ++i)
2✔
680
                        {
681
                            Value* next = popAndResolveAsPtr(context);
1✔
682

683
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
1✔
UNCOV
684
                                types::generateError(
×
UNCOV
685
                                    "concat!",
×
UNCOV
686
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
UNCOV
687
                                    { *list, *next });
×
688

689
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
1✔
690
                        }
1✔
691
                        DISPATCH();
1✔
692
                    }
4✔
693

694
                    TARGET(POP_LIST)
695
                    {
696
                        {
697
                            Value list = *popAndResolveAsPtr(context);
4✔
698
                            Value number = *popAndResolveAsPtr(context);
4✔
699

700
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
4✔
UNCOV
701
                                types::generateError(
×
UNCOV
702
                                    "pop",
×
UNCOV
703
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
UNCOV
704
                                    { list, number });
×
705

706
                            long idx = static_cast<long>(number.number());
4✔
707
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
4✔
708
                            if (std::cmp_greater_equal(idx, list.list().size()))
4✔
709
                                throwVMError(
1✔
710
                                    ErrorKind::Index,
711
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
1✔
712

713
                            list.list().erase(list.list().begin() + idx);
3✔
714
                            push(list, context);
3✔
715
                        }
4✔
716
                        DISPATCH();
3✔
717
                    }
36✔
718

719
                    TARGET(POP_LIST_IN_PLACE)
720
                    {
721
                        {
722
                            Value* list = popAndResolveAsPtr(context);
36✔
723
                            Value number = *popAndResolveAsPtr(context);
36✔
724

725
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
36✔
UNCOV
726
                                types::generateError(
×
UNCOV
727
                                    "pop!",
×
UNCOV
728
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
UNCOV
729
                                    { *list, number });
×
730

731
                            long idx = static_cast<long>(number.number());
36✔
732
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
36✔
733
                            if (std::cmp_greater_equal(idx, list->list().size()))
36✔
UNCOV
734
                                throwVMError(
×
735
                                    ErrorKind::Index,
UNCOV
736
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
×
737

738
                            list->list().erase(list->list().begin() + idx);
36✔
739
                        }
36✔
740
                        DISPATCH();
36✔
741
                    }
697✔
742

743
                    TARGET(POP)
744
                    {
745
                        pop(context);
697✔
746
                        DISPATCH();
697✔
747
                    }
11✔
748

749
                    TARGET(DUP)
750
                    {
751
                        context.stack[context.sp] = context.stack[context.sp - 1];
11✔
752
                        ++context.sp;
11✔
753
                        DISPATCH();
11✔
754
                    }
8,435✔
755

756
#pragma endregion
757

758
#pragma region "Operators"
759

760
                    TARGET(ADD)
761
                    {
762
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
8,435✔
763

764
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
8,435✔
765
                            push(Value(a->number() + b->number()), context);
8,434✔
766
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
1✔
767
                            push(Value(a->string() + b->string()), context);
1✔
768
                        else
769
                            types::generateError(
×
UNCOV
770
                                "+",
×
UNCOV
771
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
×
UNCOV
772
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
×
UNCOV
773
                                { *a, *b });
×
774
                        DISPATCH();
8,435✔
775
                    }
17✔
776

777
                    TARGET(SUB)
778
                    {
779
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17✔
780

781
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
17✔
UNCOV
782
                            types::generateError(
×
UNCOV
783
                                "-",
×
UNCOV
784
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
785
                                { *a, *b });
×
786
                        push(Value(a->number() - b->number()), context);
17✔
787
                        DISPATCH();
17✔
788
                    }
18✔
789

790
                    TARGET(MUL)
791
                    {
792
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
18✔
793

794
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
18✔
UNCOV
795
                            types::generateError(
×
UNCOV
796
                                "*",
×
UNCOV
797
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
798
                                { *a, *b });
×
799
                        push(Value(a->number() * b->number()), context);
18✔
800
                        DISPATCH();
18✔
801
                    }
10✔
802

803
                    TARGET(DIV)
804
                    {
805
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
10✔
806

807
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
10✔
UNCOV
808
                            types::generateError(
×
UNCOV
809
                                "/",
×
UNCOV
810
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
811
                                { *a, *b });
×
812
                        auto d = b->number();
10✔
813
                        if (d == 0)
10✔
814
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
815

816
                        push(Value(a->number() / d), context);
9✔
817
                        DISPATCH();
9✔
818
                    }
17✔
819

820
                    TARGET(GT)
821
                    {
822
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17✔
823
                        push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
17✔
824
                        DISPATCH();
17✔
825
                    }
2,056✔
826

827
                    TARGET(LT)
828
                    {
829
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,056✔
830
                        push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
2,054✔
831
                        DISPATCH();
2,055✔
832
                    }
3✔
833

834
                    TARGET(LE)
835
                    {
836
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
3✔
837
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
3✔
838
                        DISPATCH();
3✔
839
                    }
3✔
840

841
                    TARGET(GE)
842
                    {
843
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
3✔
844
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
3✔
845
                        DISPATCH();
3✔
846
                    }
61✔
847

848
                    TARGET(NEQ)
849
                    {
850
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
61✔
851
                        push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
61✔
852
                        DISPATCH();
61✔
853
                    }
226✔
854

855
                    TARGET(EQ)
856
                    {
857
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
226✔
858
                        push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
226✔
859
                        DISPATCH();
226✔
860
                    }
56✔
861

862
                    TARGET(LEN)
863
                    {
864
                        Value* a = popAndResolveAsPtr(context);
56✔
865

866
                        if (a->valueType() == ValueType::List)
56✔
867
                            push(Value(static_cast<int>(a->constList().size())), context);
37✔
868
                        else if (a->valueType() == ValueType::String)
19✔
869
                            push(Value(static_cast<int>(a->string().size())), context);
19✔
870
                        else
871
                            types::generateError(
×
UNCOV
872
                                "len",
×
UNCOV
873
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
UNCOV
874
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
875
                                { *a });
×
876
                        DISPATCH();
56✔
877
                    }
1✔
878

879
                    TARGET(EMPTY)
880
                    {
881
                        Value* a = popAndResolveAsPtr(context);
1✔
882

883
                        if (a->valueType() == ValueType::List)
1✔
884
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
×
885
                        else if (a->valueType() == ValueType::String)
1✔
886
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1✔
887
                        else
888
                            types::generateError(
×
UNCOV
889
                                "empty?",
×
UNCOV
890
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
UNCOV
891
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
892
                                { *a });
×
893
                        DISPATCH();
1✔
894
                    }
3✔
895

896
                    TARGET(TAIL)
897
                    {
898
                        Value* a = popAndResolveAsPtr(context);
3✔
899

900
                        if (a->valueType() == ValueType::List)
3✔
901
                        {
902
                            if (a->constList().size() < 2)
×
903
                                push(Value(ValueType::List), context);
×
904
                            else
905
                            {
906
                                std::vector<Value> tmp(a->constList().size() - 1);
×
907
                                for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
×
UNCOV
908
                                    tmp[i - 1] = a->constList()[i];
×
UNCOV
909
                                push(Value(std::move(tmp)), context);
×
UNCOV
910
                            }
×
UNCOV
911
                        }
×
912
                        else if (a->valueType() == ValueType::String)
3✔
913
                        {
914
                            if (a->string().size() < 2)
3✔
915
                                push(Value(ValueType::String), context);
2✔
916
                            else
917
                            {
918
                                Value b { *a };
1✔
919
                                b.stringRef().erase(b.stringRef().begin());
1✔
920
                                push(std::move(b), context);
1✔
921
                            }
1✔
922
                        }
3✔
923
                        else
924
                            types::generateError(
×
UNCOV
925
                                "tail",
×
UNCOV
926
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
UNCOV
927
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
928
                                { *a });
×
929
                        DISPATCH();
3✔
930
                    }
3✔
931

932
                    TARGET(HEAD)
933
                    {
934
                        Value* a = popAndResolveAsPtr(context);
3✔
935

936
                        if (a->valueType() == ValueType::List)
3✔
937
                        {
938
                            if (a->constList().empty())
×
UNCOV
939
                                push(Builtins::nil, context);
×
940
                            else
UNCOV
941
                                push(a->constList()[0], context);
×
UNCOV
942
                        }
×
943
                        else if (a->valueType() == ValueType::String)
3✔
944
                        {
945
                            if (a->string().empty())
3✔
946
                                push(Value(ValueType::String), context);
1✔
947
                            else
948
                                push(Value(std::string(1, a->stringRef()[0])), context);
2✔
949
                        }
3✔
950
                        else
951
                            types::generateError(
×
UNCOV
952
                                "head",
×
UNCOV
953
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
UNCOV
954
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
955
                                { *a });
×
956
                        DISPATCH();
3✔
957
                    }
3✔
958

959
                    TARGET(ISNIL)
960
                    {
961
                        Value* a = popAndResolveAsPtr(context);
3✔
962
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
3✔
963
                        DISPATCH();
3✔
964
                    }
×
965

966
                    TARGET(ASSERT)
967
                    {
968
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
×
969

970
                        if (b->valueType() != ValueType::String)
×
UNCOV
971
                            types::generateError(
×
972
                                "assert",
×
973
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
×
974
                                { *a, *b });
×
975

UNCOV
976
                        if (*a == Builtins::falseSym)
×
UNCOV
977
                            throw AssertionFailed(b->stringRef());
×
UNCOV
978
                        DISPATCH();
×
979
                    }
3✔
980

981
                    TARGET(TO_NUM)
982
                    {
983
                        Value* a = popAndResolveAsPtr(context);
3✔
984

985
                        if (a->valueType() != ValueType::String)
3✔
UNCOV
986
                            types::generateError(
×
UNCOV
987
                                "toNumber",
×
UNCOV
988
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
989
                                { *a });
×
990

991
                        double val;
992
                        if (Utils::isDouble(a->string(), &val))
3✔
993
                            push(Value(val), context);
2✔
994
                        else
995
                            push(Builtins::nil, context);
1✔
996
                        DISPATCH();
3✔
997
                    }
7✔
998

999
                    TARGET(TO_STR)
1000
                    {
1001
                        Value* a = popAndResolveAsPtr(context);
7✔
1002
                        push(Value(a->toString(*this)), context);
7✔
1003
                        DISPATCH();
7✔
1004
                    }
2,049✔
1005

1006
                    TARGET(AT)
1007
                    {
1008
                        {
1009
                            Value* b = popAndResolveAsPtr(context);
2,049✔
1010
                            Value a = *popAndResolveAsPtr(context);  // be careful, it's not a pointer
2,050✔
1011

1012
                            if (b->valueType() != ValueType::Number)
2,050✔
1013
                                types::generateError(
×
UNCOV
1014
                                    "@",
×
UNCOV
1015
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
UNCOV
1016
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
UNCOV
1017
                                    { a, *b });
×
1018

1019
                            long idx = static_cast<long>(b->number());
2,050✔
1020

1021
                            if (a.valueType() == ValueType::List)
2,050✔
1022
                            {
1023
                                if (std::cmp_less(std::abs(idx), a.list().size()))
2,046✔
1024
                                    push(a.list()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.list().size()) + idx : idx)], context);
2,045✔
1025
                                else
1026
                                    throwVMError(
1✔
1027
                                        ErrorKind::Index,
1028
                                        fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
1✔
1029
                            }
2,045✔
1030
                            else if (a.valueType() == ValueType::String)
4✔
1031
                            {
1032
                                if (std::cmp_less(std::abs(idx), a.string().size()))
4✔
1033
                                    push(Value(std::string(1, a.string()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.string().size()) + idx : idx)])), context);
3✔
1034
                                else
1035
                                    throwVMError(
1✔
1036
                                        ErrorKind::Index,
1037
                                        fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
1✔
1038
                            }
3✔
1039
                            else
1040
                                types::generateError(
×
UNCOV
1041
                                    "@",
×
UNCOV
1042
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
UNCOV
1043
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
UNCOV
1044
                                    { a, *b });
×
1045
                        }
2,050✔
1046
                        DISPATCH();
2,048✔
1047
                    }
2✔
1048

1049
                    TARGET(MOD)
1050
                    {
1051
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2✔
1052
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
2✔
UNCOV
1053
                            types::generateError(
×
UNCOV
1054
                                "mod",
×
UNCOV
1055
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1056
                                { *a, *b });
×
1057
                        push(Value(std::fmod(a->number(), b->number())), context);
2✔
1058
                        DISPATCH();
2✔
1059
                    }
13✔
1060

1061
                    TARGET(TYPE)
1062
                    {
1063
                        Value* a = popAndResolveAsPtr(context);
13✔
1064
                        if (a == &m_undefined_value) [[unlikely]]
13✔
UNCOV
1065
                            types::generateError(
×
UNCOV
1066
                                "type",
×
UNCOV
1067
                                { { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
×
UNCOV
1068
                                {});
×
1069

1070
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
13✔
1071
                        DISPATCH();
13✔
1072
                    }
2✔
1073

1074
                    TARGET(HASFIELD)
1075
                    {
1076
                        {
1077
                            Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
2✔
1078
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
2✔
UNCOV
1079
                                types::generateError(
×
UNCOV
1080
                                    "hasField",
×
UNCOV
1081
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
×
UNCOV
1082
                                    { *closure, *field });
×
1083

1084
                            auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef());
2✔
1085
                            if (it == m_state.m_symbols.end())
2✔
1086
                            {
1087
                                push(Builtins::falseSym, context);
1✔
1088
                                DISPATCH();
1✔
1089
                            }
1090

1091
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1092
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1093
                        }
1094
                        DISPATCH();
1✔
1095
                    }
9✔
1096

1097
                    TARGET(NOT)
1098
                    {
1099
                        Value* a = popAndResolveAsPtr(context);
9✔
1100
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
9✔
1101
                        DISPATCH();
9✔
1102
                    }
1103

1104
#pragma endregion
1105
                }
12✔
1106
#if ARK_USE_COMPUTED_GOTOS
1107
            dispatch_end:
1108
                do
12✔
1109
                {
1110
                } while (false);
12✔
1111
#endif
1112
            }
1113
        }
33✔
1114
        catch (const std::exception& e)
1115
        {
1116
            if (fail_with_exception)
21✔
1117
                throw;
21✔
1118

UNCOV
1119
            fmt::println("{}", e.what());
×
UNCOV
1120
            backtrace(context);
×
1121
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1122
            // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1123
            m_exit_code = 0;
1124
#else
UNCOV
1125
            m_exit_code = 1;
×
1126
#endif
1127
        }
33✔
1128
        catch (...)
1129
        {
UNCOV
1130
            if (fail_with_exception)
×
UNCOV
1131
                throw;
×
1132

1133
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1134
            throw;
1135
#endif
UNCOV
1136
            fmt::println("Unknown error");
×
UNCOV
1137
            backtrace(context);
×
UNCOV
1138
            m_exit_code = 1;
×
1139
        }
42✔
1140

1141
        return m_exit_code;
12✔
1142
    }
84✔
1143

1144
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
×
1145
    {
×
1146
        for (auto& local : std::ranges::reverse_view(context.locals))
×
1147
        {
1148
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
×
UNCOV
1149
                return id;
×
UNCOV
1150
        }
×
UNCOV
1151
        return std::numeric_limits<uint16_t>::max();
×
UNCOV
1152
    }
×
1153

1154
    void VM::throwVMError(ErrorKind kind, const std::string& message)
15✔
1155
    {
15✔
1156
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
15✔
1157
    }
15✔
1158

1159
    void VM::backtrace(ExecutionContext& context) noexcept
×
UNCOV
1160
    {
×
1161
        const std::size_t saved_ip = context.ip;
×
UNCOV
1162
        const std::size_t saved_pp = context.pp;
×
UNCOV
1163
        const uint16_t saved_sp = context.sp;
×
1164

UNCOV
1165
        if (const uint16_t original_frame_count = context.fc; original_frame_count > 1)
×
1166
        {
1167
            // display call stack trace
1168
            const Scope old_scope = context.locals.back();
×
1169

UNCOV
1170
            while (context.fc != 0)
×
1171
            {
1172
                fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan)));
×
1173
                if (context.pp != 0)
×
1174
                {
1175
                    const uint16_t id = findNearestVariableIdWithValue(
×
1176
                        Value(static_cast<PageAddr_t>(context.pp)),
×
UNCOV
1177
                        context);
×
1178

UNCOV
1179
                    if (id < m_state.m_symbols.size())
×
1180
                        fmt::println("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)));
×
1181
                    else  // should never happen
UNCOV
1182
                        fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold)));
×
1183

1184
                    Value* ip;
×
UNCOV
1185
                    do
×
1186
                    {
1187
                        ip = popAndResolveAsPtr(context);
×
1188
                    } while (ip->valueType() != ValueType::InstPtr);
×
1189

UNCOV
1190
                    context.ip = ip->pageAddr();
×
UNCOV
1191
                    context.pp = pop(context)->pageAddr();
×
1192
                    returnFromFuncCall(context);
×
1193
                }
×
1194
                else
1195
                {
1196
                    fmt::println("In global scope");
×
UNCOV
1197
                    break;
×
1198
                }
1199

UNCOV
1200
                if (original_frame_count - context.fc > 7)
×
1201
                {
UNCOV
1202
                    fmt::println("...");
×
UNCOV
1203
                    break;
×
1204
                }
1205
            }
1206

1207
            // display variables values in the current scope
1208
            fmt::println("\nCurrent scope variables values:");
×
1209
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
×
1210
            {
1211
                fmt::println(
×
UNCOV
1212
                    "{} = {}",
×
1213
                    fmt::styled(m_state.m_symbols[old_scope.m_data[i].first], fmt::fg(fmt::color::cyan)),
×
UNCOV
1214
                    old_scope.m_data[i].second.toString(*this));
×
1215
            }
×
1216

1217
            while (context.fc != 1)
×
1218
            {
1219
                Value* tmp = pop(context);
×
UNCOV
1220
                if (tmp->valueType() == ValueType::InstPtr)
×
1221
                    --context.fc;
×
1222
                *tmp = m_no_value;
×
UNCOV
1223
            }
×
1224
            // pop the PP as well
1225
            pop(context);
×
1226
        }
×
1227

1228
        std::cerr << "At IP: " << (saved_ip / 4)  // dividing by 4 because the instructions are actually on 4 bytes
×
UNCOV
1229
                  << ", PP: " << saved_pp
×
UNCOV
1230
                  << ", SP: " << saved_sp
×
UNCOV
1231
                  << "\n";
×
UNCOV
1232
    }
×
1233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc