/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 | } |