Skip to content

Commit 548d828

Browse files
Reimplent soft access restriction for members prefixed with _
1 parent 7c472e6 commit 548d828

14 files changed

+130
-5
lines changed

doc/classes/EditorInterface.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@
292292
<param index="4" name="type_blocklist" type="StringName[]" default="[]" />
293293
<description>
294294
Pops up an editor dialog for creating an object.
295-
The [param callback] must take a single argument of type [StringName] which will contain the type name of the selected object or be empty if no item is selected.
295+
The [param callback] must take a single argument of type [String], which will contain the type name of the selected object (or the script path of the type, if the type is created from a script), or be an empty string if no item is selected.
296296
The [param base_type] specifies the base type of objects to display. For example, if you set this to "Resource", all types derived from [Resource] will display in the create dialog.
297297
The [param current_type] will be passed in the search box of the create dialog, and the specified type can be immediately selected when the dialog pops up. If the [param current_type] is not derived from [param base_type], there will be no result of the type in the dialog.
298298
The [param dialog_title] allows you to define a custom title for the dialog. This is useful if you want to accurately hint the usage of the dialog. If the [param dialog_title] is an empty string, the dialog will use "Create New 'Base Type'" as the default title.

doc/classes/ProjectSettings.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,12 +509,18 @@
509509
Specifies the maximum number of log files allowed (used for rotation). Set to [code]1[/code] to disable log file rotation.
510510
If the [code]--log-file &lt;file&gt;[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url] is used, log rotation is always disabled.
511511
</member>
512+
<member name="debug/gdscript/warnings/access_private_member" type="int" setter="" getter="" default="1">
513+
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a script's private member (property, method, or signal) is accessed from an external script that does not inherit the original script. A member is considered private when its name begins with an underscore ([code]_[/code]).
514+
</member>
512515
<member name="debug/gdscript/warnings/assert_always_false" type="int" setter="" getter="" default="1">
513516
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to [code]false[/code].
514517
</member>
515518
<member name="debug/gdscript/warnings/assert_always_true" type="int" setter="" getter="" default="1">
516519
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to [code]true[/code].
517520
</member>
521+
<member name="debug/gdscript/warnings/call_private_method" type="int" setter="" getter="" default="1">
522+
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a script's private method is called from an external script that does not inherit the original script. A method is considered private when its name begins with an underscore ([code]_[/code]).
523+
</member>
518524
<member name="debug/gdscript/warnings/confusable_capture_reassignment" type="int" setter="" getter="" default="1">
519525
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable captured by a lambda is reassigned, since this does not modify the outer local variable.
520526
</member>

