1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // executes script functions
17 
18 #include "C4Include.h"
19 #include "script/C4AulExec.h"
20 
21 #include "control/C4Record.h"
22 #include "object/C4Def.h"
23 #include "object/C4Object.h"
24 #include "script/C4Aul.h"
25 #include "script/C4AulScriptFunc.h"
26 #include "script/C4AulDebug.h"
27 #include "script/C4ScriptHost.h"
28 
29 C4AulExec AulExec;
30 
C4AulExecError(const char * szError)31 C4AulExecError::C4AulExecError(const char *szError)
32 {
33 	assert(szError);
34 	// direct error message string
35 	sMessage.Copy(szError ? szError : "(no error message)");
36 }
37 
ReturnDump(StdStrBuf Dump)38 StdStrBuf C4AulScriptContext::ReturnDump(StdStrBuf Dump)
39 {
40 	if (!Func)
41 		return StdStrBuf("");
42 	// Context
43 	if (Obj && Obj->Status)
44 	{
45 		C4Value ObjVal(Obj);
46 		Dump.Append(ObjVal.GetDataString(0));
47 		Dump.Append("->");
48 	}
49 	bool fDirectExec = !Func->GetName();
50 	if (!fDirectExec)
51 	{
52 		// Function name
53 		Dump.Append(Func->GetName());
54 		// Parameters
55 		Dump.AppendChar('(');
56 		int iNullPars = 0;
57 		for (int i = 0; i < Func->GetParCount(); i++)
58 		{
59 			if (!Pars[i])
60 				iNullPars++;
61 			else
62 			{
63 				if (i > iNullPars)
64 					Dump.AppendChar(',');
65 				// Insert missing null parameters
66 				while (iNullPars > 0)
67 				{
68 					Dump.Append("0,");
69 					iNullPars--;
70 				}
71 				// Insert parameter
72 				Dump.Append(Pars[i].GetDataString());
73 			}
74 		}
75 		Dump.AppendChar(')');
76 	}
77 	else
78 		Dump.Append(Func->Parent->GetDataString());
79 	// Script
80 	if (!fDirectExec && Func->pOrgScript)
81 		Dump.AppendFormat(" (%s:%d)",
82 		                  Func->pOrgScript->ScriptName.getData(),
83 		                  CPos ? Func->GetLineOfCode(CPos) : SGetLine(Func->pOrgScript->GetScript(), Func->Script));
84 	// Return it
85 	return Dump;
86 }
87 
dump(StdStrBuf Dump)88 void C4AulScriptContext::dump(StdStrBuf Dump)
89 {
90 	// Log it
91 	DebugLog(ReturnDump(Dump).getData());
92 }
93 
LogCallStack()94 void C4AulExec::LogCallStack()
95 {
96 	for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
97 		pCtx->dump(StdStrBuf(" by: "));
98 }
99 
FnTranslate(C4PropList * _this,C4String * text)100 C4String *C4AulExec::FnTranslate(C4PropList * _this, C4String *text)
101 {
102 #define ReturnIfTranslationAvailable(script, key) do \
103 { \
104 	const auto &s = script; \
105 	const auto &k = key; \
106 	if (s) \
107 	{ \
108 		try \
109 		{ \
110 			return ::Strings.RegString(s->Translate(k).c_str()); \
111 		} \
112 		catch (C4LangStringTable::NoSuchTranslation &) {} \
113 	} \
114 } while(0)
115 
116 	if (!text || text->GetData().isNull()) return nullptr;
117 	// Find correct script: translations of the context if possible, containing script as fallback
118 	if (_this && _this->GetDef())
119 		ReturnIfTranslationAvailable(&(_this->GetDef()->Script), text->GetCStr());
120 	ReturnIfTranslationAvailable(AulExec.pCurCtx[0].Func->pOrgScript, text->GetCStr());
121 
122 	// No translation available, log
123 	DebugLogF(R"(WARNING: Translate: no translation for string "%s")", text->GetCStr());
124 	// Trace
125 	AulExec.LogCallStack();
126 	return text;
127 #undef ReturnIfTranslationAvailable
128 }
129 
FnLogCallStack(C4PropList * _this)130 bool C4AulExec::FnLogCallStack(C4PropList * _this)
131 {
132 	AulExec.LogCallStack();
133 	return true;
134 }
135 
ClearPointers(C4Object * obj)136 void C4AulExec::ClearPointers(C4Object * obj)
137 {
138 	for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
139 	{
140 		if (pCtx->Obj == obj)
141 			pCtx->Obj = nullptr;
142 	}
143 }
144 
Exec(C4AulScriptFunc * pSFunc,C4PropList * p,C4Value * pnPars,bool fPassErrors)145 C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4PropList * p, C4Value *pnPars, bool fPassErrors)
146 {
147 	// Save start context
148 	C4AulScriptContext *pOldCtx = pCurCtx;
149 	C4Value *pPars = pCurVal + 1;
150 	try
151 	{
152 		// Push parameters
153 		assert(pnPars);
154 		for (int i = 0; i < pSFunc->GetParCount(); i++)
155 			PushValue(pnPars[i]);
156 
157 		// Push a new context
158 		C4AulScriptContext ctx;
159 		ctx.tTime = 0;
160 		ctx.Obj = p;
161 		ctx.Return = nullptr;
162 		ctx.Pars = pPars;
163 		ctx.Func = pSFunc;
164 		ctx.CPos = nullptr;
165 		PushContext(ctx);
166 
167 		// Execute
168 		return Exec(pSFunc->GetCode());
169 	}
170 	catch (C4AulError &e)
171 	{
172 		if (!fPassErrors)
173 			::ScriptEngine.GetErrorHandler()->OnError(e.what());
174 		// Unwind stack
175 		// TODO: The stack dump should be passed to the error handler somehow
176 		while (pCurCtx > pOldCtx)
177 		{
178 			pCurCtx->dump(StdStrBuf(" by: "));
179 			PopContext();
180 		}
181 		PopValuesUntil(pPars - 1);
182 		// Pass?
183 		if (fPassErrors)
184 			throw;
185 		// Trace
186 		LogCallStack();
187 	}
188 
189 	// Return nothing
190 	return C4VNull;
191 }
192 
Exec(C4AulBCC * pCPos)193 C4Value C4AulExec::Exec(C4AulBCC *pCPos)
194 {
195 	try
196 	{
197 
198 		for (;;)
199 		{
200 
201 			bool fJump = false;
202 			switch (pCPos->bccType)
203 			{
204 			case AB_INT:
205 				PushInt(pCPos->Par.i);
206 				break;
207 
208 			case AB_BOOL:
209 				PushBool(!!pCPos->Par.i);
210 				break;
211 
212 			case AB_STRING:
213 				PushString(pCPos->Par.s);
214 				break;
215 
216 			case AB_CPROPLIST:
217 				PushPropList(pCPos->Par.p);
218 				break;
219 
220 			case AB_CARRAY:
221 				PushArray(pCPos->Par.a);
222 				break;
223 
224 			case AB_CFUNCTION:
225 				PushFunction(pCPos->Par.f);
226 				break;
227 
228 			case AB_NIL:
229 				PushValue(C4VNull);
230 				break;
231 
232 			case AB_DUP:
233 				PushValue(pCurVal[pCPos->Par.i]);
234 				break;
235 			case AB_STACK_SET:
236 				pCurVal[pCPos->Par.i] = pCurVal[0];
237 				break;
238 			case AB_POP_TO:
239 				pCurVal[pCPos->Par.i] = pCurVal[0];
240 				PopValue();
241 				break;
242 
243 			case AB_EOFN:
244 				throw C4AulExecError("internal error: function didn't return");
245 
246 			case AB_ERR:
247 				if (pCPos->Par.s)
248 					throw C4AulExecError((std::string("syntax error: ") + pCPos->Par.s->GetCStr()).c_str());
249 				else
250 					throw C4AulExecError("syntax error: see above for details");
251 
252 			case AB_DUP_CONTEXT:
253 				PushValue(AulExec.GetContext(AulExec.GetContextDepth()-2)->Pars[pCPos->Par.i]);
254 				break;
255 
256 			case AB_LOCALN:
257 				if (!pCurCtx->Obj)
258 					throw C4AulExecError("can't access local variables without this");
259 				PushNullVals(1);
260 				pCurCtx->Obj->GetPropertyByS(pCPos->Par.s, pCurVal);
261 				break;
262 			case AB_LOCALN_SET:
263 				if (!pCurCtx->Obj)
264 					throw C4AulExecError("can't access local variables without this");
265 				if (pCurCtx->Obj->IsFrozen())
266 					throw C4AulExecError("local variable: this is readonly");
267 				pCurCtx->Obj->SetPropertyByS(pCPos->Par.s, pCurVal[0]);
268 				break;
269 
270 			case AB_PROP:
271 				if (!pCurVal->CheckConversion(C4V_PropList))
272 					throw C4AulExecError(FormatString("proplist access: proplist expected, got %s", pCurVal->GetTypeName()).getData());
273 				if (!pCurVal->_getPropList()->GetPropertyByS(pCPos->Par.s, pCurVal))
274 					pCurVal->Set0();
275 				break;
276 			case AB_PROP_SET:
277 			{
278 				C4Value *pPropList = pCurVal - 1;
279 				if (!pPropList->CheckConversion(C4V_PropList))
280 					throw C4AulExecError(FormatString("proplist write: proplist expected, got %s", pPropList->GetTypeName()).getData());
281 				if (pPropList->_getPropList()->IsFrozen())
282 					throw C4AulExecError("proplist write: proplist is readonly");
283 				pPropList->_getPropList()->SetPropertyByS(pCPos->Par.s, pCurVal[0]);
284 				pPropList->Set(pCurVal[0]);
285 				PopValue();
286 				break;
287 			}
288 
289 			case AB_GLOBALN:
290 				PushValue(*::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i));
291 				break;
292 			case AB_GLOBALN_SET:
293 				::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i)->Set(pCurVal[0]);
294 				break;
295 
296 			// prefix
297 			case AB_BitNot: // ~
298 				CheckOpPar(C4V_Int, "~");
299 				pCurVal->SetInt(~pCurVal->_getInt());
300 				break;
301 			case AB_Not:  // !
302 				pCurVal->SetBool(!pCurVal->getBool());
303 				break;
304 			case AB_Neg:  // -
305 				CheckOpPar(C4V_Int, "-");
306 				pCurVal->SetInt(-pCurVal->_getInt());
307 				break;
308 			case AB_Inc: // ++
309 				CheckOpPar(C4V_Int, "++");
310 				pCurVal->SetInt(pCurVal->_getInt() + 1);
311 				break;
312 			case AB_Dec: // --
313 				CheckOpPar(C4V_Int, "--");
314 				pCurVal->SetInt(pCurVal->_getInt() - 1);
315 				break;
316 			// postfix
317 			case AB_Pow:  // **
318 			{
319 				CheckOpPars(C4V_Int, C4V_Int, "**");
320 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
321 				pPar1->SetInt(Pow(pPar1->_getInt(), pPar2->_getInt()));
322 				PopValue();
323 				break;
324 			}
325 			case AB_Div:  // /
326 			{
327 				CheckOpPars(C4V_Int, C4V_Int, "/");
328 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
329 				if (!pPar2->_getInt())
330 					throw C4AulExecError("division by zero");
331 				// INT_MIN/-1 cannot be represented in an int and would cause an uncaught exception
332 				if (pPar1->_getInt()==INT32_MIN && pPar2->_getInt()==-1)
333 					throw C4AulExecError("division overflow");
334 				pPar1->SetInt(pPar1->_getInt() / pPar2->_getInt());
335 				PopValue();
336 				break;
337 			}
338 			case AB_Mul:  // *
339 			{
340 				CheckOpPars(C4V_Int, C4V_Int, "*");
341 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
342 				pPar1->SetInt(pPar1->_getInt() * pPar2->_getInt());
343 				PopValue();
344 				break;
345 			}
346 			case AB_Mod:  // %
347 			{
348 				CheckOpPars(C4V_Int, C4V_Int, "%");
349 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
350 				// INT_MIN%-1 cannot be represented in an int and would cause an uncaught exception
351 				if (pPar1->_getInt()==INT32_MIN && pPar2->_getInt()==-1)
352 					throw C4AulExecError("modulo division overflow");
353 				if (pPar2->_getInt())
354 					pPar1->SetInt(pPar1->_getInt() % pPar2->_getInt());
355 				else
356 					pPar1->Set0();
357 				PopValue();
358 				break;
359 			}
360 			case AB_Sub:  // -
361 			{
362 				CheckOpPars(C4V_Int, C4V_Int, "-");
363 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
364 				pPar1->SetInt(pPar1->_getInt() - pPar2->_getInt());
365 				PopValue();
366 				break;
367 			}
368 			case AB_Sum:  // +
369 			{
370 				CheckOpPars(C4V_Int, C4V_Int, "+");
371 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
372 				pPar1->SetInt(pPar1->_getInt() + pPar2->_getInt());
373 				PopValue();
374 				break;
375 			}
376 			case AB_LeftShift:  // <<
377 			{
378 				CheckOpPars(C4V_Int, C4V_Int, "<<");
379 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
380 				pPar1->SetInt(pPar1->_getInt() << pPar2->_getInt());
381 				PopValue();
382 				break;
383 			}
384 			case AB_RightShift: // >>
385 			{
386 				CheckOpPars(C4V_Int, C4V_Int, ">>");
387 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
388 				pPar1->SetInt(pPar1->_getInt() >> pPar2->_getInt());
389 				PopValue();
390 				break;
391 			}
392 			case AB_LessThan: // <
393 			{
394 				CheckOpPars(C4V_Int, C4V_Int, "<");
395 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
396 				pPar1->SetBool(pPar1->_getInt() < pPar2->_getInt());
397 				PopValue();
398 				break;
399 			}
400 			case AB_LessThanEqual:  // <=
401 			{
402 				CheckOpPars(C4V_Int, C4V_Int, "<=");
403 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
404 				pPar1->SetBool(pPar1->_getInt() <= pPar2->_getInt());
405 				PopValue();
406 				break;
407 			}
408 			case AB_GreaterThan:  // >
409 			{
410 				CheckOpPars(C4V_Int, C4V_Int, ">");
411 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
412 				pPar1->SetBool(pPar1->_getInt() > pPar2->_getInt());
413 				PopValue();
414 				break;
415 			}
416 			case AB_GreaterThanEqual: // >=
417 			{
418 				CheckOpPars(C4V_Int, C4V_Int, ">=");
419 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
420 				pPar1->SetBool(pPar1->_getInt() >= pPar2->_getInt());
421 				PopValue();
422 				break;
423 			}
424 			case AB_Equal:  // ==
425 			{
426 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
427 				pPar1->SetBool(pPar1->IsIdenticalTo(*pPar2));
428 				PopValue();
429 				break;
430 			}
431 			case AB_NotEqual: // !=
432 			{
433 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
434 				pPar1->SetBool(!pPar1->IsIdenticalTo(*pPar2));
435 				PopValue();
436 				break;
437 			}
438 			case AB_BitAnd: // &
439 			{
440 				CheckOpPars(C4V_Int, C4V_Int, "&");
441 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
442 				pPar1->SetInt(pPar1->_getInt() & pPar2->_getInt());
443 				PopValue();
444 				break;
445 			}
446 			case AB_BitXOr: // ^
447 			{
448 				CheckOpPars(C4V_Int, C4V_Int, "^");
449 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
450 				pPar1->SetInt(pPar1->_getInt() ^ pPar2->_getInt());
451 				PopValue();
452 				break;
453 			}
454 			case AB_BitOr:  // |
455 			{
456 				CheckOpPars(C4V_Int, C4V_Int, "|");
457 				C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
458 				pPar1->SetInt(pPar1->_getInt() | pPar2->_getInt());
459 				PopValue();
460 				break;
461 			}
462 
463 			case AB_NEW_ARRAY:
464 			{
465 				// Create array
466 				C4ValueArray *pArray = new C4ValueArray(pCPos->Par.i);
467 
468 				// Pop values from stack
469 				for (int i = 0; i < pCPos->Par.i; i++)
470 					(*pArray)[i] = pCurVal[i - pCPos->Par.i + 1];
471 
472 				// Push array
473 				PopValues(pCPos->Par.i);
474 				PushArray(pArray);
475 
476 				break;
477 			}
478 
479 			case AB_NEW_PROPLIST:
480 			{
481 				C4PropList * pPropList = C4PropList::New();
482 
483 				for (int i = 0; i < pCPos->Par.i; i++)
484 					pPropList->SetPropertyByS(pCurVal[-2 * i - 1]._getStr(), pCurVal[-2 * i]);
485 
486 				PopValues(pCPos->Par.i * 2);
487 				PushPropList(pPropList);
488 				break;
489 			}
490 
491 			case AB_ARRAYA:
492 			{
493 				C4Value *pIndex = pCurVal, *pStruct = pCurVal - 1, *pResult = pCurVal - 1;
494 				// Typcheck to determine whether it's an array or a proplist
495 				if(CheckArrayAccess(pStruct, pIndex) == C4V_Array)
496 				{
497 					*pResult = pStruct->_getArray()->GetItem(pIndex->_getInt());
498 				}
499 				else
500 				{
501 					assert(pStruct->GetType() == C4V_PropList);
502 					C4PropList *pPropList = pStruct->_getPropList();
503 					if (!pPropList->GetPropertyByS(pIndex->_getStr(), pResult))
504 						pResult->Set0();
505 				}
506 				// Remove index
507 				PopValue();
508 				break;
509 			}
510 			case AB_ARRAYA_SET:
511 			{
512 				C4Value *pValue = pCurVal, *pIndex = pCurVal - 1, *pStruct = pCurVal - 2, *pResult = pCurVal - 2;
513 				// Typcheck to determine whether it's an array or a proplist
514 				if(CheckArrayAccess(pStruct, pIndex) == C4V_Array)
515 				{
516 					if (pStruct->_getArray()->IsFrozen())
517 						throw C4AulExecError("array write: array is readonly");
518 					pStruct->_getArray()->SetItem(pIndex->_getInt(), *pValue);
519 				}
520 				else
521 				{
522 					assert(pStruct->GetType() == C4V_PropList);
523 					C4PropList *pPropList = pStruct->_getPropList();
524 					if (pPropList->IsFrozen())
525 						throw C4AulExecError("proplist write: proplist is readonly");
526 					pPropList->SetPropertyByS(pIndex->_getStr(), *pValue);
527 				}
528 				// Set result, remove array and index from stack
529 				*pResult = *pValue;
530 				PopValues(2);
531 				break;
532 			}
533 			case AB_ARRAY_SLICE:
534 			{
535 				C4Value &Array = pCurVal[-2];
536 				C4Value &StartIndex = pCurVal[-1];
537 				C4Value &EndIndex = pCurVal[0];
538 
539 				// Typcheck
540 				if (!Array.CheckConversion(C4V_Array))
541 					throw C4AulExecError(FormatString("array slice: can't access %s as an array", Array.GetTypeName()).getData());
542 				if (!StartIndex.CheckConversion(C4V_Int))
543 					throw C4AulExecError(FormatString("array slice: start index of type %s, int expected", StartIndex.GetTypeName()).getData());
544 				if (!EndIndex.CheckConversion(C4V_Int))
545 					throw C4AulExecError(FormatString("array slice: end index of type %s, int expected", EndIndex.GetTypeName()).getData());
546 
547 				Array.SetArray(Array.GetData().Array->GetSlice(StartIndex._getInt(), EndIndex._getInt()));
548 
549 				// Remove both indices
550 				PopValues(2);
551 				break;
552 			}
553 
554 			case AB_ARRAY_SLICE_SET:
555 			{
556 				C4Value &Array = pCurVal[-3];
557 				C4Value &StartIndex = pCurVal[-2];
558 				C4Value &EndIndex = pCurVal[-1];
559 				C4Value &Value = pCurVal[0];
560 
561 				// Typcheck
562 				if (!Array.CheckConversion(C4V_Array))
563 					throw C4AulExecError(FormatString("array slice: can't access %s as an array", Array.GetTypeName()).getData());
564 				if (!StartIndex.CheckConversion(C4V_Int))
565 					throw C4AulExecError(FormatString("array slice: start index of type %s, int expected", StartIndex.GetTypeName()).getData());
566 				if (!EndIndex.CheckConversion(C4V_Int))
567 					throw C4AulExecError(FormatString("array slice: end index of type %s, int expected", EndIndex.GetTypeName()).getData());
568 
569 				C4ValueArray *pArray = Array._getArray();
570 				if (pArray->IsFrozen()) throw C4AulExecError("array write: array is readonly");
571 				pArray->SetSlice(StartIndex._getInt(), EndIndex._getInt(), Value);
572 
573 				// Set value as result, remove both indices and first copy of value
574 				Array = Value;
575 				PopValues(3);
576 				break;
577 			}
578 
579 			case AB_STACK:
580 				if (pCPos->Par.i < 0)
581 					PopValues(-pCPos->Par.i);
582 				else
583 					PushNullVals(pCPos->Par.i);
584 				break;
585 
586 			case AB_JUMP:
587 				fJump = true;
588 				pCPos += pCPos->Par.i;
589 				break;
590 
591 			case AB_JUMPAND:
592 				if (!pCurVal[0])
593 				{
594 					fJump = true;
595 					pCPos += pCPos->Par.i;
596 				}
597 				else
598 				{
599 					PopValue();
600 				}
601 				break;
602 
603 			case AB_JUMPOR:
604 				if (!!pCurVal[0])
605 				{
606 					fJump = true;
607 					pCPos += pCPos->Par.i;
608 				}
609 				else
610 				{
611 					PopValue();
612 				}
613 				break;
614 
615 			case AB_JUMPNNIL: // ??
616 			{
617 				if (pCurVal[0].GetType() != C4V_Nil)
618 				{
619 					fJump = true;
620 					pCPos += pCPos->Par.i;
621 				}
622 				else
623 				{
624 					PopValue();
625 				}
626 				break;
627 			}
628 
629 			case AB_CONDN:
630 				if (!pCurVal[0])
631 				{
632 					fJump = true;
633 					pCPos += pCPos->Par.i;
634 				}
635 				PopValue();
636 				break;
637 
638 			case AB_COND:
639 				if (pCurVal[0])
640 				{
641 					fJump = true;
642 					pCPos += pCPos->Par.i;
643 				}
644 				PopValue();
645 				break;
646 
647 			case AB_RETURN:
648 			{
649 				// Trace
650 				if (iTraceStart >= 0)
651 				{
652 					StdStrBuf Buf("T");
653 					Buf.AppendChars('>', ContextStackSize() - iTraceStart);
654 					LogF("%s%s returned %s", Buf.getData(), pCurCtx->Func->GetName(), pCurVal->GetDataString().getData());
655 				}
656 
657 				C4Value *pReturn = pCurCtx->Return;
658 
659 				// External call?
660 				if (!pReturn)
661 				{
662 					// Get return value and stop executing.
663 					C4Value rVal = *pCurVal;
664 					PopValuesUntil(pCurCtx->Pars - 1);
665 					PopContext();
666 					return rVal;
667 				}
668 
669 				// Save return value
670 				if (pCurVal != pReturn)
671 					pReturn->Set(*pCurVal);
672 
673 				// Pop context
674 				PopContext();
675 
676 				// Clear value stack, except return value
677 				PopValuesUntil(pReturn);
678 
679 				// Jump back, continue.
680 				pCPos = pCurCtx->CPos + 1;
681 				fJump = true;
682 
683 				break;
684 			}
685 
686 			case AB_FUNC:
687 			{
688 				// Get function call data
689 				C4AulFunc *pFunc = pCPos->Par.f;
690 				C4Value *pPars = pCurVal - pFunc->GetParCount() + 1;
691 				// Save current position
692 				pCurCtx->CPos = pCPos;
693 				assert(pCurCtx->Func->GetCode() <= pCPos);
694 				// Do the call
695 				C4AulBCC *pJump = Call(pFunc, pPars, pPars, nullptr);
696 				if (pJump)
697 				{
698 					pCPos = pJump;
699 					fJump = true;
700 				}
701 				break;
702 			}
703 
704 			case AB_PAR:
705 				if (!pCurVal->CheckConversion(C4V_Int))
706 					throw C4AulExecError(FormatString("Par: index of type %s, int expected", pCurVal->GetTypeName()).getData());
707 				// Push reference to parameter on the stack
708 				if (pCurVal->_getInt() >= 0 && pCurVal->_getInt() < pCurCtx->Func->GetParCount())
709 					pCurVal->Set(pCurCtx->Pars[pCurVal->_getInt()]);
710 				else
711 					pCurVal->Set0();
712 				break;
713 
714 			case AB_THIS:
715 				if (!pCurCtx->Obj || !pCurCtx->Obj->Status)
716 					PushNullVals(1);
717 				else
718 					PushPropList(pCurCtx->Obj);
719 				break;
720 
721 			case AB_FOREACH_NEXT:
722 			{
723 				// This should always hold
724 				assert(pCurVal->CheckConversion(C4V_Int));
725 				int iItem = pCurVal->_getInt();
726 				// Check array the first time only
727 				if (!iItem)
728 				{
729 					if (!pCurVal[-1].CheckConversion(C4V_Array))
730 						throw C4AulExecError(FormatString("for: array expected, but got %s", pCurVal[-1].GetTypeName()).getData());
731 				}
732 				C4ValueArray *pArray = pCurVal[-1]._getArray();
733 				// No more entries?
734 				if (pCurVal->_getInt() >= pArray->GetSize())
735 					break;
736 				// Get next
737 				pCurVal[pCPos->Par.i] = pArray->GetItem(iItem);
738 				// Save position
739 				pCurVal->SetInt(iItem + 1);
740 				// Jump over next instruction
741 				pCPos += 2;
742 				fJump = true;
743 				break;
744 			}
745 
746 			case AB_CALL:
747 			case AB_CALLFS:
748 			{
749 
750 				C4Value *pPars = pCurVal - C4AUL_MAX_Par + 1;
751 				C4Value *pTargetVal = pCurVal - C4AUL_MAX_Par;
752 
753 				C4PropList *pDest;
754 				if (pTargetVal->CheckConversion(C4V_PropList))
755 				{
756 					pDest = pTargetVal->_getPropList();
757 				}
758 				else
759 					throw C4AulExecError(FormatString("'->': invalid target type %s, expected proplist", pTargetVal->GetTypeName()).getData());
760 
761 				// Search function for given context
762 				C4AulFunc * pFunc = pDest->GetFunc(pCPos->Par.s);
763 				if (!pFunc && pCPos->bccType == AB_CALLFS)
764 				{
765 					PopValuesUntil(pTargetVal);
766 					pTargetVal->Set0();
767 					break;
768 				}
769 
770 				// Function not found?
771 				if (!pFunc)
772 					throw C4AulExecError(FormatString(R"('->': no function "%s" in object "%s")", pCPos->Par.s->GetCStr(), pTargetVal->GetDataString().getData()).getData());
773 
774 				// Save current position
775 				pCurCtx->CPos = pCPos;
776 				assert(pCurCtx->Func->GetCode() <= pCPos);
777 
778 				// adjust parameter count
779 				if (pCurVal + 1 - pPars > pFunc->GetParCount())
780 					PopValues(pCurVal + 1 - pPars - pFunc->GetParCount());
781 				else
782 					PushNullVals(pFunc->GetParCount() - (pCurVal + 1 - pPars));
783 
784 				// Call function
785 				C4AulBCC *pNewCPos = Call(pFunc, pTargetVal, pPars, pDest);
786 				if (pNewCPos)
787 				{
788 					// Jump
789 					pCPos = pNewCPos;
790 					fJump = true;
791 				}
792 
793 				break;
794 			}
795 
796 			case AB_DEBUG:
797 #ifndef NOAULDEBUG
798 				if (C4AulDebug *pDebug = C4AulDebug::GetDebugger())
799 					pDebug->DebugStep(pCPos, pCurVal);
800 #endif
801 				break;
802 			}
803 
804 			// Continue
805 			if (!fJump)
806 				pCPos++;
807 		}
808 
809 	}
810 	catch (C4AulError &)
811 	{
812 		// Save current position
813 		assert(pCurCtx->Func->GetCode() <= pCPos);
814 		pCurCtx->CPos = pCPos;
815 		throw;
816 	}
817 }
818 
Call(C4AulFunc * pFunc,C4Value * pReturn,C4Value * pPars,C4PropList * pContext)819 C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4PropList *pContext)
820 {
821 	// No object given? Use current context
822 	if (!pContext)
823 	{
824 		assert(pCurCtx >= Contexts);
825 		pContext = pCurCtx->Obj;
826 	}
827 
828 	pFunc->CheckParTypes(pPars, true);
829 
830 	// Script function?
831 	C4AulScriptFunc *pSFunc = pFunc->SFunc();
832 	if (pSFunc)
833 	{
834 		// Push a new context
835 		C4AulScriptContext ctx;
836 		ctx.Obj = pContext;
837 		if (ctx.Obj && !ctx.Obj->Status)
838 			throw C4AulExecError("using removed object");
839 		ctx.Return = pReturn;
840 		ctx.Pars = pPars;
841 		ctx.Func = pSFunc;
842 		ctx.CPos = nullptr;
843 		PushContext(ctx);
844 
845 		// Jump to code
846 		return pSFunc->GetCode();
847 	}
848 	else
849 	{
850 		if (pContext && !pContext->Status)
851 			throw C4AulExecError("using removed object");
852 
853 		if (DEBUGREC_SCRIPT && Config.General.DebugRec)
854 		{
855 			StdStrBuf sCallText;
856 			if (pContext && pContext->GetObject())
857 				sCallText.AppendFormat("Object(%d): ", pContext->GetObject()->Number);
858 			sCallText.Append(pFunc->GetName());
859 			sCallText.AppendChar('(');
860 			for (int i=0; i<C4AUL_MAX_Par; ++i)
861 			{
862 				if (i) sCallText.AppendChar(',');
863 				C4Value &rV = pPars[i];
864 				if (rV.GetType() == C4V_String)
865 				{
866 					C4String *s = rV.getStr();
867 					if (!s)
868 						sCallText.Append("(Snull)");
869 					else
870 					{
871 						sCallText.Append(R"(")");
872 						sCallText.Append(s->GetData());
873 						sCallText.Append(R"(")");
874 					}
875 				}
876 				else
877 					sCallText.Append(rV.GetDataString());
878 			}
879 			sCallText.AppendChar(')');
880 			sCallText.AppendChar(';');
881 			AddDbgRec(RCT_AulFunc, sCallText.getData(), sCallText.getLength()+1);
882 		}
883 
884 		// Execute
885 #ifdef _DEBUG
886 		C4AulScriptContext *pCtx = pCurCtx;
887 #endif
888 		if (pReturn > pCurVal)
889 			PushValue(pFunc->Exec(pContext, pPars, true));
890 		else
891 			pReturn->Set(pFunc->Exec(pContext, pPars, true));
892 #ifdef _DEBUG
893 		assert(pCtx == pCurCtx);
894 #endif
895 
896 		// Remove parameters from stack
897 		PopValuesUntil(pReturn);
898 
899 		// Continue
900 		return nullptr;
901 	}
902 
903 }
904 
StartTrace()905 void C4AulExec::StartTrace()
906 {
907 	if (iTraceStart < 0)
908 		iTraceStart = ContextStackSize();
909 }
910 
StartProfiling(C4ScriptHost * pProfiledScript)911 void C4AulExec::StartProfiling(C4ScriptHost *pProfiledScript)
912 {
913 	// stop previous profiler run
914 	if (fProfiling) StopProfiling();
915 	fProfiling = true;
916 	// resets profling times and starts recording the times
917 	this->pProfiledScript = pProfiledScript;
918 	C4TimeMilliseconds tNow = C4TimeMilliseconds::Now();
919 	tDirectExecStart = tNow; // in case profiling is started from DirectExec
920 	tDirectExecTotal = 0;
921 	for (C4AulScriptContext *pCtx = Contexts; pCtx <= pCurCtx; ++pCtx)
922 		pCtx->tTime = tNow;
923 }
924 
PushContext(const C4AulScriptContext & rContext)925 void C4AulExec::PushContext(const C4AulScriptContext &rContext)
926 {
927 	if (pCurCtx >= Contexts + MAX_CONTEXT_STACK - 1)
928 		throw C4AulExecError("context stack overflow");
929 	*++pCurCtx = rContext;
930 	// Trace?
931 	if (iTraceStart >= 0)
932 	{
933 		StdStrBuf Buf("T");
934 		Buf.AppendChars('>', ContextStackSize() - iTraceStart);
935 		pCurCtx->dump(Buf);
936 	}
937 	// Profiler: Safe time to measure difference afterwards
938 	if (fProfiling) pCurCtx->tTime = C4TimeMilliseconds::Now();
939 }
940 
PopContext()941 void C4AulExec::PopContext()
942 {
943 	if (pCurCtx < Contexts)
944 		throw C4AulExecError("internal error: context stack underflow");
945 	// Profiler adding up times
946 	if (fProfiling)
947 	{
948 		uint32_t dt = C4TimeMilliseconds::Now() - pCurCtx->tTime;
949 		if (pCurCtx->Func)
950 			pCurCtx->Func->tProfileTime += dt;
951 	}
952 	// Trace done?
953 	if (iTraceStart >= 0)
954 	{
955 		if (ContextStackSize() <= iTraceStart)
956 		{
957 			iTraceStart = -1;
958 		}
959 	}
960 	pCurCtx--;
961 }
962 
StartProfiling(C4ScriptHost * pScript)963 void C4AulProfiler::StartProfiling(C4ScriptHost *pScript)
964 {
965 	AulExec.StartProfiling(pScript);
966 	if(pScript)
967 		ResetTimes(pScript->GetPropList());
968 	else
969 		ResetTimes();
970 }
971 
StopProfiling()972 void C4AulProfiler::StopProfiling()
973 {
974 	if (!AulExec.IsProfiling()) return;
975 	AulExec.StopProfiling();
976 	// collect profiler times
977 	C4AulProfiler Profiler;
978 	Profiler.CollectEntry(nullptr, AulExec.tDirectExecTotal);
979 	if(AulExec.pProfiledScript)
980 		Profiler.CollectTimes(AulExec.pProfiledScript->GetPropList());
981 	else
982 		Profiler.CollectTimes();
983 	Profiler.Show();
984 }
985 
CollectEntry(C4AulScriptFunc * pFunc,uint32_t tProfileTime)986 void C4AulProfiler::CollectEntry(C4AulScriptFunc *pFunc, uint32_t tProfileTime)
987 {
988 	// zero entries are not collected to have a cleaner list
989 	if (!tProfileTime) return;
990 	// add entry to list
991 	Entry e;
992 	e.pFunc = pFunc;
993 	e.tProfileTime = tProfileTime;
994 	Times.push_back(e);
995 }
996 
Show()997 void C4AulProfiler::Show()
998 {
999 	// sort by time
1000 	std::sort(Times.rbegin(), Times.rend());
1001 	// display them
1002 	Log("Profiler statistics:");
1003 	Log("==============================");
1004 	typedef std::vector<Entry> EntryList;
1005 	for (auto & e : Times)
1006 	{
1007 		LogF("%05ums\t%s", e.tProfileTime, e.pFunc ? (e.pFunc->GetFullName().getData()) : "Direct exec");
1008 	}
1009 	Log("==============================");
1010 	// done!
1011 }
1012 
DirectExec(C4PropList * p,const char * szScript,const char * szContext,bool fPassErrors,C4AulScriptContext * context,bool parse_function)1013 C4Value C4AulExec::DirectExec(C4PropList *p, const char *szScript, const char *szContext, bool fPassErrors, C4AulScriptContext* context, bool parse_function)
1014 {
1015 	if (DEBUGREC_SCRIPT && Config.General.DebugRec)
1016 	{
1017 		AddDbgRec(RCT_DirectExec, szScript, strlen(szScript)+1);
1018 		int32_t iObjNumber = p && p->GetPropListNumbered() ? p->GetPropListNumbered()->Number : -1;
1019 		AddDbgRec(RCT_DirectExec, &iObjNumber, sizeof(int32_t));
1020 	}
1021 	// profiler
1022 	StartDirectExec();
1023 	C4PropListStatic * script = ::GameScript.GetPropList();
1024 	if (p && p->IsStatic())
1025 		script = p->IsStatic();
1026 	else if (p && p->GetDef())
1027 		script = p->GetDef();
1028 	// Add a new function
1029 	auto pFunc = std::make_unique<C4AulScriptFunc>(script, nullptr, nullptr, szScript);
1030 	// Parse function
1031 	try
1032 	{
1033 		if (parse_function)
1034 		{
1035 			// Expect a full function (e.g. "func foo() { return bar(); }")
1036 			pFunc->ParseDirectExecFunc(&::ScriptEngine, context);
1037 		}
1038 		else
1039 		{
1040 			// Expect a single statement (e.g. "bar()")
1041 			pFunc->ParseDirectExecStatement(&::ScriptEngine, context);
1042 		}
1043 		C4AulParSet Pars;
1044 		C4Value vRetVal(Exec(pFunc.get(), p, Pars.Par, fPassErrors));
1045 		// profiler
1046 		StopDirectExec();
1047 		return vRetVal;
1048 	}
1049 	catch (C4AulError &ex)
1050 	{
1051 		if(fPassErrors)
1052 			throw;
1053 		::ScriptEngine.GetErrorHandler()->OnError(ex.what());
1054 		LogCallStack();
1055 		StopDirectExec();
1056 		return C4VNull;
1057 	}
1058 }
1059 
ResetTimes(C4PropListStatic * p)1060 void C4AulProfiler::ResetTimes(C4PropListStatic * p)
1061 {
1062 	// zero all profiler times of owned functions
1063 	C4AulScriptFunc *pSFunc;
1064 	for (C4String *pFn = p->EnumerateOwnFuncs(); pFn; pFn = p->EnumerateOwnFuncs(pFn))
1065 		if ((pSFunc = p->GetFunc(pFn)->SFunc()))
1066 			pSFunc->tProfileTime = 0;
1067 }
1068 
CollectTimes(C4PropListStatic * p)1069 void C4AulProfiler::CollectTimes(C4PropListStatic * p)
1070 {
1071 	// collect all profiler times of owned functions
1072 	C4AulScriptFunc *pSFunc;
1073 	for (C4String *pFn = p->EnumerateOwnFuncs(); pFn; pFn = p->EnumerateOwnFuncs(pFn))
1074 		if ((pSFunc = p->GetFunc(pFn)->SFunc()))
1075 			CollectEntry(pSFunc, pSFunc->tProfileTime);
1076 }
1077 
ResetTimes()1078 void C4AulProfiler::ResetTimes()
1079 {
1080 	// zero all profiler times of owned functions
1081 	ResetTimes(::ScriptEngine.GetPropList());
1082 	// reset sub-scripts
1083 	for (C4ScriptHost *pScript = ::ScriptEngine.Child0; pScript; pScript = pScript->Next)
1084 		ResetTimes(pScript->GetPropList());
1085 }
1086 
CollectTimes()1087 void C4AulProfiler::CollectTimes()
1088 {
1089 	// collect all profiler times of owned functions
1090 	CollectTimes(::ScriptEngine.GetPropList());
1091 	// collect sub-scripts
1092 	for (C4ScriptHost *pScript = ::ScriptEngine.Child0; pScript; pScript = pScript->Next)
1093 		CollectTimes(pScript->GetPropList());
1094 }
1095