Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions modules/gdscript/gdscript_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1805,7 +1805,8 @@
int default_par_count = 0;
BitField<MethodFlags> method_flags = {};
StringName native_base;
if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) {
GDScriptParser::Node::AccessLevel acceess_level = GDScriptParser::Node::PUBLIC;
if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, acceess_level, &native_base)) {
bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC);

if (p_function->return_type != nullptr) {
Expand Down Expand Up @@ -1925,6 +1926,27 @@
}
}

if (p_function->must_call_super) {
if (p_is_lambda) {
push_error(R"(Cannot be used in lambda expressions)", p_function);
} else {
if (p_function->body->statements.size() == 0) {
push_error(R"(Must call the base class function)", p_function);
return;
}
GDScriptParser::Node *node = p_function->body->statements[0];
bool is_super = false;

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🍏 iOS / Template (target=template_release)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🤖 Android / Editor (target=editor)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🍎 macOS / Editor (target=editor)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🤖 Android / Template arm64 (target=template_release, arch=arm64)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🤖 Android / Template arm32 (target=template_release, arch=arm32)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🍎 macOS / Template (target=template_release)

unused variable 'is_super'

Check warning on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor (target=editor)

'is_super': local variable is initialized but not referenced

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor (target=editor)

the following warning is treated as an error

Check warning on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template (target=template_release)

'is_super': local variable is initialized but not referenced

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template (target=template_release)

the following warning is treated as an error

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor w/ clang-cl (target=editor, use_llvm=yes)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🌐 Web / Template w/o threads (target=template_release, threads=no)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with clang sanitizers (target=editor, dev_build=yes, use_asan=yes, use_ubsan=yes, use_llvm=yes, linker=lld)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🌐 Web / Template w/ threads (target=template_release, threads=yes)

unused variable 'is_super'

Check failure on line 1938 in modules/gdscript/gdscript_analyzer.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with ThreadSanitizer (target=editor, dev_build=yes, use_tsan=yes, use_llvm=yes, linker=lld)

unused variable 'is_super'
if (node->type == GDScriptParser::Node::Type::CALL) {
GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(node);
if (!call_node->is_super) {
push_error(R"(Must call the base class function)", p_function);
}
} else {
push_error(R"(Must call the base class function)", p_function);
}
}
}

parser->current_function = previous_function;
static_context = previous_static_context;
}
Expand Down Expand Up @@ -2277,7 +2299,8 @@
List<GDScriptParser::DataType> par_types;
int default_arg_count = 0;
BitField<MethodFlags> method_flags = {};
if (get_function_signature(p_for->list, false, list_type, CoreStringName(_iter_get), return_type, par_types, default_arg_count, method_flags)) {
GDScriptParser::Node::AccessLevel access_level = GDScriptParser::Node::PUBLIC;
if (get_function_signature(p_for->list, false, list_type, CoreStringName(_iter_get), return_type, par_types, default_arg_count, method_flags, access_level)) {
variable_type = return_type;
variable_type.type_source = list_type.type_source;
} else if (!list_type.is_hard_type()) {
Expand Down Expand Up @@ -3570,6 +3593,7 @@
BitField<MethodFlags> method_flags = {};
GDScriptParser::DataType return_type;
List<GDScriptParser::DataType> par_types;
GDScriptParser::Node::AccessLevel access_level = GDScriptParser::Node::PUBLIC;

bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new");

Expand All @@ -3584,12 +3608,14 @@
}
}

if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) {
if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags, access_level)) {
// If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there.
// Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time.
p_call->is_static = method_flags.has_flag(METHOD_FLAG_STATIC);
if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) {
push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call);
} else if (p_call->is_super && access_level < GDScriptParser::Node::AccessLevel::PROTECTED) {
push_error(R"(Access restricted.)", p_call);
}

// If the function requires typed arrays we must make literals be typed.
Expand Down Expand Up @@ -3630,7 +3656,12 @@
base_type.is_meta_type = false; // For `to_string()`.
push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
} else if (is_self && !p_call->is_static) {
if (access_level < GDScriptParser::Node::AccessLevel::PROTECTED) {
push_error(R"(Access restricted.)", p_call);
}
mark_lambda_use_self();
} else if (!is_self && access_level < GDScriptParser::Node::AccessLevel::PUBLIC) {
push_error(R"(Access restricted.)", p_call);
}

if (!p_is_root && !p_is_await && return_type.is_hard_type() && return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
Expand Down Expand Up @@ -4171,6 +4202,7 @@
if (is_base && (!base.is_meta_type || member.variable->is_static)) {
p_identifier->set_datatype(member.get_datatype());
p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
p_identifier->is_base = is_base;
p_identifier->variable_source = member.variable;
member.variable->usages += 1;
return;
Expand Down Expand Up @@ -4447,6 +4479,18 @@
} else {
push_error(vformat(R"*(Cannot access %s "%s" from a static variable initializer.)*", source_type, p_identifier->name), p_identifier);
}
} else if (!static_context && source_is_instance_variable) {
switch (p_identifier->variable_source->access_level) {
case GDScriptParser::Node::AccessLevel::PRIVATE: {
if (p_identifier->is_base || p_identifier->source != GDScriptParser::IdentifierNode::MEMBER_VARIABLE) {
push_error(R"(Access restricted.)", p_identifier);
}
} break;
case GDScriptParser::Node::AccessLevel::PROTECTED: {
} break;
default:
break;
}
}