editor/gui/create_dialog.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,9 @@ void CreateDialog::_configure_search_option_item(TreeItem *r_item, const StringN
370370
if (is_tool) {
371371
tooltip = TTR("The script will run in the editor.") + "\n" + tooltip;
372372
}
373-
r_item->add_button(0, get_editor_theme_icon(SNAME("Script")), 1, false, vformat(tooltip, ScriptServer::get_global_class_path(p_type)));
373+
const String script_path = ScriptServer::get_global_class_path(p_type);
374+
r_item->add_button(0, get_editor_theme_icon(SNAME("Script")), 1, false, vformat(tooltip, script_path));
375+
r_item->set_meta(SNAME("_script_path"), script_path);
374376
if (is_tool) {
375377
int button_index = r_item->get_button_count(0) - 1;
376378
r_item->set_button_color(0, button_index, get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));
@@ -608,11 +610,13 @@ void CreateDialog::select_base() {
608610

609611
String CreateDialog::get_selected_type() {
610612
TreeItem *selected = search_options->get_selected();
613+
611614
if (!selected) {
612615
return String();
613616
}
614617

615-
return selected->get_text(0);
618+
String type = selected->get_text(0);
619+
return ClassDB::class_exists(type) ? type : String(selected->get_meta("_script_path", ""));
616620
}
617621

618622
void CreateDialog::set_base_type(const String &p_base) {

modules/gdscript/gdscript_analyzer.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2049,6 +2049,33 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
20492049
}
20502050
}
20512051

2052+
#ifdef DEBUG_ENABLED
2053+
void GDScriptAnalyzer::check_access_private_member(GDScriptParser::IdentifierNode *p_identifier, const GDScriptParser::DataType &p_datatype, const bool p_is_call) {
2054+
if (p_identifier == nullptr) {
2055+
return;
2056+
}
2057+
if (!String(p_identifier->name).begins_with("_")) {
2058+
return;
2059+
}
2060+
if (p_datatype.kind != GDScriptParser::DataType::Kind::CLASS && p_datatype.kind != GDScriptParser::DataType::Kind::SCRIPT) {
2061+
return; // Accessing from self
2062+
}
2063+
if (parser->current_function && parser->current_function->body && parser->current_function->body->has_local(p_identifier->name)) {
2064+
return; // Accessing local variable
2065+
}
2066+
2067+
GDScriptParser::DataType target_resolved = type_from_metatype(p_datatype);
2068+
if (target_resolved.class_type != nullptr && !target_resolved.class_type->has_member(p_identifier->name)) {
2069+
return; // Accessing an inexisting method
2070+
}
2071+
if (is_type_compatible(target_resolved, type_from_metatype(parser->current_class->get_datatype()))) {
2072+
return; // Not accessing from the external places
2073+
}
2074+
2075+
parser->push_warning(p_identifier, p_is_call ? GDScriptWarning::CALL_PRIVATE_METHOD : GDScriptWarning::ACCESS_PRIVATE_MEMBER, p_identifier->name);
2076+
}
2077+
#endif // DEBUG_ENABLED
2078+
20522079
void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
20532080
for (int i = 0; i < p_suite->statements.size(); i++) {
20542081
GDScriptParser::Node *stmt = p_suite->statements[i];
@@ -3552,10 +3579,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
35523579
if (p_call->callee == nullptr && current_lambda != nullptr) {
35533580
push_error("Cannot use `super()` inside a lambda.", p_call);
35543581
}
3582+
3583+
#ifdef DEBUG_ENABLED
3584+
GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
3585+
check_access_private_member(identifier, p_call->get_datatype(), true);
3586+
#endif
35553587
} else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
35563588
base_type = parser->current_class->get_datatype();
35573589
base_type.is_meta_type = false;
35583590
is_self = true;
3591+
#ifdef DEBUG_ENABLED
3592+
GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
3593+
check_access_private_member(identifier, p_call->get_datatype(), true);
3594+
#endif
35593595
} else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
35603596
GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee);
35613597
if (subscript->base == nullptr) {
@@ -3589,6 +3625,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
35893625
base_type = subscript->base->get_datatype();
35903626
is_self = subscript->base->type == GDScriptParser::Node::SELF;
35913627
}
3628+
#ifdef DEBUG_ENABLED
3629+
check_access_private_member(subscript->attribute, subscript->base->get_datatype(), true);
3630+
#endif
35923631
} else {
35933632
// Invalid call. Error already sent in parser.
35943633
// TODO: Could check if Callable here too.
@@ -4520,6 +4559,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
45204559
}
45214560
}
45224561

4562+
#ifdef DEBUG_ENABLED
4563+
check_access_private_member(p_identifier, p_identifier->get_datatype());
4564+
#endif
4565+
45234566
return;
45244567
}
45254568

@@ -4881,6 +4924,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
48814924
}
48824925
result_type.kind = GDScriptParser::DataType::VARIANT;
48834926
}
4927+
4928+
#ifdef DEBUG_ENABLED
4929+
check_access_private_member(p_subscript->attribute, p_subscript->base->get_datatype());
4930+
#endif
48844931
} else {
48854932
if (p_subscript->index == nullptr) {
48864933
return;

modules/gdscript/gdscript_analyzer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ class GDScriptAnalyzer {
7171

7272
void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement);
7373

74+
#ifdef DEBUG_ENABLED
75+
void check_access_private_member(GDScriptParser::IdentifierNode *p_identifier, const GDScriptParser::DataType &p_datatype, const bool p_is_call = false);
76+
#endif
77+
7478
void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation);
7579
void resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source = nullptr);
7680
void resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source = nullptr);

