Coverage Report

Created: 2026-06-30 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/test/evmc/loader.c
Line
Count
Source
1
// EVMC: Ethereum Client-VM Connector API.
2
// Copyright 2018 The EVMC Authors.
3
// Licensed under the Apache License, Version 2.0.
4
5
#include <evmc/loader.h>
6
7
#include <evmc/evmc.h>
8
#include <evmc/helpers.h>
9
10
#include <stdarg.h>
11
#include <stdint.h>
12
#include <stdio.h>
13
#include <string.h>
14
15
#if defined(EVMC_LOADER_MOCK)
16
#include "../../test/unittests/loader_mock.h"
17
#elif defined(_WIN32)
18
#include <Windows.h>
19
#define DLL_HANDLE HMODULE
20
#define DLL_OPEN(filename) LoadLibrary(filename)
21
#define DLL_CLOSE(handle) FreeLibrary(handle)
22
#define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) GetProcAddress(handle, name)
23
#define DLL_GET_ERROR_MSG() NULL
24
#else
25
#include <dlfcn.h>
26
0
#define DLL_HANDLE void*
27
0
#define DLL_OPEN(filename) dlopen(filename, RTLD_LAZY)
28
0
#define DLL_CLOSE(handle) dlclose(handle)
29
// NOLINTNEXTLINE(performance-no-int-to-ptr)
30
0
#define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) dlsym(handle, name)
31
0
#define DLL_GET_ERROR_MSG() dlerror()
32
#endif
33
34
#ifdef __has_attribute
35
#if __has_attribute(format)
36
#define ATTR_FORMAT(archetype, string_index, first_to_check) \
37
    __attribute__((format(archetype, string_index, first_to_check)))
38
#endif
39
#endif
40
41
#ifndef ATTR_FORMAT
42
#define ATTR_FORMAT(...)
43
#endif
44
45
/*
46
 * Limited variant of strcpy_s().
47
 */
48
#if !defined(EVMC_LOADER_MOCK)
49
static
50
#endif
51
    int
52
    strcpy_sx(char* dest, size_t destsz, const char* src)
53
0
{
54
0
    size_t len = strlen(src);
55
0
    if (len >= destsz)
56
0
    {
57
        // The input src will not fit into the dest buffer.
58
        // Set the first byte of the dest to null to make it effectively empty string
59
        // and return error.
60
0
        dest[0] = 0;
61
0
        return 1;
62
0
    }
63
    // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
64
0
    memcpy(dest, src, len);
65
0
    dest[len] = 0;
66
0
    return 0;
67
0
}
68
69
enum
70
{
71
    PATH_MAX_LENGTH = 4096,
72
    LAST_ERROR_MSG_BUFFER_SIZE = 511
73
};
74
75
static const char* last_error_msg = NULL;
76
77
// Buffer for formatted error messages.
78
// It has one null byte extra to avoid buffer read overflow during concurrent access.
79
static char last_error_msg_buffer[LAST_ERROR_MSG_BUFFER_SIZE + 1];
80
81
ATTR_FORMAT(printf, 2, 3)
82
static enum evmc_loader_error_code set_error(enum evmc_loader_error_code error_code,
83
                                             const char* format,
84
                                             ...)
