GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 26.0% 131 / 0 / 503
Functions: 39.5% 15 / 0 / 38
Branches: 15.5% 102 / 0 / 657

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