modules/gdscript/gdscript_warning.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ String GDScriptWarning::get_message() const {
162162
return vformat(R"*(The default value uses "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]);
163163
case ONREADY_WITH_EXPORT:
164164
return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
165+
case ACCESS_PRIVATE_MEMBER:
166+
CHECK_SYMBOLS(1);
167+
return vformat(R"(The member "%s" is private. It should not be accessed from an external script.)", symbols[0]);
168+
case CALL_PRIVATE_METHOD:
169+
CHECK_SYMBOLS(1);
170+
return vformat(R"*(The method "%s()" is private. It should not be called from an external script.)*", symbols[0]);
165171
#ifndef DISABLE_DEPRECATED
166172
// Never produced. These warnings migrated from 3.x by mistake.
167173
case PROPERTY_USED_AS_FUNCTION: // There is already an error.
@@ -238,6 +244,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
238244
PNAME("NATIVE_METHOD_OVERRIDE"),
239245
PNAME("GET_NODE_DEFAULT_WITHOUT_ONREADY"),
240246
PNAME("ONREADY_WITH_EXPORT"),
247+
PNAME("ACCESS_PRIVATE_MEMBER"),
248+
PNAME("CALL_PRIVATE_METHOD"),
241249
#ifndef DISABLE_DEPRECATED
242250
"PROPERTY_USED_AS_FUNCTION",
243251
"CONSTANT_USED_AS_FUNCTION",

modules/gdscript/gdscript_warning.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class GDScriptWarning {
8989
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
9090
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
9191
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
92+
ACCESS_PRIVATE_MEMBER, // Accessing a private member from external places. E.g. accessing an `_`-prefixed member from other classes that are not derived from the class where the member is defined.
93+
CALL_PRIVATE_METHOD, // Calling a private method from external places. E.g. calling an `_`-prefixed method from other classes that are not derived from the class where the method is defined.
9294
#ifndef DISABLE_DEPRECATED
9395
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
9496
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
@@ -146,6 +148,8 @@ class GDScriptWarning {
146148
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
147149
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
148150
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
151+
WARN, // ACCESS_PRIVATE_MEMBER
152+
WARN, // CALL_PRIVATE_METHOD
149153
#ifndef DISABLE_DEPRECATED
150154
WARN, // PROPERTY_USED_AS_FUNCTION
151155
WARN, // CONSTANT_USED_AS_FUNCTION

modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class SuperMethodsRecognized extends BaseClass:
1515
return result
1616

1717
func test():
18+
@warning_ignore_start("call_private_method")
1819
var test1 = SuperClassMethodsRecognized.new()
1920
print(test1._get_property_list()) # Calls base class's method.
2021
var test2 = SuperMethodsRecognized.new()

modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,30 @@ func test_unsafe_void_return() -> void:
151151
func get_class():
152152
pass
153153

154+
class PrivA:
155+
var _t = 1
156+
157+
func _priv_method():
158+
_t = 5
159+
160+
class PrivB:
161+
var a = PrivA.new()
162+
163+
func _foo():
164+
@warning_ignore("access_private_member")
165+
a._t = 2
166+
@warning_ignore("call_private_method")
167+
a._priv_method()
168+
169+
class PrivC extends PrivA:
170+
var a = PrivA.new()
171+
172+
func _foo():
173+
@warning_ignore("access_private_member")
174+
a._t = 2
175+
@warning_ignore("call_private_method")
176+
a._priv_method()
177+
154178
# We don't want to execute it because of errors, just analyze.
155179
func test():
156180
pass
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
GDTEST_OK
1+
GDTEST_PARSER_ERROR
2+
Used space character for indentation instead of tab as used before in the file.

0 commit comments

Comments
 (0)