Skip to content

Commit 7dbbba7

Browse files
author
fbcosentino
committed
Added globals disabled feature to GDScript class
Games and apps requiring in-game code to be written and executed could use GDScript as both language and compiler, but misuse could lead to unexpected results, damage to device, security breaches and in the case of shared code (e.g. mods downloaded from other players) even attacks. This commit adds a feature allowing scripts to run without access to any globals (class names, singletons, constants), allowing GDScript to be used as a general purpose script compiler and interpreter running as standalone programs, where interfaces to the environment external to the script are explicitly given. This is done via the new set_globals_disabled(bool) method.
1 parent 821f484 commit 7dbbba7

File tree

4 files changed

+113
-36
lines changed

4 files changed

+113
-36
lines changed

modules/gdscript/doc_classes/GDScript.xml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,61 @@
66
<description>
77
A script implemented in the GDScript programming language. The script extends the functionality of all objects that instance it.
88
[method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes.
9+
A GDScript object can also be used as standalone, isolated script to safely run code supplied by players (either as mods or part of the game mechanic) if globals are disabled for that particular script. Disabling globals means all classes, engine singletons, project autoload scripts, and even constants which are not defined in that script (such as [code]PI[/code]) will no longer be accessible, and a script with globals disabled attempting to include those will fail to compile and run. (This is set per script, not per instance.)
10+
Since node classes (such as [Node]) can't be accessed if globals are disabled (and therefore inheritance such as [code]extends Node[/code] is not possible), those scripts can only be created and used from other scripts, not directly as nodes in the [SceneTree].
11+
Assuming the following [code]res://user_script.gd[/code]:
12+
[codeblock]
13+
var my_var
14+
15+
func my_func(arg):
16+
print("Hello %s!" % arg)
17+
[/codeblock]
18+
A node in the [SceneTree] can safely run this script with globals disabled:
19+
[codeblock]
20+
var new_script = load("res://user_script.gd")
21+
new_script.set_globals_disabled(true)
22+
if new_script.reload():
23+
# Script recompiled successfully
24+
var new_script_instance = new_script.new()
25+
# Any method can be called externally,
26+
# and variables can be externally assigned
27+
new_script_instance.my_var = 42
28+
new_script_instance.my_func("world") # prints "Hello world!"
29+
else:
30+
# Pauses with an error if running from the editor,
31+
# fails silently and continues running in standalone build
32+
print("Failed to compile script")
33+
[/codeblock]
34+
The script doesn't need to be stored as a [code].gd[/code] file. It can be compiled from source code retrieved or generated during gameplay:
35+
[codeblock]
36+
var my_source_code = "func main():\n print(\"Hello world!\")\n"
37+
var new_script = GDScript.new()
38+
new_script.source_code = my_source_code
39+
new_script.set_globals_disabled(true)
40+
if new_script.reload():
41+
new_script.new().main()
42+
else:
43+
print("Failed to compile script")
44+
[/codeblock]
45+
[b]Note:[/b] although global names won't work in the code, a script with globals disabled [i]can access[/i] anything if given [i]explicit[/i] access through method arguments or variable assignments. As example, assuming the [code]res://test_script.gd[/code] script:
46+
[codeblock]
47+
var input
48+
49+
func do_something():
50+
if input.is_action_pressed("ui_accept"):
51+
print("Hello world!")
52+
[/codeblock]
53+
The [code]input[/code] local variable can be used to give the script access to the [Input] singleton:
54+
[codeblock]
55+
var new_script = load("res://test_script.gd")
56+
new_script.set_globals_disabled(true)
57+
if new_script.reload():
58+
var new_script_instance = new_script.new()
59+
new_script_instance.set("input", Input)
60+
new_script_instance.do_something()
61+
[/codeblock]
62+
Notice the use of [method Object.set] instead of [code]new_script_instance.input = Input[/code]. If the user script did not declare the [code]input[/code] variable, using [method Object.set] fails silently, and therefore declaring and using the variable becomes optional from the user point of view.
63+
[b]Warning:[/b] since the scripts with globals disabled can access any properties and methods of objects which are explicitly given to them, critical care must be taken when exposing those objects. As example, if a node in the [SceneTree] is given, methods such as [method Node.get_node] or [method Node.get_tree] would give the script access to a large part or potentially the whole of the project. For cases like game mods (which usually need to expose running game objects and assets), an intermediate interface must be carefully designed to prevent abuse.
964
</description>
1065
<tutorials>
1166
<link>$DOCS_URL/tutorials/scripting/gdscript/index.html</link>
@@ -17,6 +72,12 @@
1772
Returns byte code for the script source code.
1873
</description>
1974
</method>
75+
<method name="is_globals_disabled">
76+
<return type="bool" />
77+
<description>
78+
Returns [code]true[/code] if globals are disabled for this script.
79+
</description>
80+
</method>
2081
<method name="new" qualifiers="vararg">
2182
<return type="Variant" />
2283
<description>
@@ -29,6 +90,13 @@
2990
[/codeblock]
3091
</description>
3192
</method>
93+
<method name="set_globals_disabled">
94+
<return type="void" />
95+
<argument index="0" name="disabled" type="bool" />
96+
<description>
97+
Sets whether access to globals should be disabled for this script, including class names, engine singletons, project autoload scripts, and anything not defined in the script itself or given explicit access externally.
98+
</description>
99+
</method>
32100
</methods>
33101
<constants>
34102
</constants>

modules/gdscript/gdscript.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,8 @@ void GDScript::_bind_methods() {
719719
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
720720

721721
ClassDB::bind_method(D_METHOD("get_as_byte_code"), &GDScript::get_as_byte_code);
722+
ClassDB::bind_method(D_METHOD("is_globals_disabled"), &GDScript::is_globals_disabled);
723+
ClassDB::bind_method(D_METHOD("set_globals_disabled", "disabled"), &GDScript::set_globals_disabled);
722724
}
723725

724726
Vector<uint8_t> GDScript::get_as_byte_code() const {
@@ -910,6 +912,7 @@ GDScript::GDScript() :
910912
_base = nullptr;
911913
_owner = nullptr;
912914
tool = false;
915+
globals_disabled = false;
913916
#ifdef TOOLS_ENABLED
914917
source_changed_cache = false;
915918
placeholder_fallback_enabled = false;

modules/gdscript/gdscript.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class GDScript : public Script {
5656
GDCLASS(GDScript, Script);
5757
bool tool;
5858
bool valid;
59+
bool globals_disabled;
5960

6061
struct MemberInfo {
6162
int index;
@@ -146,6 +147,9 @@ class GDScript : public Script {
146147
static void _bind_methods();
147148

148149
public:
150+
bool is_globals_disabled() { return globals_disabled; }
151+
void set_globals_disabled(bool p_disabled) { globals_disabled = p_disabled; }
152+
149153
virtual bool is_valid() const { return valid; }
150154

151155
bool inherits_script(const Ref<Script> &p_script) const;

modules/gdscript/gdscript_compiler.cpp

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -324,54 +324,56 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
324324
owner = owner->_owner;
325325
}
326326

327-
if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
328-
int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
329-
return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
330-
}
327+
if (!codegen.script->is_globals_disabled()) {
328+
if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
329+
int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
330+
return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
331+
}
331332

332-
/* TRY GLOBAL CLASSES */
333+
/* TRY GLOBAL CLASSES */
333334

334-
if (ScriptServer::is_global_class(identifier)) {
335-
const GDScriptParser::ClassNode *class_node = codegen.class_node;
336-
while (class_node->owner) {
337-
class_node = class_node->owner;
338-
}
335+
if (ScriptServer::is_global_class(identifier)) {
336+
const GDScriptParser::ClassNode *class_node = codegen.class_node;
337+
while (class_node->owner) {
338+
class_node = class_node->owner;
339+
}
339340

340-
if (class_node->name == identifier) {
341-
_set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression);
342-
return -1;
343-
}
341+
if (class_node->name == identifier) {
342+
_set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression);
343+
return -1;
344+
}
344345

345-
RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
346-
if (res.is_null()) {
347-
_set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
348-
return -1;
349-
}
346+
RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
347+
if (res.is_null()) {
348+
_set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
349+
return -1;
350+
}
350351

351-
Variant key = res;
352-
int idx;
352+
Variant key = res;
353+
int idx;
353354

354-
if (!codegen.constant_map.has(key)) {
355-
idx = codegen.constant_map.size();
356-
codegen.constant_map[key] = idx;
355+
if (!codegen.constant_map.has(key)) {
356+
idx = codegen.constant_map.size();
357+
codegen.constant_map[key] = idx;
357358

358-
} else {
359-
idx = codegen.constant_map[key];
360-
}
359+
} else {
360+
idx = codegen.constant_map[key];
361+
}
361362

362-
return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access)
363-
}
363+
return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access)
364+
}
364365

365366
#ifdef TOOLS_ENABLED
366-
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
367-
int idx = codegen.named_globals.find(identifier);
368-
if (idx == -1) {
369-
idx = codegen.named_globals.size();
370-
codegen.named_globals.push_back(identifier);
367+
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
368+
int idx = codegen.named_globals.find(identifier);
369+
if (idx == -1) {
370+
idx = codegen.named_globals.size();
371+
codegen.named_globals.push_back(identifier);
372+
}
373+
return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS);
371374
}
372-
return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS);
373-
}
374375
#endif
376+
}
375377

376378
//not found, error
377379

0 commit comments

Comments
 (0)