85
0
{
86
0
    va_list args;
87
0
    va_start(args, format);
88
    // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
89
0
    if (vsnprintf(last_error_msg_buffer, LAST_ERROR_MSG_BUFFER_SIZE, format, args) <
90
0
        LAST_ERROR_MSG_BUFFER_SIZE)
91
0
        last_error_msg = last_error_msg_buffer;
92
0
    va_end(args);
93
0
    return error_code;
94
0
}
95
96
97
evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* error_code)
98
0
{
99
0
    last_error_msg = NULL;  // Reset last error.
100
0
    enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
101
0
    evmc_create_fn create_fn = NULL;
102
103
0
    if (!filename)
104
0
    {
105
0
        ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be null");
106
0
        goto exit;
107
0
    }
108
109
0
    const size_t length = strlen(filename);
110
0
    if (length == 0)
111
0
    {
112
0
        ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be empty");
113
0
        goto exit;
114
0
    }
115
0
    else if (length > PATH_MAX_LENGTH)
116
0
    {
117
0
        ec = set_error(EVMC_LOADER_INVALID_ARGUMENT,
118
0
                       "invalid argument: file name is too long (%d, maximum allowed length is %d)",
119
0
                       (int)length, PATH_MAX_LENGTH);
120
0
        goto exit;
121
0
    }
122
123
0
    DLL_HANDLE handle = DLL_OPEN(filename);
124
0
    if (!handle)
125
0
    {
126
        // Get error message if available.
127
0
        last_error_msg = DLL_GET_ERROR_MSG();
128
0
        if (last_error_msg)
129
0
            ec = EVMC_LOADER_CANNOT_OPEN;
130
0
        else
131
0
            ec = set_error(EVMC_LOADER_CANNOT_OPEN, "cannot open %s", filename);
132
0
        goto exit;
133
0
    }
134
135
    // Create name buffer with the prefix.
136
0
    const char prefix[] = "evmc_create_";
137
0
    const size_t prefix_length = strlen(prefix);
138
0
    char prefixed_name[sizeof(prefix) + PATH_MAX_LENGTH];
139
0
    strcpy_sx(prefixed_name, sizeof(prefixed_name), prefix);
140
141
    // Find filename in the path.
142
0
    const char* sep_pos = strrchr(filename, '/');
143
#ifdef _WIN32
144
    // On Windows check also Windows classic path separator.
145
    const char* sep_pos_windows = strrchr(filename, '\\');
146
    sep_pos = sep_pos_windows > sep_pos ? sep_pos_windows : sep_pos;
147
#endif
148
0
    const char* name_pos = sep_pos ? sep_pos + 1 : filename;
149
150
    // Skip "lib" prefix if present.
151
0
    const char lib_prefix[] = "lib";
152
0
    const size_t lib_prefix_length = strlen(lib_prefix);
153
0
    if (strncmp(name_pos, lib_prefix, lib_prefix_length) == 0)
154
0
        name_pos += lib_prefix_length;
155
156
0
    char* base_name = prefixed_name + prefix_length;
157
0
    strcpy_sx(base_name, PATH_MAX_LENGTH, name_pos);
158
159
    // Trim all file extensions.
160
0
    char* ext_pos = strchr(prefixed_name, '.');
161
0
    if (ext_pos)
162
0
        *ext_pos = 0;
163
164
    // Replace all "-" with "_".
165
0
    char* dash_pos = base_name;
166
0
    while ((dash_pos = strchr(dash_pos, '-')) != NULL)
167
0
        *dash_pos++ = '_';
168
169
    // Search for the built function name.
170
0
    create_fn = DLL_GET_CREATE_FN(handle, prefixed_name);
171
172
0
    if (!create_fn)
173
0
        create_fn = DLL_GET_CREATE_FN(handle, "evmc_create");
174
175
0
    if (!create_fn)
176
0
    {
177
0
        DLL_CLOSE(handle);
178
0
        ec = set_error(EVMC_LOADER_SYMBOL_NOT_FOUND, "EVMC create function not found in %s",
179
0
                       filename);
180
0
    }
181
182
0
exit:
183
0
    if (error_code)
184
0
        *error_code = ec;
185
0
    return create_fn;
186
0
}
187
188
const char* evmc_last_error_msg(void)
189
0
{
190
0
    const char* m = last_error_msg;
191
0
    last_error_msg = NULL;
192
0
    return m;
193
0
}
194
195
struct evmc_vm* evmc_load_and_create(const char* filename, enum evmc_loader_error_code* error_code)
196
0
{
197
    // First load the DLL. This also resets the last_error_msg;
198
0
    evmc_create_fn create_fn = evmc_load(filename, error_code);
199
200
0
    if (!create_fn)
201
0
        return NULL;
202
203
0
    enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
204
205
0
    struct evmc_vm* vm = create_fn();
206
0
    if (!vm)
207
0
    {
208
0
        ec = set_error(EVMC_LOADER_VM_CREATION_FAILURE, "creating EVMC VM of %s has failed",
209
0
                       filename);
210
0
        goto exit;
211
0
    }
212
213
0
    if (!evmc_is_abi_compatible(vm))
214
0
    {
215
0
        ec = set_error(EVMC_LOADER_ABI_VERSION_MISMATCH,
216
0
                       "EVMC ABI version %d of %s mismatches the expected version %d",
217
0
                       vm->abi_version, filename, EVMC_ABI_VERSION);
218
0
        evmc_destroy(vm);
219
0
        vm = NULL;
220
0
        goto exit;
221
0
    }
222
223
0
exit:
224
0
    if (error_code)
225
0
        *error_code = ec;
226
227
0
    return vm;
228
0
}
229
230
/// Gets the token delimited by @p delim character of the string pointed by the @p str_ptr.
231
/// If the delimiter is not found, the whole string is returned.
232
/// The @p str_ptr is also slided after the delimiter or to the string end
233
/// if the delimiter is not found (in this case the @p str_ptr points to an empty string).
234
static char* get_token(char** str_ptr, char delim)
235
0
{
236
0
    char* str = *str_ptr;
237
0
    char* delim_pos = strchr(str, delim);
238
0
    if (delim_pos)
239
0
    {
240
        // If the delimiter is found, null it to get null-terminated prefix
241
        // and slide the str_ptr after the delimiter.
242
0
        *delim_pos = '\0';
243
0
        *str_ptr = delim_pos + 1;
244
0
    }
245
0
    else
246
0
    {
247
        // Otherwise, slide the str_ptr to the end and return the whole string as the prefix.
248
0
        *str_ptr += strlen(str);
249
0
    }
250
0
    return str;
251
0
}
252
253
struct evmc_vm* evmc_load_and_configure(const char* config, enum evmc_loader_error_code* error_code)
254
0
{
255
0
    enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
256
0
    struct evmc_vm* vm = NULL;
257
258
0
    char config_copy_buffer[PATH_MAX_LENGTH];
259
0
    if (strcpy_sx(config_copy_buffer, sizeof(config_copy_buffer), config) != 0)
260
0
    {
261
0
        ec = set_error(EVMC_LOADER_INVALID_ARGUMENT,
262
0
                       "invalid argument: configuration is too long (maximum allowed length is %d)",
263
0
                       (int)sizeof(config_copy_buffer));
264
0
        goto exit;
265
0
    }
266
267
0
    char* options = config_copy_buffer;
268
0
    const char* path = get_token(&options, ',');
269
270
0
    vm = evmc_load_and_create(path, error_code);
271
0
    if (!vm)
272
0
        return NULL;
273
274
0
    while (strlen(options) != 0)
275
0
    {
276
0
        if (vm->set_option == NULL)
277
0
        {
278
0
            ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s) does not support any options",
279
0
                           vm->name, path);
280
0
            goto exit;
281
0
        }
