GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 26.2% 130 / 0 / 497
Functions: 41.0% 16 / 0 / 39
Branches: 15.8% 105 / 0 / 663

core/PluginManager.cpp
Line Branch Exec Source
1 #include "PluginManager.h"
2 #include <avisynth.h>
3 #include <cstring>
4 #include <memory>
5 #include <unordered_set>
6 #include <avisynth_c.h>
7 #include "strings.h"
8 #include "InternalEnvironment.h"
9 #include <cassert>
10 #include "function.h"
11 #include <avs/filesystem.h>
12
13 #ifdef AVS_WINDOWS
14 #include <avs/win.h>
15 #else
16 #include <avs/posix.h>
17 #endif
18
19 #ifdef AVS_WINDOWS
20 #include <imagehlp.h>
21 #endif
22 #include "parser/script.h"
23 #include "parser/expression.h" // TODO we only need FunctionInstance from here
24
25 typedef const char* (__stdcall *AvisynthPluginInit3Func)(IScriptEnvironment* env, const AVS_Linkage* const vectors);
26 typedef const char* (__stdcall *AvisynthPluginInit2Func)(IScriptEnvironment_Avs25* env);
27 typedef const char* (AVSC_CC *AvisynthCPluginInitFunc)(AVS_ScriptEnvironment* env);
28
29 #ifdef AVS_WINDOWS // only Windows has a registry we care about
30 const char RegAvisynthKey[] = "Software\\Avisynth";
31 #if defined (AVS_WINDOWS_X86)
32 #if defined (__GNUC__)
33 const char RegPluginDirPlus_GCC[] = "PluginDir+GCC";
34 #if defined(X86_32)
35 #define GCC_WIN32
36 #endif // X86_32
37 #endif // __GNUC__
38 #endif // AVS_WINDOWS_X86
39 const char RegPluginDirClassic[] = "PluginDir2_5";
40 const char RegPluginDirPlus[] = "PluginDir+";
41 #endif // AVS_WINDOWS
42
43 #ifdef AVS_POSIX
44 #include <dlfcn.h>
45 // Redifining these is easier than adding several ifdefs.
46 #define HMODULE void*
47 #define FreeLibrary dlclose
48 #if defined(AVS_MACOS) || defined(AVS_BSD)
49 #include <sys/syslimits.h>
50 #endif
51 #endif
52
53 #ifdef AVS_MACOS
54 #include <mach-o/dyld.h>
55 #endif
56
57 /*
58 ---------------------------------------------------------------------------------
59 ---------------------------------------------------------------------------------
60 Static helpers
61 ---------------------------------------------------------------------------------
62 ---------------------------------------------------------------------------------
63 */
64
65 void IFunction::AddRef() {
66 InterlockedIncrement(&refcnt);
67 }
68
69 void IFunction::Release() {
70 if (InterlockedDecrement(&refcnt) <= 0)
71 delete this;
72 }
73
74 #ifdef AVS_WINDOWS // translate to Linux error handling
75 // Translates a Windows error code to a human-readable text message.
76 static std::string GetLastErrorText(DWORD nErrorCode)
77 {
78 wchar_t* msg;
79 // Ask Windows to prepare a standard message for a GetLastError() code:
80 if (0 == FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, nErrorCode, 0, (LPWSTR)&msg, 0, NULL))
81 return("Unknown error");
82 else
83 {
84 auto msg_utf8 = WideCharToUtf8(msg);
85 std::string ret(msg_utf8.get());
86 LocalFree(msg);
87 return ret;
88 }
89 }
90
91 // utf8 output
92 static bool GetRegString(HKEY rootKey, const char path[], const char entry[], std::string* result_utf8) {
93 HKEY AvisynthKey;
94
95 // Convert input path/entry (UTF-8/ANSI) to wide char for Unicode registry API
96 auto path_w = Utf8ToWideChar(path);
97 auto entry_w = Utf8ToWideChar(entry);
98
99 if (RegOpenKeyExW(rootKey, path_w.get(), 0, KEY_READ, &AvisynthKey) != ERROR_SUCCESS)
100 return false;
101
102 DWORD type = 0;
103 DWORD sizeBytes = 0;
104 LONG rc = RegQueryValueExW(AvisynthKey, entry_w.get(), NULL, &type, NULL, &sizeBytes);
105 if (rc != ERROR_SUCCESS) {
106 RegCloseKey(AvisynthKey);
107 return false;
108 }
109
110 // Handle empty value
111 if (sizeBytes == 0) {
112 *result_utf8 = std::string();
113 RegCloseKey(AvisynthKey);
114 return true;
115 }
116
117 // If value is stored as wide string, read via wide API and convert to UTF-8
118 if (type == REG_SZ || type == REG_EXPAND_SZ) {
119 // sizeBytes is number of bytes; number of wchar_t elements:
120 size_t wcharCount = (sizeBytes / sizeof(wchar_t));
121 // Ensure space for a terminating wchar_t
122 std::vector<wchar_t> buf(wcharCount + 1);
123 // Initialize to zero for safety
124 buf.assign(wcharCount + 1, L'\0');
125
126 rc = RegQueryValueExW(AvisynthKey, entry_w.get(), NULL, &type,
127 reinterpret_cast<LPBYTE>(buf.data()), &sizeBytes);
128 if (rc != ERROR_SUCCESS) {
129 RegCloseKey(AvisynthKey);
130 return false;
131 }
132
133 // Ensure null-termination (sizeBytes may include or exclude terminator)
134 size_t charsRead = (sizeBytes / sizeof(wchar_t));
135 if (charsRead == 0)
136 buf[0] = L'\0';
137 else
138 buf[std::min(charsRead, buf.size() - 1)] = L'\0';
139
140 auto utf8 = WideCharToUtf8(buf.data());
141 *result_utf8 = std::string(utf8.get());
142
143 RegCloseKey(AvisynthKey);
144 return true;
145 }
146
147 // Fallback: read ANSI data and convert to UTF-8
148 {
149 DWORD sizeA = 0;
150 rc = RegQueryValueExA(AvisynthKey, entry, NULL, NULL, NULL, &sizeA);
151 if (rc != ERROR_SUCCESS) {
152 RegCloseKey(AvisynthKey);
153 return false;
154 }
155
156 std::vector<char> bufA(sizeA + 1);
157 if (sizeA > 0)
158 memset(bufA.data(), 0, sizeA + 1);
159
160 rc = RegQueryValueExA(AvisynthKey, entry, NULL, NULL,
161 reinterpret_cast<LPBYTE>(bufA.data()), &sizeA);
162 if (rc != ERROR_SUCCESS) {
163 RegCloseKey(AvisynthKey);
164 return false;
165 }
166
167 // Ensure null-terminated
168 bufA[std::min<size_t>(sizeA, bufA.size() - 1)] = '\0';
169
170 // Convert ANSI -> wide (system codepage) -> UTF-8
171 int wideLen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, bufA.data(), -1, NULL, 0);
172 if (wideLen <= 0) {
173 RegCloseKey(AvisynthKey);
174 return false;
175 }
176 std::vector<wchar_t> wbuf(wideLen + 1);
177 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, bufA.data(), -1, wbuf.data(), wideLen);
178 wbuf[wideLen] = L'\0';
179
180 auto utf8 = WideCharToUtf8(wbuf.data());
181 *result_utf8 = std::string(utf8.get());
182
183 RegCloseKey(AvisynthKey);
184 return true;
185 }
186 }
187
188 #endif // AVS_WINDOWS
189
190 // see also: AVSFunction::TypeMatch
191 static bool IsParameterTypeSpecifier(char c) {
192 switch (c) {
193 case 'b': case 'i': case 'f': case 's': case 'c': case '.':
194 // case 'd': case 'l':
195 // from v11 f and i will accept 64 bit data as well
196 case 'n':
197 case 'a': // Arrays as function parameters
198 return true;
199 default:
200 return false;
201 }
202 }
203
204 static bool IsParameterTypeModifier(char c) {
205 switch (c) {
206 case '+': case '*':
207 return true;
208 default:
209 return false;
210 }
211 }
212
213 static bool IsValidParameterString(const char* p) {
214 // does not check for logical errors such as
215 // when unnamed untyped array (.+) is followed by additional parameters
216 int state = 0;
217 char c;
218 while ((c = *p++) != '\0' && state != -1) {
219 switch (state) {
220 case 0:
221 if (IsParameterTypeSpecifier(c)) {
222 state = 1;
223 }
224 else if (c == '[') {
225 state = 2;
226 }
227 else {
228 state = -1;
229 }
230 break;
231
232 case 1:
233 if (IsParameterTypeSpecifier(c)) {
234 // do nothing; stay in the current state
235 }
236 else if (c == '[') {
237 state = 2;
238 }
239 else if (IsParameterTypeModifier(c)) {
240 state = 0;
241 }
242 else {
243 state = -1;
244 }
245 break;
246
247 case 2:
248 if (c == ']') {
249 state = 3;
250 }
251 else {
252 // do nothing; stay in the current state
253 }
254 break;
255
256 case 3:
257 if (IsParameterTypeSpecifier(c)) {
258 state = 1;
259 }
260 else {
261 state = -1;
262 }
263 break;
264 }
265 }
266
267 // states 0, 1 are the only ending states we accept
268 return state == 0 || state == 1;
269 }
270
271 /*
272 ---------------------------------------------------------------------------------
273 ---------------------------------------------------------------------------------
274 AVSFunction
275 ---------------------------------------------------------------------------------
276 ---------------------------------------------------------------------------------
277 */
278
279 3170400 static std::unique_ptr<char[]> DuplicateString(const char* source)
280 {
281
2/2
✓ Branch 2 → 3 taken 919416 times.
✓ Branch 2 → 4 taken 2250984 times.
3170400 if (!source)
282 919416 return {};
283
284 2250984 const size_t length = std::strlen(source);
285
1/2
✓ Branch 4 → 5 taken 2250984 times.
✗ Branch 4 → 10 not taken.
2250984 auto result = std::make_unique<char[]>(length + 1);
286 2250984 std::memcpy(result.get(), source, length + 1);
287 2250984 return result;
288 2250984 }
289
290 42272 AVSFunction::AVSFunction(void*) :
291 42272 AVSFunction(NULL, NULL, NULL, NULL, NULL, NULL, false, false)
292 42272 {}
293
294 542931 AVSFunction::AVSFunction(const char* _name, const char* _plugin_basename, const char* _param_types, apply_func_t _apply) :
295 542931 AVSFunction(_name, _plugin_basename, _param_types, _apply, NULL, NULL, false, false)
296 542931 {}
297
298 207397 AVSFunction::AVSFunction(const char* _name, const char* _plugin_basename, const char* _param_types, apply_func_t _apply, void *_user_data) :
299 207397 AVSFunction(_name, _plugin_basename, _param_types, _apply, _user_data, NULL, false, false)
300 207397 {}
301
302 792600 AVSFunction::AVSFunction(const char* _name, const char* _plugin_basename, const char* _param_types, apply_func_t _apply, void *_user_data, const char* _dll_path,
303 792600 bool _isPluginAvs25, bool _isPluginPreV11C) :
304 792600 Function()
305 {
306 792600 std::string canonical_name;
307
2/2
✓ Branch 3 → 4 taken 750328 times.
✓ Branch 3 → 10 taken 42272 times.
792600 if (_name) {
308
2/4
✓ Branch 4 → 5 taken 750328 times.
✗ Branch 4 → 6 not taken.
✓ Branch 7 → 8 taken 750328 times.
✗ Branch 7 → 33 not taken.
750328 canonical_name.assign(_plugin_basename ? _plugin_basename : "");
309
2/4
✓ Branch 8 → 9 taken 750328 times.
✗ Branch 8 → 33 not taken.
✓ Branch 9 → 10 taken 750328 times.
✗ Branch 9 → 33 not taken.
750328 canonical_name.append("_").append(_name);
310 }
311
312
1/2
✓ Branch 10 → 11 taken 792600 times.
✗ Branch 10 → 33 not taken.
792600 auto dll_path_owner = DuplicateString(_dll_path);
313
1/2
✓ Branch 11 → 12 taken 792600 times.
✗ Branch 11 → 31 not taken.
792600 auto name_owner = DuplicateString(_name);
314
1/2
✓ Branch 12 → 13 taken 792600 times.
✗ Branch 12 → 29 not taken.
792600 auto param_types_owner = DuplicateString(_param_types);
315
3/4
✓ Branch 13 → 14 taken 750328 times.
✓ Branch 13 → 15 taken 42272 times.
✓ Branch 16 → 17 taken 792600 times.
✗ Branch 16 → 27 not taken.
792600 auto canon_name_owner = DuplicateString(_name ? canonical_name.c_str() : nullptr);
316
317 792600 apply = _apply;
318 792600 user_data = _user_data;
319 792600 isPluginAvs25 = _isPluginAvs25;
320 792600 isPluginPreV11C = _isPluginPreV11C;
321
322 792600 dll_path = dll_path_owner.release();
323 792600 name = name_owner.release();
324 792600 param_types = param_types_owner.release();
325 792600 canon_name = canon_name_owner.release();
326 792600 }
327
328 792600 AVSFunction::~AVSFunction()
329 {
330
2/2
✓ Branch 2 → 3 taken 750328 times.
✓ Branch 2 → 4 taken 42272 times.
792600 delete [] canon_name;
331
2/2
✓ Branch 4 → 5 taken 750328 times.
✓ Branch 4 → 6 taken 42272 times.
792600 delete [] name;
332
2/2
✓ Branch 6 → 7 taken 750328 times.
✓ Branch 6 → 8 taken 42272 times.
792600 delete [] param_types;
333
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 792600 times.
792600 delete [] dll_path;
334 792600 }
335
336 386629 bool AVSFunction::empty() const
337 {
338 386629 return NULL == name;
339 }
340
341 8 bool AVSFunction::IsScriptFunction(const Function* func)
342 {
343 8 return ( (func->apply == &(FunctionInstance::Execute_))
344
1/2
✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 7 not taken.
8 || (func->apply == &(ScriptFunction::Execute))
345
1/2
✓ Branch 4 → 5 taken 8 times.
✗ Branch 4 → 7 not taken.
8 || (func->apply == &Eval)
346
1/2
✓ Branch 5 → 6 taken 8 times.
✗ Branch 5 → 7 not taken.
8 || (func->apply == &EvalOop)
347
2/4
✓ Branch 2 → 3 taken 8 times.
✗ Branch 2 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 8 times.
16 || (func->apply == &Import)
348 8 );
349 }
350
351 21 bool AVSFunction::SingleTypeMatch(char type, const AVSValue& arg, bool strict) {
352
5/9
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 7 times.
✓ Branch 2 → 6 taken 4 times.
✓ Branch 2 → 8 taken 1 time.
✓ Branch 2 → 16 taken 1 time.
✓ Branch 2 → 18 taken 8 times.
✗ Branch 2 → 20 not taken.
✗ Branch 2 → 22 not taken.
✗ Branch 2 → 24 not taken.
21 switch (type) {
353 case '.': return true;
354 7 case 'b': return arg.IsBool();
355 4 case 'i': return arg.IsInt(); // IsInt is true for long (int64) parameters as well, worst case they will be AsInt-ed, or can use AsLong
356
3/6
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 14 not taken.
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 13 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
1 case 'f': return arg.IsFloat() && (!strict || !arg.IsInt()); // IsFloat is true for 'double' as well
357 1 case 's': return arg.IsString();
358 8 case 'c': return arg.IsClip();
359 case 'n': return arg.IsFunction();
360 case 'a': return arg.IsArray(); // PF 161028 AVS+ script arrays
361 default: return false;
362 }
363 }
364
365 bool AVSFunction::SingleTypeMatchArray(char type, const AVSValue& arg, bool strict) {
366 if (!arg.IsArray())
367 return false;
368
369 for (int i = 0; i < arg.ArraySize(); i++)
370 {
371 if (!SingleTypeMatch(type, arg[i], strict))
372 return false;
373 }
374
375 return true;
376 }
377
378
379 8 bool AVSFunction::TypeMatch(const char* param_types, const AVSValue* args, size_t num_args, bool strict, IScriptEnvironment* env) {
380
381 8 bool optional = false;
382
383 /* examples
384 { "StackHorizontal", BUILTIN_FUNC_PREFIX, "cc+", StackHorizontal::Create },
385 { "Spline", BUILTIN_FUNC_PREFIX, "[x]ff+[cubic]b", Spline },
386 { "Select", BUILTIN_FUNC_PREFIX, "i.+", Select },
387 { "Array", BUILTIN_FUNC_PREFIX, ".*", ArrayCreate },
388 { "IsArray", BUILTIN_FUNC_PREFIX, ".", IsArray },
389 { "ArrayGet", BUILTIN_FUNC_PREFIX, ".s", ArrayGet },
390 { "ArrayGet", BUILTIN_FUNC_PREFIX, ".i+", ArrayGet }, // .+i+ syntax is not possible.
391 { "ArraySize", BUILTIN_FUNC_PREFIX, ".", ArraySize },
392 */
393
394 // arguments are provided in a flattened way (flattened=array elements extracted)
395 // e.g. string array is provided here string,string,string
396
397 // '*' or '+' to indicate "zero or more" or "one or more"
398 // '.' matches a single argument of any type. To match multiple arguments of any type, use ".*" or ".+".
399
400 8 size_t i = 0;
401
2/2
✓ Branch 49 → 3 taken 23 times.
✓ Branch 49 → 50 taken 8 times.
31 while (i < num_args) {
402
403
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 23 times.
23 if (*param_types == '\0') {
404 // more args than params
405 return false;
406 }
407
408
2/2
✓ Branch 5 → 6 taken 14 times.
✓ Branch 5 → 10 taken 9 times.
23 if (*param_types == '[') {
409 // named arg: skip over the name
410 14 param_types = strchr(param_types+1, ']');
411
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 14 times.
14 if (param_types == NULL) {
412 env->ThrowError("TypeMatch: unterminated parameter name (bug in filter)");
413 }
414
415 14 ++param_types;
416 14 optional = true;
417
418
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 14 times.
14 if (*param_types == '\0') {
419 env->ThrowError("TypeMatch: no type specified for optional parameter (bug in filter)");
420 }
421 }
422
423
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 23 times.
23 if (param_types[1] == '*') {
424 // skip over initial test of type for '*' (since zero matches is ok)
425 ++param_types;
426 }
427
428 // see also: IsParameterTypeSpecifier
429
1/4
✓ Branch 12 → 13 taken 23 times.
✗ Branch 12 → 31 not taken.
✗ Branch 12 → 32 not taken.
✗ Branch 12 → 47 not taken.
23 switch (*param_types) {
430 23 case 'b': case 'i': case 'f': case 's': case 'c':
431 // case 'd': case 'l':
432 // from v11 f and i will accept 64 bit data as well
433 case 'n':
434 case 'a':
435 // PF 2016: 'a' is special letter for script arrays, but if possible we are using .* and .+ (legacy Avisynth style) instead
436 // Note (2021): 'a' is still not used
437 // cons: no z or nz (+ or *) possibility
438 // no type check (array of int)
439 // cannot be used in plugins which are intended to work for Avisynth 2.6 Classic. ("a" is invalid in function signature -> plugin load error)
440 // pros: clean syntax, accept _only_ arrays when required, no comma-delimited-list-to-array option (like in old Avisynth syntax)
441 // array arguments are not necessarily "flattened" when TypeMatch is called.
442 46 if (param_types[1] == '+' // parameter indicates an array-type args[i]
443 && args[i].IsArray() // allow single e.g. 'c' parameter in place of a 'c+' requirement
444
2/6
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 18 taken 23 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 23 times.
23 && *param_types != 'a'
445 )
446 {
447 ++param_types; // will be found in case '+' section
448 break;
449 }
450
451
2/2
✓ Branch 23 → 24 taken 12 times.
✓ Branch 23 → 27 taken 2 times.
14 if ( (!optional || args[i].Defined())
452
4/6
✓ Branch 21 → 22 taken 14 times.
✓ Branch 21 → 24 taken 9 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 21 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 23 times.
37 && !SingleTypeMatch(*param_types, args[i], strict))
453 return false;
454
455 23 ++param_types;
456 23 ++i;
457 23 break;
458
459 case '.': // any type
460 // This allows even an array in the place of a "."
461 // Use cases: IsArray "." can be fed with any AvsValue. ArrayGet ".i+" requires an array in the place of "." as well.
462 // Array-ness of such AVSValue parameters can be checked in the function itself.
463 ++param_types;
464 ++i;
465 break;
466 case '+': case '*':
467 // check array content type if required
468 if (args[i].IsArray() && param_types[-1] != '.') {
469 // A script can provide an array argument in an direct array-type variable.
470 // e.g. a user defined script function function Summa(int_array "x") will translate to "[x]i*"
471 // parameter list. Passing an integer array directly e.g. [1,2,3] will be handled here.
472 // All elements in the array should match with the type character preceding '+' or '*'
473 // (There was another option in legacy AviSynth: the comma separated values e.g. 1,2,3
474 // could be recognized and moved to an unnamed array, this is check later)
475 if (!SingleTypeMatchArray(param_types[-1], args[i], strict))
476 return false;
477 ++param_types;
478 ++i;
479 }
480 else
481 // Legacy Avisynth array check.
482 // Array of arguments of known types last until an argument of another type is found.
483 // This is the reason why an .+ or .* (array of anything) must only appear at the end
484 // of the parameter list since we cannot detect type-change in an any-type argument sequence.
485 if (!SingleTypeMatch(param_types[-1], args[i], strict)) {
486 // we're done with the + or *, parameter type has been changed
487 ++param_types;
488 }
489 else {
490 // parameter type matched, step parameter pointer but leave type pointer
491 ++i;
492 }
493 break;
494 default:
495 env->ThrowError("TypeMatch: invalid character in parameter list (bug in filter)");
496 }
497 }
498
499 // We're out of args. We have a match if one of the following is true:
500 // (a) we're out of params.
501 // (b) remaining params are named i.e. optional.
502 // (c) we're at a '+' or '*' and any remaining params are optional.
503
504
2/4
✓ Branch 50 → 51 taken 8 times.
✗ Branch 50 → 52 not taken.
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 53 taken 8 times.
8 if (*param_types == '+' || *param_types == '*')
505 param_types += 1;
506
507
3/4
✓ Branch 53 → 54 taken 5 times.
✓ Branch 53 → 55 taken 3 times.
✓ Branch 54 → 55 taken 5 times.
✗ Branch 54 → 56 not taken.
8 if (*param_types == '\0' || *param_types == '[')
508 8 return true;
509
510 while (param_types[1] == '*') {
511 param_types += 2;
512 if (*param_types == '\0' || *param_types == '[')
513 return true;
514 }
515
516 return false;
517 }
518
519 8 bool AVSFunction::ArgNameMatch(const char* param_types, size_t args_names_count, const char* const* arg_names) {
520
521
1/2
✗ Branch 17 → 3 not taken.
✓ Branch 17 → 18 taken 8 times.
8 for (size_t i=0; i<args_names_count; ++i) {
522 if (arg_names[i]) {
523 bool found = false;
524 size_t len = strlen(arg_names[i]);
525 for (const char* p = param_types; *p; ++p) {
526 if (*p == '[') {
527 p += 1;
528 const char* q = strchr(p, ']');
529 if (!q) return false;
530 if (len == q-p && !_strnicmp(arg_names[i], p, q-p)) {
531 found = true;
532 break;
533 }
534 p = q+1;
535 }
536 }
537 if (!found) return false;
538 }
539 }
540 8 return true;
541 }
542
543 /*
544 ---------------------------------------------------------------------------------
545 ---------------------------------------------------------------------------------
546 PluginFile
547 ---------------------------------------------------------------------------------
548 ---------------------------------------------------------------------------------
549 */
550
551
552 struct PluginFile
553 {
554 std::string FilePath; // Fully qualified, canonical file path
555 std::string BaseName; // Only file name, without extension
556 HMODULE Library; // LoadLibrary handle
557 bool isPluginAvs25;
558 bool isPluginPreV11C;
559 bool isPluginC; // we register it, but it won't be used
560
561 PluginFile(const std::string &filePath);
562 };
563
564 PluginFile::PluginFile(const std::string &filePath) :
565 FilePath(GetFullPathNameWrapUtf8(filePath)), BaseName(), Library(NULL),
566 isPluginAvs25(false), isPluginPreV11C(false), isPluginC(false)
567 {
568 // Turn all '\' into '/'
569 replace(FilePath, '\\', '/');
570
571 // Find position of dot in extension
572 size_t dot_pos = FilePath.rfind('.');
573
574 // Find position of last directory slash
575 size_t slash_pos = FilePath.rfind('/');
576
577 // Extract basename
578 if ((dot_pos != std::string::npos) && (slash_pos != std::string::npos))
579 {// we have both a slash and a dot
580 if (dot_pos > slash_pos)
581 BaseName = FilePath.substr(slash_pos+1, dot_pos - slash_pos - 1);
582 else
583 BaseName = FilePath.substr(slash_pos+1, std::string::npos);
584 }
585 else if ((dot_pos == std::string::npos) && (slash_pos != std::string::npos))
586 {// we have a slash but no dot
587 // Extract basename
588 BaseName = FilePath.substr(slash_pos+1, std::string::npos);
589 }
590 else
591 {// everything else
592 // Because we have used GetFullPathName, FilePath should contain an absolute path,
593 // meaning that this case should be unreachable, but the devil never sleeps.
594 assert(0);
595 BaseName = FilePath;
596 }
597 }
598
599 /*
600 ---------------------------------------------------------------------------------
601 ---------------------------------------------------------------------------------
602 PluginManager
603 ---------------------------------------------------------------------------------
604 ---------------------------------------------------------------------------------
605 */
606
607 642 PluginManager::PluginManager(InternalEnvironment* env) :
608 642 Env(env), PluginInLoad(NULL), AutoloadExecuted(false), Autoloading(false)
609 {
610
2/4
✓ Branch 8 → 9 taken 642 times.
✗ Branch 8 → 14 not taken.
✓ Branch 9 → 10 taken 642 times.
✗ Branch 9 → 12 not taken.
642 env->SetGlobalVar("$PluginFunctions$", AVSValue(""));
611 642 }
612
613 void PluginManager::ClearAutoloadDirs()
614 {
615 if (AutoloadExecuted)
616 Env->ThrowError("Cannot modify directory list after the autoload procedure has already executed.");
617
618 AutoloadDirs.clear();
619 }
620
621 static fs::path PathFromUtf8(const std::string& utf8)
622 {
623 #ifdef AVS_WINDOWS
624 if (utf8.empty()) return fs::path();
625 auto wstr = Utf8ToWideChar(utf8.c_str());
626 return fs::path(wstr.get());
627 #else
628 return fs::path(utf8);
629 #endif
630 }
631
632 2568 void PluginManager::AddAutoloadDir(const std::string &dirPath_utf8, bool toFront)
633 {
634
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2568 times.
2568 if (AutoloadExecuted)
635 Env->ThrowError("Cannot modify directory list after the autoload procedure has already executed.");
636
637
1/2
✓ Branch 4 → 5 taken 2568 times.
✗ Branch 4 → 161 not taken.
2568 std::string dir(dirPath_utf8);
638
639 #if !defined(AVS_BSD)
640 // Any use of /proc should be avoided on BSD, since
641 // most of them have removed it or discourage its use.
642 // Thankfully, it actually looks like the need for it
643 // is to simply populate the PROGRAMDIR variable for
644 // AddAutoloadDirs, but on POSIX systems this variable
645 // should probably not be expected to be as flexible
646 // as it is on Windows, negating the need for pulling
647 // it out programmatically. Since the macOS and Linux
648 // forms of the code still function, leave those alone.
649 2568 std::string ExeFilePath;
650 #ifdef AVS_WINDOWS
651 // get folder of our executable as wide char and convert to UTF-8
652 {
653 WCHAR ExeFilePathW[AVS_MAX_PATH];
654 // Ensure buffer is zeroed (older Windows may not null-terminate)
655 // e.g. WinXP does not terminate the result of GetModuleFileName with a zero, so me must zero our buffer
656 memset(ExeFilePathW, 0, sizeof(ExeFilePathW));
657 DWORD len = GetModuleFileNameW(NULL, ExeFilePathW, AVS_MAX_PATH);
658 if (len == 0) {
659 // Fallback to empty string on failure
660 ExeFilePath.clear();
661 }
662 else {
663 // Convert wide-char path to UTF-8 for internal use
664 auto exe_utf8 = WideCharToUtf8(ExeFilePathW);
665 ExeFilePath = exe_utf8.get();
666 }
667 }
668 #else // AVS_POSIX
669 2568 char buf[PATH_MAX + 1] {};
670 #ifdef AVS_LINUX
671
1/2
✓ Branch 7 → 8 taken 2568 times.
✗ Branch 7 → 9 not taken.
2568 if (readlink("/proc/self/exe", buf, sizeof(buf) - 1) != -1)
672 #elif defined(AVS_MACOS)
673 uint32_t size = sizeof(buf) - 1;
674 if (_NSGetExecutablePath(buf, &size) == 0)
675 #endif // AVS_LINUX
676 {
677
1/2
✓ Branch 8 → 9 taken 2568 times.
✗ Branch 8 → 157 not taken.
2568 ExeFilePath = buf;
678 }
679 #endif
680
1/2
✓ Branch 9 → 10 taken 2568 times.
✗ Branch 9 → 157 not taken.
2568 std::string ExeFileDir(ExeFilePath);
681
1/2
✓ Branch 10 → 11 taken 2568 times.
✗ Branch 10 → 155 not taken.
2568 replace(ExeFileDir, '\\', '/');
682 #ifndef AVS_HAIKU
683 // Haiku's exe path stuff differs enough from the *nix OSes
684 // that it fails spectacularly when loading the library in a client
685 // like avs2yuv or FFmpeg. Try to skip this for now and hope
686 // this doesn't cause more errors.
687
2/4
✓ Branch 12 → 13 taken 2568 times.
✗ Branch 12 → 155 not taken.
✓ Branch 13 → 14 taken 2568 times.
✗ Branch 13 → 155 not taken.
2568 ExeFileDir = ExeFileDir.erase(ExeFileDir.rfind('/'), std::string::npos);
688 #endif
689 #endif // !AVS_BSD
690
691 // variable expansion
692 // now "dir" is utf8, so we can use utf8 variants of macros
693
4/8
✓ Branch 16 → 17 taken 2568 times.
✗ Branch 16 → 111 not taken.
✓ Branch 17 → 18 taken 2568 times.
✗ Branch 17 → 111 not taken.
✓ Branch 20 → 21 taken 2568 times.
✗ Branch 20 → 105 not taken.
✓ Branch 21 → 22 taken 2568 times.
✗ Branch 21 → 103 not taken.
10272 replace_beginning(dir, "SCRIPTDIR", Env->GetVarString("$ScriptDirUtf8$", ""));
694
4/8
✓ Branch 28 → 29 taken 2568 times.
✗ Branch 28 → 123 not taken.
✓ Branch 29 → 30 taken 2568 times.
✗ Branch 29 → 123 not taken.
✓ Branch 32 → 33 taken 2568 times.
✗ Branch 32 → 117 not taken.
✓ Branch 33 → 34 taken 2568 times.
✗ Branch 33 → 115 not taken.
10272 replace_beginning(dir, "MAINSCRIPTDIR", Env->GetVarString("$MainScriptDirUtf8$", ""));
695 #if !defined(AVS_BSD)
696
2/4
✓ Branch 40 → 41 taken 2568 times.
✗ Branch 40 → 129 not taken.
✓ Branch 41 → 42 taken 2568 times.
✗ Branch 41 → 127 not taken.
2568 replace_beginning(dir, "PROGRAMDIR", ExeFileDir);
697 #endif
698
699 // further macro expansions on Windows
700 2568 std::string plugin_dir;
701 #ifdef AVS_WINDOWS
702 // folders are read as utf8, can contain non-ansi characters as well
703 // where registry entry does not exist, delete the whole macro string if it contains only that macro
704 #if defined (AVS_WINDOWS_X86)
705 #if defined (__GNUC__)
706 if (GetRegString(HKEY_CURRENT_USER, RegAvisynthKey, RegPluginDirPlus_GCC, &plugin_dir))
707 replace_beginning(dir, "USER_PLUS_PLUGINS", plugin_dir);
708 else
709 replace_beginning(dir, "USER_PLUS_PLUGINS", "");
710 if (GetRegString(HKEY_LOCAL_MACHINE, RegAvisynthKey, RegPluginDirPlus_GCC, &plugin_dir))
711 replace_beginning(dir, "MACHINE_PLUS_PLUGINS", plugin_dir);
712 else
713 replace_beginning(dir, "MACHINE_PLUS_PLUGINS", "");
714 #else
715 // note: if e.g HKCU/PluginDir+ does not exist, USER_PLUS_PLUGINS as a string remain in search path
716 if (GetRegString(HKEY_CURRENT_USER, RegAvisynthKey, RegPluginDirPlus, &plugin_dir))
717 replace_beginning(dir, "USER_PLUS_PLUGINS", plugin_dir);
718 else
719 replace_beginning(dir, "USER_PLUS_PLUGINS", "");
720 if (GetRegString(HKEY_LOCAL_MACHINE, RegAvisynthKey, RegPluginDirPlus, &plugin_dir))
721 replace_beginning(dir, "MACHINE_PLUS_PLUGINS", plugin_dir);
722 else
723 replace_beginning(dir, "MACHINE_PLUS_PLUGINS", "");
724 if (GetRegString(HKEY_CURRENT_USER, RegAvisynthKey, RegPluginDirClassic, &plugin_dir))
725 replace_beginning(dir, "USER_CLASSIC_PLUGINS", plugin_dir);
726 else
727 replace_beginning(dir, "USER_CLASSIC_PLUGINS", "");
728 if (GetRegString(HKEY_LOCAL_MACHINE, RegAvisynthKey, RegPluginDirClassic, &plugin_dir))
729 replace_beginning(dir, "MACHINE_CLASSIC_PLUGINS", plugin_dir);
730 else
731 replace_beginning(dir, "MACHINE_CLASSIC_PLUGINS", "");
732 #endif // _GNUC_
733 #else
734 if (GetRegString(HKEY_CURRENT_USER, RegAvisynthKey, RegPluginDirPlus, &plugin_dir))
735 replace_beginning(dir, "USER_PLUS_PLUGINS", plugin_dir);
736 else
737 replace_beginning(dir, "USER_PLUS_PLUGINS", "");
738 if (GetRegString(HKEY_LOCAL_MACHINE, RegAvisynthKey, RegPluginDirPlus, &plugin_dir))
739 replace_beginning(dir, "MACHINE_PLUS_PLUGINS", plugin_dir);
740 else
741 replace_beginning(dir, "MACHINE_PLUS_PLUGINS", "");
742
743 #endif // AVS_WINDOWS_X86
744 #endif // AVS_WINDOWS
745
746 // replace backslashes with forward slashes
747
1/2
✓ Branch 45 → 46 taken 2568 times.
✗ Branch 45 → 153 not taken.
2568 replace(dir, '\\', '/');
748
749 // append terminating slash if needed
750
4/8
✓ Branch 47 → 48 taken 2568 times.
✗ Branch 47 → 52 not taken.
✓ Branch 49 → 50 taken 2568 times.
✗ Branch 49 → 153 not taken.
✓ Branch 50 → 51 taken 2568 times.
✗ Branch 50 → 52 not taken.
✓ Branch 53 → 54 taken 2568 times.
✗ Branch 53 → 55 not taken.
2568 if (dir.size() > 0 && dir[dir.size()-1] != '/')
751
1/2
✓ Branch 54 → 55 taken 2568 times.
✗ Branch 54 → 153 not taken.
2568 dir.append("/");
752
753 // remove double slashes
754
4/8
✓ Branch 58 → 59 taken 2568 times.
✗ Branch 58 → 141 not taken.
✓ Branch 61 → 62 taken 2568 times.
✗ Branch 61 → 135 not taken.
✓ Branch 62 → 63 taken 2568 times.
✗ Branch 62 → 133 not taken.
✗ Branch 67 → 56 not taken.
✓ Branch 67 → 68 taken 2568 times.
12840 while(replace(dir, "//", "/"));
755
756
1/2
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 71 taken 2568 times.
2568 if (dir.empty())
757 return;
758
1/2
✗ Branch 71 → 72 not taken.
✓ Branch 71 → 80 taken 2568 times.
2568 if (toFront)
759 AutoloadDirs.insert(AutoloadDirs.begin(), GetFullPathNameWrapUtf8(dir));
760 else
761
2/4
✓ Branch 80 → 81 taken 2568 times.
✗ Branch 80 → 152 not taken.
✓ Branch 81 → 82 taken 2568 times.
✗ Branch 81 → 150 not taken.
2568 AutoloadDirs.push_back(GetFullPathNameWrapUtf8(dir));
762
4/8
✓ Branch 86 → 87 taken 2568 times.
✗ Branch 86 → 88 not taken.
✓ Branch 90 → 91 taken 2568 times.
✗ Branch 90 → 92 not taken.
✓ Branch 94 → 95 taken 2568 times.
✗ Branch 94 → 96 not taken.
✓ Branch 98 → 99 taken 2568 times.
✗ Branch 98 → 101 not taken.
2568 }
763
764 void PluginManager::AutoloadPlugins()
765 {
766 if (AutoloadExecuted)
767 return;
768
769 AutoloadExecuted = true;
770 Autoloading = true;
771
772 // Load binary plugins
773 // AutoLoadDirs are utf8 on Windows as well
774 for (const std::string& dir : AutoloadDirs)
775 {
776 std::error_code ec;
777
778 #ifdef AVS_POSIX
779 #ifdef AVS_MACOS
780 const char* binaryFilter = ".dylib";
781 #else
782 const char* binaryFilter = ".so";
783 #endif
784 #else
785 const char* binaryFilter = ".dll";
786 #endif
787
788 // Build platform-native path from UTF-8 directory string
789 fs::path dir_path = PathFromUtf8(dir);
790 if (dir_path.empty())
791 continue;
792
793 for (auto& file : fs::directory_iterator(dir_path, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink, ec))
794 {
795 #ifdef AVS_POSIX
796 const bool extensionsMatch =
797 file.path().extension() == binaryFilter; // case sensitive
798 #else
799 auto ext_w = file.path().extension().wstring();
800 auto ext_utf8 = WideCharToUtf8(ext_w.c_str());
801 const bool extensionsMatch =
802 streqi(ext_utf8.get(), binaryFilter);
803 #endif
804
805 if (extensionsMatch)
806 {
807 // Convert filename back to UTF-8 for internal handling (plugin expects UTF-8 strings)
808 #ifdef AVS_POSIX
809 std::string filename_utf8 = file.path().filename().generic_string();
810 #else
811 auto fn_w = file.path().filename().wstring();
812 auto fn_utf8 = WideCharToUtf8(fn_w.c_str());
813 std::string filename_utf8 = fn_utf8.get();
814 #endif
815
816 PluginFile p(concat(dir, filename_utf8)); // utf8 handled
817
818 // Search for loaded plugins with the same base name.
819 bool same_found = false;
820 for (size_t i = 0; i < AutoLoadedPlugins.size(); ++i)
821 {
822 #ifdef AVS_POSIX
823 if (AutoLoadedPlugins[i].BaseName == p.BaseName) // case insentitive
824 #else
825 if (streqi(AutoLoadedPlugins[i].BaseName.c_str(), p.BaseName.c_str()))
826 #endif
827 {
828 // Prevent loading a plugin with a basename that is
829 // already loaded (from another autoload folder).
830 same_found = true;
831 break;
832 }
833 }
834
835 if (same_found)
836 continue;
837
838 // Try to load plugin
839 AVSValue dummy;
840 LoadPlugin(p, false, &dummy);
841 }
842 }
843
844 const char* scriptFilter = ".avsi";
845 // Build platform-native path again (already available as dir_path)
846 for (auto& file : fs::directory_iterator(dir_path, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink, ec)) // and not recursive_directory_iterator
847 {
848 const bool extensionsMatch =
849 #ifdef AVS_POSIX
850 file.path().extension() == scriptFilter; // case sensitive
851 #else
852 // Convert extension to UTF-8 for comparison
853 ([](const fs::path &p, const char *filter)->bool {
854 auto ext_w = p.extension().wstring();
855 auto ext_utf8 = WideCharToUtf8(ext_w.c_str());
856 return streqi(ext_utf8.get(), filter);
857 })(file.path(), scriptFilter);
858 #endif
859
860 if (extensionsMatch)
861 {
862 // CWDChanger expects a char*; we keep passing the UTF-8 dir here (as before).
863 CWDChanger cwdchange(dir.c_str());
864
865 #ifdef AVS_POSIX
866 std::string filename_utf8 = file.path().filename().generic_string();
867 #else
868 auto fn_w = file.path().filename().wstring();
869 auto fn_utf8 = WideCharToUtf8(fn_w.c_str());
870 std::string filename_utf8 = fn_utf8.get();
871 #endif
872
873 PluginFile p(concat(dir, filename_utf8));
874
875 // Search for loaded avsi scripts with the same base name.
876 bool same_found = false;
877 for (size_t i = 0; i < AutoLoadedImports.size(); ++i)
878 {
879 #ifdef AVS_POSIX
880 if (AutoLoadedImports[i].BaseName == p.BaseName) // case insensitive
881 #else
882 if (streqi(AutoLoadedImports[i].BaseName.c_str(), p.BaseName.c_str()))
883 #endif
884 {
885 // Prevent loading an avsi script with a basename that is
886 // already loaded (from another autoload folder).
887 same_found = true;
888 break;
889 }
890 }
891
892 if (same_found)
893 continue;
894
895 // Try to load script
896 Env->Invoke("Import", p.FilePath.c_str()); // FIXME: utf8?
897 AutoLoadedImports.push_back(p);
898 }
899 }
900 }
901
902 Autoloading = false;
903 }
904
905 642 PluginManager::~PluginManager()
906 {
907 // Delete all AVSFunction objects that we created
908 642 std::unordered_set<const AVSFunction*> function_set;
909
1/2
✗ Branch 23 → 5 not taken.
✓ Branch 23 → 24 taken 642 times.
642 for (const auto& lists : ExternalFunctions)
910 {
911 const FunctionList& funcList = lists.second;
912 for (const auto& func : funcList)
913 function_set.insert(func);
914 }
915
1/2
✗ Branch 44 → 26 not taken.
✓ Branch 44 → 45 taken 642 times.
642 for (const auto& lists : AutoloadedFunctions)
916 {
917 const FunctionList& funcList = lists.second;
918 for (const auto& func : funcList)
919 function_set.insert(func);
920 }
921
1/2
✗ Branch 53 → 47 not taken.
✓ Branch 53 → 54 taken 642 times.
642 for (const auto& func : function_set)
922 {
923 delete func;
924 }
925
926
927 // Unload plugin binaries
928
1/2
✗ Branch 63 → 55 not taken.
✓ Branch 63 → 64 taken 642 times.
642 for (size_t i = 0; i < LoadedPlugins.size(); ++i)
929 {
930 assert(LoadedPlugins[i].Library);
931 FreeLibrary(LoadedPlugins[i].Library);
932 LoadedPlugins[i].Library = NULL;
933 }
934
1/2
✗ Branch 73 → 65 not taken.
✓ Branch 73 → 74 taken 642 times.
642 for (size_t i = 0; i < AutoLoadedPlugins.size(); ++i)
935 {
936 assert(AutoLoadedPlugins[i].Library);
937 FreeLibrary(AutoLoadedPlugins[i].Library);
938 AutoLoadedPlugins[i].Library = NULL;
939 }
940
941 642 Env = NULL;
942 642 PluginInLoad = NULL;
943 642 }
944
945 void PluginManager::UpdateFunctionExports(const char* funcName, const char* funcParams, const char *exportVar)
946 {
947 if (exportVar == NULL)
948 exportVar = "$PluginFunctions$";
949
950 // Update $PluginFunctions$
951 const char *oldFnList = Env->GetVarString(exportVar, "");
952 std::string FnList(oldFnList);
953 if (FnList.size() > 0) // if the list is not empty...
954 FnList.push_back(' '); // ...add a delimiting whitespace
955 FnList.append(funcName);
956 Env->SetGlobalVar(exportVar, AVSValue( Env->SaveString(FnList.c_str(), (int)FnList.size()) ));
957
958 // Update $Plugin!...!Param$
959 std::string param_id;
960 param_id.reserve(128);
961 param_id.append("$Plugin!");
962 param_id.append(funcName);
963 param_id.append("!Param$");
964 Env->SetGlobalVar(Env->SaveString(param_id.c_str(), (int)param_id.size()), AVSValue(Env->SaveString(funcParams)));
965 }
966
967 bool PluginManager::LoadPlugin(const char* path, bool throwOnError, AVSValue *result)
968 {
969 auto pf = PluginFile { path };
970 return LoadPlugin(pf, throwOnError, result);
971 }
972 #ifdef AVS_WINDOWS
973 static bool Is64BitDLL(std::string sDLL, bool &bIs64BitDLL)
974 {
975 bIs64BitDLL = false;
976 LOADED_IMAGE li;
977
978 if (!MapAndLoad((LPSTR)sDLL.c_str(), NULL, &li, TRUE, TRUE))
979 {
980 //error handling (check GetLastError())
981 return false;
982 }
983
984 if (li.FileHeader->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) //64 bit image
985 bIs64BitDLL = true;
986
987 UnMapAndLoad(&li);
988
989 return true;
990 }
991 #endif //AVS_WINDOWS
992 bool PluginManager::LoadPlugin(PluginFile &plugin, bool throwOnError, AVSValue *result)
993 {
994 std::vector<PluginFile>& PluginList = Autoloading ? AutoLoadedPlugins : LoadedPlugins;
995
996 for (size_t i = 0; i < PluginList.size(); ++i)
997 {
998 if (streqi(PluginList[i].FilePath.c_str(), plugin.FilePath.c_str()))
999 {
1000 // Imitate successful loading if the plugin is already loaded
1001 plugin = PluginList[i];
1002 return true;
1003 }
1004 }
1005
1006 plugin.isPluginAvs25 = false;
1007 plugin.isPluginPreV11C = false;
1008 plugin.isPluginC = false;
1009
1010 #ifdef AVS_WINDOWS
1011 // Search for dependent DLLs in the plugin's directory too
1012 size_t slash_pos = plugin.FilePath.rfind('/');
1013 std::string plugin_dir = plugin.FilePath.substr(0, slash_pos);;
1014 DllDirChanger dllchange(plugin_dir.c_str());
1015
1016 // Load the dll into memory
1017 plugin.Library = LoadLibraryEx(plugin.FilePath.c_str(), 0, LOAD_WITH_ALTERED_SEARCH_PATH);
1018 if (plugin.Library == NULL)
1019 {
1020 DWORD errCode = GetLastError();
1021
1022 // Bitness mixing always throws an error, regardless of throwOnError state
1023 // By this new behaviour even plugin auto-load will fail
1024 bool bIs64BitDLL;
1025 bool succ = Is64BitDLL(plugin.FilePath, bIs64BitDLL);
1026 if (succ) {
1027 const bool selfIs32 = sizeof(void *) == 4;
1028 if (selfIs32 && bIs64BitDLL)
1029 Env->ThrowError("Cannot load a 64 bit DLL in 32 bit Avisynth: '%s'.\n", plugin.FilePath.c_str());
1030 if (!selfIs32 && !bIs64BitDLL)
1031 Env->ThrowError("Cannot load a 32 bit DLL in 64 bit Avisynth: '%s'.\n", plugin.FilePath.c_str());
1032 }
1033 if (throwOnError)
1034 {
1035 Env->ThrowError("Cannot load file '%s'. Platform returned code %d:\n%s", plugin.FilePath.c_str(), errCode, GetLastErrorText(errCode).c_str());
1036 }
1037 else
1038 return false;
1039 }
1040 #else // AVS_POSIX
1041 plugin.Library = dlopen(plugin.FilePath.c_str(), RTLD_LAZY);
1042 if (plugin.Library == NULL)
1043 Env->ThrowError("Cannot load file '%s'. Reason: %s", plugin.FilePath.c_str(), dlerror());
1044 #endif
1045
1046 // Try to load various plugin interfaces
1047 std::string avsexception26_message;
1048 const int avs26res = TryAsAvs26(plugin, result, avsexception26_message);
1049 if (avs26res != 0) // 0: OK, plugin had AvisynthPluginInit3Func
1050 {
1051 if (avs26res != 1) { // 1: AvisynthPluginInit3Func not found
1052 // plugin entry point exists but exception was thrown
1053 // Bad plugin, we must report the exception immediately regardless of throwOnError
1054 // Message could be from plugin author or, e.g., from env->AddFunction()
1055 Env->ThrowError("'%s' plugin loading error:\n%s", plugin.FilePath.c_str(), avsexception26_message.c_str());
1056 }
1057
1058 if (!TryAsAvsC(plugin, result)) // V11: try avisynth_c_plugin_init2, plugin is 64 bit capable
1059 {
1060 if (!TryAsAvsPreV11C(plugin, result)) // try avisynth_c_plugin_init, plugin is not 64 bit capable, 64 bit data will be casted down to int/float
1061 {
1062 if (!TryAsAvs25(plugin, result))
1063 {
1064 FreeLibrary(plugin.Library);
1065 plugin.Library = NULL;
1066
1067 if (throwOnError)
1068 Env->ThrowError("'%s' cannot be used as a plugin for AviSynth.", plugin.FilePath.c_str());
1069 else
1070 return false;
1071 }
1072 }
1073 }
1074 }
1075
1076 PluginList.push_back(plugin);
1077 return true;
1078 }
1079
1080 std::string PluginManager::ListAutoloadDirs()
1081 {
1082 // lf separated list, no separator after the last one
1083 std::string result;
1084 if (!AutoloadDirs.empty()) {
1085 result = AutoloadDirs[0];
1086 for (size_t i = 1; i < AutoloadDirs.size(); ++i) {
1087 result += "\n" + AutoloadDirs[i];
1088 }
1089 }
1090 return result;
1091 }
1092
1093 16 const AVSFunction* PluginManager::Lookup(const FunctionMap& map, const char* search_name, const AVSValue* args, size_t num_args,
1094 bool strict, size_t args_names_count, const char* const* arg_names) const
1095 {
1096
2/4
✓ Branch 4 → 5 taken 16 times.
✗ Branch 4 → 33 not taken.
✓ Branch 5 → 6 taken 16 times.
✗ Branch 5 → 31 not taken.
16 FunctionMap::const_iterator list_it = map.find(search_name);
1097
1/2
✓ Branch 10 → 11 taken 16 times.
✗ Branch 10 → 12 not taken.
16 if (list_it == map.end())
1098 16 return NULL;
1099
1100 for ( FunctionList::const_reverse_iterator func_it = list_it->second.rbegin();
1101 func_it != list_it->second.rend();
1102 ++func_it)
1103 {
1104 const AVSFunction *func = *func_it;
1105 if (AVSFunction::TypeMatch(func->param_types, args, num_args, strict, Env) &&
1106 AVSFunction::ArgNameMatch(func->param_types, args_names_count, arg_names)
1107 )
1108 {
1109 return func;
1110 }
1111 }
1112
1113 return NULL;
1114 }
1115
1116 8 const AVSFunction* PluginManager::Lookup(const char* search_name, const AVSValue* args, size_t num_args,
1117 bool strict, size_t args_names_count, const char* const* arg_names) const
1118 {
1119 /* Lookup in non-autoloaded functions first, so that they take priority */
1120 8 const AVSFunction* func = Lookup(ExternalFunctions, search_name, args, num_args, strict, args_names_count, arg_names);
1121
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 if (func != NULL)
1122 return func;
1123
1124 /* If not found, look amongst the autoloaded */
1125 8 return Lookup(AutoloadedFunctions, search_name, args, num_args, strict, args_names_count, arg_names);
1126 }
1127
1128 bool PluginManager::FunctionExists(const char* name) const
1129 {
1130 bool autoloaded = (AutoloadedFunctions.find(name) != AutoloadedFunctions.end());
1131 return autoloaded || (ExternalFunctions.find(name) != ExternalFunctions.end());
1132 }
1133
1134 // A minor helper function
1135 static bool FunctionListHasDll(const FunctionList &list, const char *dll_path)
1136 {
1137 for (const auto &f : list) {
1138 if ( (nullptr == f->dll_path) || (nullptr == dll_path) ) {
1139 if (f->dll_path == dll_path) {
1140 return true;
1141 }
1142 } else if (streqi(f->dll_path, dll_path)) {
1143 return true;
1144 }
1145 }
1146 return false;
1147 }
1148
1149 void PluginManager::AddFunction(const char* name, const char* params, IScriptEnvironment::ApplyFunc apply, void* user_data, const char *exportVar,
1150 bool isCalledFromAvs25Interface,
1151 bool isCalledFromPreV11CInterface)
1152 {
1153 if (!IsValidParameterString(params))
1154 Env->ThrowError("%s has an invalid parameter string (bug in filter)", name);
1155
1156 FunctionMap& functions = Autoloading ? AutoloadedFunctions : ExternalFunctions;
1157
1158 AVSFunction *newFunc = NULL;
1159 if (PluginInLoad != NULL)
1160 {
1161 // either called using IScriptEnvironment_Avs25 or we are inside of a CPPv2.5 plugin load
1162 const bool isAvs25like = isCalledFromAvs25Interface || PluginInLoad->isPluginAvs25;
1163
1164 // During function instantiation the new V11 64 bit 'l'ong/'d'ouble
1165 // parameters must be converted to int/float instead.
1166 // If 64->32-bit conversion is not done, the pre-V11 C plugin does not detect
1167 // AVS_Value type properly, since the type check is not performed through interface calls.
1168 // The 'baked code' in avisynth_c.h does not know about 'l'ong or 'd'ouble type:
1169 // IsInt() / IsFloat() or avs_is_int() / avs_is_float() would return false on the new 64 bit types.
1170
1171 // How Avisynth detects that a C plugin 'knows' about 64 bit types?
1172 // - the plugin is 64 bit aware plugin, works with regular IScriptEnvironment
1173 // - When avisynth_c_plugin_init2 is available (PluginInLoad->isPluginC is set)
1174 // - When C client called avs_create_script_environment(ver) with ver>=11.
1175 // - the plugin is pre-V11 C plugin and we pass IScriptEnvironment_AvsPreV11C
1176 // - when the plugin responded only to avisynth_c_plugin_init;
1177 // (PluginInLoad->isPluginPerV11C is true)
1178 // - C client called avs_create_script_environment(ver) with ver<11
1179 // (isCalledFromPreV11CInterface is true)
1180
1181 const bool isPrev11Clike = isCalledFromPreV11CInterface || PluginInLoad->isPluginPreV11C;
1182 newFunc = new AVSFunction(name, PluginInLoad->BaseName.c_str(), params, apply, user_data, PluginInLoad->FilePath.c_str(),
1183 isAvs25like, isPrev11Clike);
1184 }
1185 else
1186 {
1187 // Not plugin load case.
1188 // AddFunction or avs_add_function was called by a client
1189 // (a C client which directly loads avisynth)
1190 // or when called from a cpp v2.5 level script environtment.
1191 // isCalledFromAvs25Interface: IScriptEnvironment_Avs25->AddFunction
1192 newFunc = new AVSFunction(name, NULL, params, apply, user_data, NULL,
1193 isCalledFromAvs25Interface,
1194 isCalledFromPreV11CInterface
1195 );
1196 }
1197
1198 // Warn user if a function with the same name is already registered by another plugin
1199 {
1200 const auto &it = functions.find(newFunc->name);
1201 if ( (functions.end() != it) && !FunctionListHasDll(it->second, newFunc->dll_path) )
1202 {
1203 OneTimeLogTicket ticket(LOGTICKET_W1008, newFunc->name);
1204 Env->LogMsgOnce(ticket, LOGLEVEL_WARNING, "%s() is defined by multiple plugins. Calls to this filter might be ambiguous and could result in the wrong function being called.", newFunc->name);
1205 }
1206 }
1207
1208 functions[newFunc->name].push_back(newFunc);
1209 UpdateFunctionExports(newFunc->name, newFunc->param_types, exportVar);
1210
1211 if (NULL != newFunc->canon_name)
1212 {
1213 // Warn user if a function with the same name is already registered by another plugin
1214 {
1215 const auto &it = functions.find(newFunc->canon_name);
1216 if ((functions.end() != it) && !FunctionListHasDll(it->second, newFunc->dll_path))
1217 {
1218 OneTimeLogTicket ticket(LOGTICKET_W1008, newFunc->canon_name);
1219 Env->LogMsgOnce(ticket, LOGLEVEL_WARNING, "%s() is defined by multiple plugins. Calls to this filter might be ambiguous and could result in the wrong function being called.", newFunc->name);
1220 }
1221 }
1222
1223 functions[newFunc->canon_name].push_back(newFunc);
1224 UpdateFunctionExports(newFunc->canon_name, newFunc->param_types, exportVar);
1225 }
1226 }
1227
1228 std::string PluginManager::PluginLoading() const
1229 {
1230 if (NULL == PluginInLoad)
1231 return std::string();
1232 else
1233 return PluginInLoad->BaseName;
1234 }
1235
1236 // 0: success
1237 // 1: no AvisynthPluginInit3Func
1238 // 2: Avisynth exception
1239 // 3: other exception
1240 int PluginManager::TryAsAvs26(PluginFile &plugin, AVSValue *result, std::string &avsexception_message)
1241 {
1242 extern const AVS_Linkage* const AVS_linkage; // In interface.cpp
1243 #ifdef AVS_POSIX
1244 AvisynthPluginInit3Func AvisynthPluginInit3 = (AvisynthPluginInit3Func)dlsym(plugin.Library, "AvisynthPluginInit3");
1245 #elif defined(GCC_WIN32)
1246 AvisynthPluginInit3Func AvisynthPluginInit3 = (AvisynthPluginInit3Func)GetProcAddress(plugin.Library, "_AvisynthPluginInit3");
1247 if (!AvisynthPluginInit3)
1248 AvisynthPluginInit3 = (AvisynthPluginInit3Func)GetProcAddress(plugin.Library, "AvisynthPluginInit3@8");
1249 #else
1250 AvisynthPluginInit3Func AvisynthPluginInit3 = (AvisynthPluginInit3Func)GetProcAddress(plugin.Library, "AvisynthPluginInit3");
1251 if (!AvisynthPluginInit3)
1252 AvisynthPluginInit3 = (AvisynthPluginInit3Func)GetProcAddress(plugin.Library, "_AvisynthPluginInit3@8");
1253 #endif
1254
1255 int success = 0; // O.K.
1256 avsexception_message = "";
1257 if (AvisynthPluginInit3 == NULL)
1258 return 1; // not found
1259 else
1260 {
1261 PluginInLoad = &plugin;
1262 // a bad plugin can kill everything if it uses e.g. an old IScriptEnvironment2
1263 try {
1264 *result = AvisynthPluginInit3(Env, AVS_linkage);
1265 }
1266 catch (const AvisynthError& error) {
1267 avsexception_message = error.msg;
1268 success = 2;
1269 }
1270 catch (const std::exception& ex) {
1271 avsexception_message = ex.what();
1272 success = 3;
1273 }
1274 catch (...) {
1275 avsexception_message = "Unknown exception";
1276 success = 3;
1277 }
1278 PluginInLoad = NULL;
1279 }
1280
1281 return success;
1282 }
1283
1284 bool PluginManager::TryAsAvs25(PluginFile &plugin, AVSValue *result)
1285 {
1286 #ifdef AVS_POSIX
1287 AvisynthPluginInit2Func AvisynthPluginInit2 = (AvisynthPluginInit2Func)dlsym(plugin.Library, "AvisynthPluginInit2");
1288 #elif defined(GCC_WIN32)
1289 AvisynthPluginInit2Func AvisynthPluginInit2 = (AvisynthPluginInit2Func)GetProcAddress(plugin.Library, "_AvisynthPluginInit2");
1290 if (!AvisynthPluginInit2)
1291 AvisynthPluginInit2 = (AvisynthPluginInit2Func)GetProcAddress(plugin.Library, "AvisynthPluginInit2@4");
1292 #else
1293 AvisynthPluginInit2Func AvisynthPluginInit2 = (AvisynthPluginInit2Func)GetProcAddress(plugin.Library, "AvisynthPluginInit2");
1294 if (!AvisynthPluginInit2)
1295 AvisynthPluginInit2 = (AvisynthPluginInit2Func)GetProcAddress(plugin.Library, "_AvisynthPluginInit2@4");
1296 #endif
1297
1298 bool success = true;
1299 if (AvisynthPluginInit2 == NULL)
1300 return false;
1301 else
1302 {
1303 PluginInLoad = &plugin;
1304 // in case of a crash in init2
1305 try {
1306 // Pass the 2.5 variant IScriptEnvironment, which has different Invoke
1307 // and AddFunction method to avoid array copy/free problems.
1308 // (NEW_AVSVALUE compatibility: "baked code" strikes back)
1309
1310 // set before AddFunction callbacks happen from the AvisynthPluginInit2 called below
1311 plugin.isPluginAvs25 = true;
1312 *result = AvisynthPluginInit2(Env->GetEnv25());
1313 }
1314 catch (...)
1315 {
1316 success = false;
1317 }
1318 PluginInLoad = NULL;
1319 }
1320
1321 return success;
1322 }
1323
1324 bool PluginManager::TryAsAvsPreV11C(PluginFile& plugin, AVSValue* result)
1325 {
1326 #ifdef AVS_POSIX
1327 AvisynthCPluginInitFunc AvisynthCPluginInit = (AvisynthCPluginInitFunc)dlsym(plugin.Library, "avisynth_c_plugin_init");
1328 #else
1329 #ifdef _WIN64
1330 AvisynthCPluginInitFunc AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "avisynth_c_plugin_init");
1331 if (!AvisynthCPluginInit)
1332 AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "_avisynth_c_plugin_init@4");
1333 #else // _WIN32
1334 AvisynthCPluginInitFunc AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "_avisynth_c_plugin_init@4");
1335 if (!AvisynthCPluginInit)
1336 AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "avisynth_c_plugin_init@4");
1337 if (!AvisynthCPluginInit)
1338 AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "avisynth_c_plugin_init");
1339 #endif
1340 #endif // AVS_POSIX
1341
1342 if (AvisynthCPluginInit == NULL)
1343 return false;
1344 else
1345 {
1346 PluginInLoad = &plugin;
1347 // set before AddFunction callbacks happen from the AvisynthCPluginInit called below
1348 plugin.isPluginPreV11C = true; // no array deep copy/free when NEW_AVSVALUE
1349 {
1350 AVS_ScriptEnvironment e;
1351 e.env = Env;
1352 AVS_ScriptEnvironment* pe;
1353 pe = &e;
1354 const char* s = NULL;
1355 #if defined(X86_32) && defined(MSVC)
1356 int callok = 1; // (stdcall)
1357 __asm // Tritical - Jan 2006
1358 {
1359 push eax
1360 push edx
1361
1362 push 0x12345678 // Stash a known value
1363
1364 mov eax, pe // Env pointer
1365 push eax // Arg1
1366 call AvisynthCPluginInit // avisynth_c_plugin_init
1367
1368 lea edx, s // return value is in eax
1369 mov DWORD PTR[edx], eax
1370
1371 pop eax // Get top of stack
1372 cmp eax, 0x12345678 // Was it our known value?
1373 je end // Yes! Stack was cleaned up, was a stdcall
1374
1375 lea edx, callok
1376 mov BYTE PTR[edx], 0 // Set callok to 0 (_cdecl)
1377
1378 pop eax // Get 2nd top of stack
1379 cmp eax, 0x12345678 // Was this our known value?
1380 je end // Yes! Stack is now correctly cleaned up, was a _cdecl
1381
1382 mov BYTE PTR[edx], 2 // Set callok to 2 (bad stack)
1383 end:
1384 pop edx
1385 pop eax
1386 }
1387 switch (callok)
1388 {
1389 case 0: // cdecl
1390 #ifdef AVSC_USE_STDCALL
1391 Env->ThrowError("Avisynth 2 C Plugin '%s' has wrong calling convention! Must be _stdcall.", plugin.BaseName.c_str());
1392 #endif
1393 break;
1394 case 1: // stdcall
1395 #ifndef AVSC_USE_STDCALL
1396 Env->ThrowError("Avisynth 2 C Plugin '%s' has wrong calling convention! Must be _cdecl.", plugin.BaseName.c_str());
1397 #endif
1398 break;
1399 case 2:
1400 Env->ThrowError("Avisynth 2 C Plugin '%s' has corrupted the stack.", plugin.BaseName.c_str());
1401 }
1402 #else
1403 s = AvisynthCPluginInit(pe);
1404 #endif
1405 // if (s == 0)
1406 // Env->ThrowError("Avisynth 2 C Plugin '%s' returned a NULL pointer.", plugin.BaseName.c_str());
1407
1408 *result = AVSValue(s);
1409 }
1410 PluginInLoad = NULL;
1411 }
1412
1413 return true;
1414 }
1415
1416
1417 // v11 capable: C plugin implements avisynth_c_plugin_init2!
1418 bool PluginManager::TryAsAvsC(PluginFile& plugin, AVSValue* result)
1419 {
1420 #ifdef AVS_POSIX
1421 AvisynthCPluginInitFunc AvisynthCPluginInit = (AvisynthCPluginInitFunc)dlsym(plugin.Library, "avisynth_c_plugin_init2");
1422 #else
1423 #ifdef _WIN64
1424 AvisynthCPluginInitFunc AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "avisynth_c_plugin_init2");
1425 if (!AvisynthCPluginInit)
1426 AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "_avisynth_c_plugin_init2@4");
1427 #else // _WIN32
1428 AvisynthCPluginInitFunc AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "_avisynth_c_plugin_init2@4");
1429 if (!AvisynthCPluginInit)
1430 AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "avisynth_c_plugin_init2@4");
1431 if (!AvisynthCPluginInit)
1432 AvisynthCPluginInit = (AvisynthCPluginInitFunc)GetProcAddress(plugin.Library, "avisynth_c_plugin_init2");
1433 #endif
1434 #endif // AVS_POSIX
1435
1436 if (AvisynthCPluginInit == NULL)
1437 return false;
1438 else
1439 {
1440 PluginInLoad = &plugin;
1441 // set before AddFunction callbacks happen from the AvisynthCPluginInit called below
1442 plugin.isPluginC = true; // no array deep copy/free when NEW_AVSVALUE, but 64 bit data capable
1443 {
1444 AVS_ScriptEnvironment e;
1445 e.env = Env;
1446 AVS_ScriptEnvironment* pe;
1447 pe = &e;
1448 const char* s = NULL;
1449 #if defined(X86_32) && defined(MSVC)
1450 int callok = 1; // (stdcall)
1451 __asm // Tritical - Jan 2006
1452 {
1453 push eax
1454 push edx
1455
1456 push 0x12345678 // Stash a known value
1457
1458 mov eax, pe // Env pointer
1459 push eax // Arg1
1460 call AvisynthCPluginInit // avisynth_c_plugin_init
1461
1462 lea edx, s // return value is in eax
1463 mov DWORD PTR[edx], eax
1464
1465 pop eax // Get top of stack
1466 cmp eax, 0x12345678 // Was it our known value?
1467 je end // Yes! Stack was cleaned up, was a stdcall
1468
1469 lea edx, callok
1470 mov BYTE PTR[edx], 0 // Set callok to 0 (_cdecl)
1471
1472 pop eax // Get 2nd top of stack
1473 cmp eax, 0x12345678 // Was this our known value?
1474 je end // Yes! Stack is now correctly cleaned up, was a _cdecl
1475
1476 mov BYTE PTR[edx], 2 // Set callok to 2 (bad stack)
1477 end:
1478 pop edx
1479 pop eax
1480 }
1481 switch (callok)
1482 {
1483 case 0: // cdecl
1484 #ifdef AVSC_USE_STDCALL
1485 Env->ThrowError("Avisynth C Plugin '%s' has wrong calling convention! Must be _stdcall.", plugin.BaseName.c_str());
1486 #endif
1487 break;
1488 case 1: // stdcall
1489 #ifndef AVSC_USE_STDCALL
1490 Env->ThrowError("Avisynth C Plugin '%s' has wrong calling convention! Must be _cdecl.", plugin.BaseName.c_str());
1491 #endif
1492 break;
1493 case 2:
1494 Env->ThrowError("Avisynth C Plugin '%s' has corrupted the stack.", plugin.BaseName.c_str());
1495 }
1496 #else
1497 s = AvisynthCPluginInit(pe);
1498 #endif
1499
1500 * result = AVSValue(s);
1501 }
1502 PluginInLoad = NULL;
1503 }
1504
1505 return true;
1506 }
1507
1508 /*
1509 ---------------------------------------------------------------------------------
1510 ---------------------------------------------------------------------------------
1511 LoadPlugin
1512 ---------------------------------------------------------------------------------
1513 ---------------------------------------------------------------------------------
1514 */
1515
1516 AVSValue LoadPlugin(AVSValue args, void*, IScriptEnvironment* env)
1517 {
1518 IScriptEnvironment2 *env2 = static_cast<IScriptEnvironment2*>(env);
1519
1520 bool success = true;
1521 const bool utf8 = args[1].AsBool(false); // default: false (ANSI on Windows), n/a on other OS
1522 for (int i = 0; i < args[0].ArraySize(); ++i)
1523 {
1524 AVSValue dummy;
1525 auto path_utf8 = charToUtf8(args[0][i].AsString(), utf8);
1526 success &= env2->LoadPlugin(path_utf8.c_str(), true, &dummy); // accepts only utf8 paths on all OS
1527 }
1528
1529 return AVSValue(success);
1530 }
1531
1532 extern const AVSFunction Plugin_functions[] = {
1533 {"LoadPlugin", BUILTIN_FUNC_PREFIX, "s+[utf8]b", LoadPlugin},
1534 {"LoadCPlugin", BUILTIN_FUNC_PREFIX, "s+[utf8]b", LoadPlugin }, // for compatibility with older scripts
1535 {"Load_Stdcall_Plugin", BUILTIN_FUNC_PREFIX, "s+[utf8]b", LoadPlugin }, // for compatibility with older scripts
1536 { 0 }
1537 };
1538