fInternal basically acted as a reverse "evil bit" as in RFC 3514: when
set, the engine would not do any checks on the script contained in the
control packet, nor log the script (visibly in game; the packet log
would of course contain the packet). A malicious game client thus would
be able to inject arbitrary script without people (immediately) noticing
anything was amiss.
As of this patch, only the host is able to execute arbitrary scripts,
and those will be shown in the message board for all players to see.
This privilege can be irrevocably disabled in network games by any
client by using the "/nodebug" message board command.
Closes#936.
While fInternal-flagged script controls may be useful for debuggers to
evaluate watch expressions because they don't get shown ingame, they
also bypass all checks the engine does and as such are a nightmare in
network play.
Part of #936.
This is mostly for the benefit of savegames, which rely on being able to
save scenario functions as Scenario.Prototype.*, but also removes an
opportunity to break things.
C4StringTable::RegString modifies the provided StdStrBuf. Use FindString
instead, since GetPropertyByS reliably returns failure for a
freshly-registered string anyway.
excNotFound is used to signal that the end of a list is reached. But these
error conditions in C4Value::CompileFunc signal some logic error, not
finding an end-of-list marker instead of a C4Value.
The parser does not know whether the constant proplist it is about to fill
is missing because it was overwritten by a later local/constant, or because
the preparser was interrupted by a script error and didn't store its
proplist. Thus, the parser cannot simply give up at that point, and in
order to keep things simple it creates a throwaway proplist. This proplist
was thrown away too soon, though.
Thanks to Zapper for the testcase.
C4AulParse::Host is now only used for local variables and named functions.
Global directexec functions have neither, so they won't need a scripthost
at all anymore.
The missing break meant that the freshly created copy of the proplist would
get overwritten by the original, which was thankfully caught and announced
with "internal error: constant proplist has the wrong parent".
Thanks to Zapper for the testcase.
This makes functions independent of their "Code Owner"s, which removes the
necessity to maintain that connection and carefully reset functions when
their scripthost is cleared.
"Anon" referred to the fact that these proplists have neither a number,
like simple proplists, objects and effects, nor an ID like Definitions.
However, they now store the names of the global constant or property they
are in, so "Anon" is no longer appropriate.
There are now three classes of proplists:
- ordinary proplists (C4PropListScript) have a number only in the savegame
- objects and effects (C4PropListNumbered) always have a number
- proplists created during initialization (C4PropListStatic) have a path
So the function could be called NewNamed, but the source of the proplist
has been far more stable than the method used for serialization, and Static
somewhat describes the source.
C4Aul's Warn uses the format attribute to check for format string correctness.
Passing a dynamic string buffer from FormatString to it will break the build
with -Werror=format-string. Given that Warn already does the formatting itself,
just drop the use of FormatString
C4Set<T>::Sort() breaks the internals of the class which Guenther doesn't like. Now the sorted list is returned as a list of pointers into the original set.
Functions are refcounted now and might outlive their script. Fortunately,
the function destructor didn't really need the Owner. This further damages
the pretense of multiple scriptengines, but that could be repaired by
storing a pointer to the engine in the functions.
Mostly by changing functions to take a const reference, but also by using
move constructors. This helps with C4String leak debugging by reducing the
reference count changes.
Previously, only the outer proplist would be copied, but the parser expects
the inner proplists to also be present. Copy proplists deeply instead, as
is already done for functions, and in the linking step.
Since the script engine doesn't have an appropriate function to create the
proplist in, simply create it in the constructor and arrange for the
string table to be constructed first.
Because the interpreter throws for every function that is called with a
this parameter that has been removed, this() isn't a function anymore.
The function could stay around so that Call("this") or foo->this()
would still work, but I doubt any script ever did that.
These operators have a stricter definition of equality than the == and !=
operators. Those are already stricter than in some other languages, so the
new ones are probably not needed very often. But if the need does arise,
there's no workaround short of modifying the data structures and checking
whether they are still the same.
The rope will create a C4AulScript for the rope engine functions instead of
putting them into the global scope, and we might want to put C4Object-only
functions into a separate C4AulScript some day, too.
The map is currently only used in the parser for some warning heuristics.
Since it uses a hash table with separate chaining and the amount of
functions is fairly predictable, the hash table doesn't have to be
resizable.
The parser now copies the contents of the proplists in the order of their
source scripts into the final proplist. This way, local variable contents
get properly included, and the list of functions has one user less.
Also move C4AulDefFunc and C4ScriptFnDef to the same header the template
helper classes are in. Like them, these classes are a mostly invisible
implementation detail of the engine script functions.
The C4AulScriptContext is now an implementation detail of C4AulExec.
Also consolidate the C4Object * Obj and C4PropList * Def members to just
C4PropList * Obj and convert that to C4Object * on demand.
Constant expressions for global constants and for Definition properties are
now treated the same way. The preparser creates the structure that
the parser will fill in. Since the structure will not move, the parser can
refer to it before it is filled in, just like functions can call siblings
defined further down in the script. This will also allow proplists (and later
functions) to refer to each other.
For example, the proplist in Clonk.ActMap.Walk is saved as DClonk.ActMap.Walk.
Should the script defining the proplist change while the savegame is stored,
the proplist will have the new contents instead of the old ones after savegame
load.
Also, save functions as DFlint.Hit instead of fDFlint.Hit. Loading uses the same
code as static proplist loading.
Curiously, this makes g++ 4.4 use the C4RefCntPointer move constructor,
which was broken until now. Fix it to take a mutable rvalue reference.
The rules for ctx->Obj and ctx->Def changed recently. The latter is now the
real this pointer, and Obj is Def->GetObject(). There's no need to check
this in FnTranslate.
This short-circuiting operator will evaluate to its first operand if the operand
is not nil, or to its second operand otherwise. Its intended use is to simplify
defaulting expressions that may evaluate to nil to a valid value.
So the only thing #appendtos may contain are lokal functions and variables,
which are only reachable via the Definition the script is appended to, and
global constants and variables, which only need to be parsed once. Thus,
#appendto scripts can skip being parsed without a definition, and the
errors that sometimes produces are gone.
This was probably broken by the C4V_Any/C4V_Nil separation. Also clean
the function up a bit and add an assert that should catch similar stuff in
the future.
The global variables ended up with the temporary name list created during
load that didn't necessarily contain all variables or even in the right
order. As far as I can tell, this happened since 2005, but nobody noticed
because the list of global variables didn't tend to change between save and
load.
For functions that are not appended/included, this is done by reusing them.
Functions that are not in the new script version are left with their code
raising and error. Appended/included functions are handled by a reference count.
The parser doesn't need it to find the functions it has to parse anymore.
And the global proplist can be cleared before removing the functions,
instead of having engine functions deregister themselves and script
functions being deregistered when their linked function is removed.
Instead of carefully inserting functions at the start or end of the list,
build the list just before the parser runs, at the same time as filling
the proplist where the functions are looked up.
This way, the overloaded function is simply the one that was previously in
the proplist, is not needed outside of the overloading function, and can thus
be replaced in the proplist.