282
283
0
        char* option = get_token(&options, ',');
284
285
        // Slit option into name and value by taking the name token.
286
        // The option variable will have the value, can be empty.
287
0
        const char* name = get_token(&option, '=');
288
289
0
        enum evmc_set_option_result r = vm->set_option(vm, name, option);
290
0
        switch (r)
291
0
        {
292
0
        case EVMC_SET_OPTION_SUCCESS:
293
0
            break;
294
0
        case EVMC_SET_OPTION_INVALID_NAME:
295
0
            ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s): unknown option '%s'",
296
0
                           vm->name, path, name);
297
0
            goto exit;
298
0
        case EVMC_SET_OPTION_INVALID_VALUE:
299
0
            ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE,
300
0
                           "%s (%s): unsupported value '%s' for option '%s'", vm->name, path,
301
0
                           option, name);
302
0
            goto exit;
303
304
0
        default:
305
0
            ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE,
306
0
                           "%s (%s): unknown error when setting value '%s' for option '%s'",
307
0
                           vm->name, path, option, name);
308
0
            goto exit;
309
0
        }
310
0
    }
311
312
0
exit:
313
0
    if (error_code)
314
0
        *error_code = ec;
315
316
0
    if (ec == EVMC_LOADER_SUCCESS)
317
0
        return vm;
318
319
0
    if (vm)
320
0
        evmc_destroy(vm);
321
    return NULL;
322
0
}