Cogito, ergo sum

Runtime problems

From Unreal Wiki, The Unreal Engine Documentation Site
Revision as of 09:24, 15 May 2011 by Wormbo (Talk | contribs) (good enough for now, feel free to extend and improve)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

When writing UnrealScript code, not all problems are caught by the compiler. Certain constructs are syntactically correct, but contain logical errors that only manifest at runtime. This article will try to list a few of the problems you may encounter.

Rule of thumb: The compiler does not execute your code.

And because it doesn't execute your code, it cannot know whether it actually makes sense. For example, it is perfectly valid to write the following two lines:

while (A < 100);
  A += 10;

But when you execute that code, the engine will quickly panic out and tell you something about a "runaway loop". How come? Well, the compiler knows it is sometimes not required to put any statements into the loop body and also allows a single semicolon after the while condition. That, however, means the A += 10; statement is actually after the loop, not inside it. Code execution will either not enter the loop at all (because A is already >= 100) or never leave it because A never changes.

Crashes[edit]

Runaway loops[edit]

As mentioned above, the engine may crash on "runaway" or infinite loops. Internally all loops are deconstructed into conditional and unconditional jumps, similar to If and GoTo. Each of these jumps increments an internal "loop counter" that deliberately triggers the crash when it exceeds a certain, quite large value. That is a safety measure to prevent the engine from completely locking up in case of infinite loop in bad code. There is only one shared loop counter in the engine for all objects and it is reset between ticks.

Infinite recursion[edit]

Another reason for an intentional crash is infinite recursion, i.e. functions calling themselves directly or indirectly, again and again without returning at some point. Like for loops, the engine maintains a counter for recursion depth. Any time one UnrealScript function calls another (or itself), the counter is incremented. When the called function returns control to the calling function, the counter is decremented again. If a function call causes the recursion depth to reach 250, the engine exits with the "infinite recursion error".

250 is quite a lot for normal operations but can easily be reached when something goes wrong with spawning/replacing Actors in Mutator.CheckReplacement(). Also certain algorithms like QuickSort rely on recursion and can potentially exceed the recursion limit. In such cases try converting your algorithm into an iterative form, possibly with some kind of stack structure if the algorithm does not use end recursion.

Non-crash problems[edit]

Accessing None[edit]

This is probably the most frequent problem you'll have to deal with. It can happen whenever you access object properties or functions through variables, for example:

function DoSomethingWith(SomeActor A)
{
  A.DoSomething();
  A.DidSomething = True;
}

That looks unsuspicious at first, but will throw Accessed None warnings at you if you pass in None as parameter. There are two possible solutions for this problem:

  1. Passing in None is an expected behavior. In that case you need to catch the None case using something like: if (A != None) A.DoSomething();
  2. Passing in None is not n expected behavior and should not really happen. Here you will need to figure out why none was passed in. Something must be wrong in the code that calls your function. Maybe the problem is even further away and only becomes obvious when you try to call your function.

Note that because there are good reasons for not crashing out or immediately leaving the affected functions, accessing None may still return a value:

local SomeActor A;
local name ActorName;
 
ActorName = A.Name;

ActorName will be 'None' after this example because A was not initialized and thus empty when A.Name was evaluated. Any access through a None variable will result in the corresponding null value of the variable type you tried to access. Similarly, trying to call a function by accessing None will have no effect. Note that function parameters are still evaluated as usual, even though the function itself cannot be called. If that function was to return a value, it will be the null value for the function's return type. Null values are 0 (zero), "" (empty string), 'None' (empty name value), None (no object), etc.

Accessing null class context[edit]

Basically this is the same problem as the Accessed None above. The difference is: accessing None applies to object variables, while accessing null class context applies to class variables:

function DoSomethingWithclass(class<SomeActor> aClass)
{
  aClass.static.DoSomething();
  // or:
  aClass.default.DidSomething = True;
}

Passing in None will cause the "Accessed null class context" warning for both lines. The same solutions apply as for the Accessed None warning, including return values and (non-)effect of function calls.

Attempt to assign variable through None[edit]

Another problem related to variables being None. This warning will only happen after either an "Accessed None" or "Accessed null class context" and is always related to variable assignment via the = operator. The two examples above both contain such an assignment line:

A.DidSomething = True;
aClass.default.DidSomething = True;

Such an assignment will not have any effect as obviously there is no place to store the value. However, the right side of the assignment is evaluated as usual.

Accessing array index out of bounds[edit]

This can happen both on static arrays and dynamic arrays if the array index is either negative or greater than or equal to the array length. The behavior differs depending on the situation, but generally has similar results as accessing None: functions are not called and the access result is a null value.

There is one exception, though: Assigning to a dynamic array index greater than or equal to the current array length will resize the array to make the assignment valid. Not that this only applies to direct assignments to the array element, not to assignments through the array element:

local array<vector> vArray< SEMI >
 
vArray[vArray.Length] = vect(1,2,3); // direct assignment, valid
vArray[vArray.Length].X = 13.37;     // not assigning to the element itself, invalid access out of bounds