if (current_lambda != nullptr) {
Expand Down Expand Up @@ -4784,6 +4828,18 @@
}
}

for (GDScriptParser::ClassNode::Member member : base_type.class_type->members) {
if (member.get_name() == p_subscript->attribute->name) {
for (GDScriptParser::AnnotationNode *annotation : member.get_source_node()->annotations) {
if (annotation->name == "@protected" || annotation->name == "@protected") {
push_error(R"(Access restricted.)", p_subscript);
break;
}
}
break;
}
}

if (valid) {
// Do nothing.
} else if (base_type.is_variant() || !base_type.is_hard_type()) {
Expand Down Expand Up @@ -5709,7 +5765,7 @@
return result;
}

bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class) {
bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, GDScriptParser::Node::AccessLevel &access_level, StringName *r_native_class) {
r_method_flags = METHOD_FLAGS_DEFAULT;
r_default_arg_count = 0;
if (r_native_class) {
Expand Down Expand Up @@ -5811,6 +5867,7 @@
r_return_type = p_is_constructor ? p_base_type : found_function->get_datatype();
r_return_type.is_meta_type = false;
r_return_type.is_coroutine = found_function->is_coroutine;
access_level = found_function->access_level;

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion modules/gdscript/gdscript_analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class GDScriptAnalyzer {
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, GDScriptParser::Node::AccessLevel &access_level, StringName *r_native_class = nullptr);
bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags);
void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
Expand Down
36 changes: 36 additions & 0 deletions modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
register_annotation(MethodInfo("@must_call_super"), AnnotationInfo::FUNCTION, &GDScriptParser::must_call_super_annotation);
register_annotation(MethodInfo("@protected"), AnnotationInfo::CLASS_LEVEL, &GDScriptParser::protected_annotation);
register_annotation(MethodInfo("@private"), AnnotationInfo::CLASS_LEVEL, &GDScriptParser::private_annotation);
// Onready annotation.
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
// Export annotations.
Expand Down Expand Up @@ -4349,6 +4352,39 @@ bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node
return true;
}

bool GDScriptParser::must_call_super_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, R"("@must_call_super" annotation can only be applied to functions.)");
FunctionNode *func_node = static_cast<FunctionNode *>(p_target);
if (func_node->must_call_super) {
push_error(vformat(R"("%s" annotation can only be used once per function.)", p_annotation->name), p_annotation);
return false;
}
func_node->must_call_super = true;
return true;
}

bool GDScriptParser::protected_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CONSTANT && p_target->type != Node::VARIABLE && p_target->type != Node::FUNCTION && p_target->type != Node::CLASS && p_target->type != Node::SIGNAL,
false, R"(@protected can only be applied to constants, variables, signals, functions, and classes.)");
if (p_target->access_level != GDScriptParser::Node::AccessLevel::PUBLIC) {
push_error(R"(Only one of @protected/@private is allowed, and it can be used only once.)", p_target);
return false;
}
p_target->access_level = GDScriptParser::Node::AccessLevel::PROTECTED;
return true;
}

bool GDScriptParser::private_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CONSTANT && p_target->type != Node::VARIABLE && p_target->type != Node::FUNCTION && p_target->type != Node::CLASS && p_target->type != Node::SIGNAL,
false, R"(@private can only be applied to constants, variables, signals, functions, and classes.)");
if (p_target->access_level != GDScriptParser::Node::AccessLevel::PUBLIC) {
push_error(R"(Only one of @protected/@private is allowed, and it can be used only once.)", p_target);
return false;
}
p_target->access_level = GDScriptParser::Node::AccessLevel::PRIVATE;
return true;
}

bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");

Expand Down
13 changes: 13 additions & 0 deletions modules/gdscript/gdscript_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,15 @@ class GDScriptParser {
WHILE,
};

enum AccessLevel {
PRIVATE,
PROTECTED,
FRIEND,
PUBLIC
};

Type type = NONE;
AccessLevel access_level = PUBLIC;
int start_line = 0, end_line = 0;
int start_column = 0, end_column = 0;
int leftmost_column = 0, rightmost_column = 0;
Expand Down Expand Up @@ -856,6 +864,7 @@ class GDScriptParser {
SuiteNode *body = nullptr;
bool is_static = false; // For lambdas it's determined in the analyzer.
bool is_coroutine = false;
bool must_call_super = false;
Variant rpc_config;
MethodInfo info;
LambdaNode *source_lambda = nullptr;
Expand Down Expand Up @@ -912,6 +921,7 @@ class GDScriptParser {
FunctionNode *function_source;
};
bool function_source_is_static = false; // For non-GDScript scripts.
bool is_base = false;

FunctionNode *source_function = nullptr; // TODO: Rename to disambiguate `function_source`.

Expand Down Expand Up @@ -1528,6 +1538,9 @@ class GDScriptParser {
bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool must_call_super_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool protected_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool private_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyHint t_hint, Variant::Type t_type>
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
Expand Down
Loading