diff --git a/README.md b/README.md index d36b08774..eede7f5ac 100644 --- a/README.md +++ b/README.md @@ -185,3 +185,14 @@ Also as of this release, both the compiler and VM can run on non-x86 platforms ### SourcePawn 1.12 SourcePawn 1.12 is currently in development. + +This release contains a number of bug fixes and internal improvements. It also greatly improves +error message readability in the style of modern compilers. + +This release contains a number of language changes. + - Function bodies must now be braced. There is no compatibility mode for this change. + - `INVALID_FUNCTION` is now type `null_t`. The old internal type `nullfunc_t` has been removed. + `INVALID_FUNCTION` and `null` are now equivalent, which means code manually checking for `-1` + will not work. There are three functions added to `IPluginContext` to help native code with + this change: `GetNullFunctionValue()`, `IsNullFunctionId()`, and `GetFunctionByIdOrNull()`. + - Warnings around indentation have been removed. diff --git a/compiler/assembler.cpp b/compiler/assembler.cpp index dc83d95f9..4d5b88bcd 100644 --- a/compiler/assembler.cpp +++ b/compiler/assembler.cpp @@ -924,7 +924,8 @@ Assembler::Assemble(SmxByteBuffer* buffer) code->header().main = 0; code->header().code = sizeof(sp_file_code_t); code->header().features = SmxConsts::kCodeFeatureDirectArrays | - SmxConsts::kCodeFeatureHeapScopes; + SmxConsts::kCodeFeatureHeapScopes | + SmxConsts::kCodeFeatureNullFunctions; code->setBlob(cg_.code_ptr(), cg_.code_size()); // Set up the data section. Note pre-SourceMod 1.7, the |memsize| was diff --git a/compiler/expressions.cpp b/compiler/expressions.cpp index 1aa73d410..7ea0cf078 100644 --- a/compiler/expressions.cpp +++ b/compiler/expressions.cpp @@ -290,20 +290,9 @@ matchobjecttags(Type* formal, Type* actual, int flags) return FALSE; } - if (actualtag == types->tag_nullfunc()) { - // All functions are nullable. We use a separate constant for backward - // compatibility; plugins and extensions check -1, not 0. - if (formal->isFunction()) - return TRUE; - - if (!(flags & MATCHTAG_SILENT)) - report(154) << pc_tagname(formaltag); - return FALSE; - } - if (actualtag == types->tag_null()) { // All objects are nullable. - if (formal->isObject()) + if (formal->isFunction() || formal->isObject()) return TRUE; // Some methodmaps are nullable. The nullable property is inherited @@ -427,7 +416,7 @@ matchfunctags(Type* formal, Type* actual) if (formaltag == types->tag_function() && actual->isFunction()) return TRUE; - if (actualtag == types->tag_nullfunc()) + if (actualtag == types->tag_null()) return TRUE; if (!actual->isFunction()) diff --git a/compiler/main.cpp b/compiler/main.cpp index 996b3276e..245fd87db 100644 --- a/compiler/main.cpp +++ b/compiler/main.cpp @@ -361,7 +361,7 @@ setconstants(void) { auto& cc = CompileContext::get(); DefineConstant(cc, cc.atom("EOS"), 0, 0); - DefineConstant(cc, cc.atom("INVALID_FUNCTION"), -1, cc.types()->tag_nullfunc()); + DefineConstant(cc, cc.atom("INVALID_FUNCTION"), 0, cc.types()->tag_null()); DefineConstant(cc, cc.atom("cellmax"), INT_MAX, 0); DefineConstant(cc, cc.atom("cellmin"), INT_MIN, 0); diff --git a/compiler/messages.h b/compiler/messages.h index 986a1a585..c3ac13411 100644 --- a/compiler/messages.h +++ b/compiler/messages.h @@ -178,7 +178,7 @@ static const char* errmsg[] = { /*151*/ "unmatched opening brace ('{') (line %d)\n", /*152*/ "no setter found for property %s\n", /*153*/ "Array-based enum structs have been removed. See https://wiki.alliedmods.net/SourcePawn_Transitional_Syntax#Enum_Structs\n", - /*154*/ "cannot assign INVALID_FUNCTION to a non-function type\n", + /*154*/ "unused154\n", /*155*/ "expected newline, but found '%s'\n", /*156*/ "invalid 'using' declaration\n", /*157*/ "'%s' is a reserved keyword\n", diff --git a/compiler/types.cpp b/compiler/types.cpp index f76b4b8d1..61650aff4 100644 --- a/compiler/types.cpp +++ b/compiler/types.cpp @@ -131,7 +131,6 @@ TypeDictionary::init() type_void_ = defineVoid(); type_object_ = defineObject("object"); type_null_ = defineObject("null_t"); - type_nullfunc_ = defineObject("nullfunc_t"); } Type* diff --git a/compiler/types.h b/compiler/types.h index 94316eb49..132cce5a7 100644 --- a/compiler/types.h +++ b/compiler/types.h @@ -384,7 +384,6 @@ class TypeDictionary callback(type); } - Type* type_nullfunc() const { return type_nullfunc_; } Type* type_object() const { return type_object_; } Type* type_null() const { return type_null_; } Type* type_function() const { return type_function_; } @@ -394,7 +393,6 @@ class TypeDictionary Type* type_bool() const { return type_bool_; } Type* type_string() const { return type_string_; } - int tag_nullfunc() const { return type_nullfunc_->tagid(); } int tag_object() const { return type_object_->tagid(); } int tag_null() const { return type_null_->tagid(); } int tag_function() const { return type_function_->tagid(); } @@ -414,7 +412,6 @@ class TypeDictionary tr::unordered_map types_; tr::unordered_map tags_; Type* type_int_ = nullptr; - Type* type_nullfunc_ = nullptr; Type* type_object_ = nullptr; Type* type_null_ = nullptr; Type* type_function_ = nullptr; diff --git a/include/smx/smx-headers.h b/include/smx/smx-headers.h index 676662c14..950681b47 100644 --- a/include/smx/smx-headers.h +++ b/include/smx/smx-headers.h @@ -78,6 +78,9 @@ struct SmxConsts { // This feature adds the HEAP_SAVE and HEAP_RESTORE opcodes. static const uint32_t kCodeFeatureHeapScopes = (1 << 2); + + // This feature indicates that INVALID_FUNCTION is null (0) instead of -1. + static const uint32_t kCodeFeatureNullFunctions = (1 << 3); }; // These structures are byte-packed. diff --git a/include/sp_vm_api.h b/include/sp_vm_api.h index 484662372..4ad040866 100644 --- a/include/sp_vm_api.h +++ b/include/sp_vm_api.h @@ -475,7 +475,7 @@ class IPluginRuntime virtual IPluginFunction* GetFunctionByName(const char* public_name) = 0; /** - * @brief Returns a function by its id. + * @brief Deprecated. Use GetFunctionByIdOrNull or GetFunctionByIdOrError instead. * * @param func_id Function ID. * @return A new IPluginFunction pointer, NULL if not found. @@ -1198,6 +1198,38 @@ class IPluginContext * EnterHeapScope. */ virtual void LeaveHeapScope() = 0; + + /** + * @brief Returns the value of a null function. If kCodeFeatureNullFunction + * is set, this returns 0. If unset, this returns -1, the legacy value. + */ + virtual cell_t GetNullFunctionValue() = 0; + + /** + * @brief Returns whether the value is a null function. + */ + virtual bool IsNullFunctionId(funcid_t func) = 0; + + /** + * @brief Convert a funcid_t to an IPluginFunction, reporting an error if + * the function is invalid. funcid_t can be INVALID_FUNCTION. + * + * If |func| is non-null and valid, |out| is set to the function and true + * is returned. + * + * If |func| is non-null and invalid, |out| is set to null, false is + * returned, and an error is reported. + * + * If |func| is GetNullFunctionValue(), |out| is set to null and true is + * returned. + */ + virtual bool GetFunctionByIdOrNull(funcid_t func, IPluginFunction** out) = 0; + + /** + * @brief Convert a funcid_t to an IPluginFunction, reporting an error if + * the function is invalid or null. + */ + virtual IPluginFunction* GetFunctionByIdOrError(funcid_t func) = 0; }; class AutoEnterHeapScope diff --git a/tests/api/function-id.sp b/tests/api/function-id.sp new file mode 100644 index 000000000..95dfd6548 --- /dev/null +++ b/tests/api/function-id.sp @@ -0,0 +1,5 @@ +#include + +public main() { + printnum(execute(INVALID_FUNCTION, 1)); +} diff --git a/vm/plugin-context.cpp b/vm/plugin-context.cpp index 3d96d0798..364308d0a 100644 --- a/vm/plugin-context.cpp +++ b/vm/plugin-context.cpp @@ -1101,14 +1101,43 @@ PluginContext::HeapAlloc2dArray(unsigned int length, unsigned int stride, cell_t return true; } -void -PluginContext::EnterHeapScope() -{ +void PluginContext::EnterHeapScope() { enterHeapScope(); } -void -PluginContext::LeaveHeapScope() -{ +void PluginContext::LeaveHeapScope() { leaveHeapScope(); } + +cell_t PluginContext::GetNullFunctionValue() { + auto image = runtime()->image(); + if (image->DescribeCode().features & SmxConsts::kCodeFeatureNullFunctions) { + return 0; + } + return -1; +} + +bool PluginContext::IsNullFunctionId(funcid_t func) { + return func == GetNullFunctionValue(); +} + +bool PluginContext::GetFunctionByIdOrNull(funcid_t func, IPluginFunction** out) { + if (IsNullFunctionId(func)) { + *out = nullptr; + return true; + } + + *out = GetFunctionById(func); + if (!*out) { + ReportError("Invalid function id: 0x%08x", func); + return false; + } + return true; +} + +IPluginFunction* PluginContext::GetFunctionByIdOrError(funcid_t func_id) { + if (auto fn = GetFunctionById(func_id)) + return fn; + ReportError("Invalid function id: 0x%08x", func_id); + return nullptr; +} diff --git a/vm/plugin-context.h b/vm/plugin-context.h index 184f4a0f2..5d190b01b 100644 --- a/vm/plugin-context.h +++ b/vm/plugin-context.h @@ -25,7 +25,7 @@ static const cell_t STACK_MARGIN = 64; // 16 parameters of safety, I guess class Environment; class PluginContext; -class PluginContext : public BasePluginContext +class PluginContext final : public BasePluginContext { public: PluginContext(PluginRuntime* pRuntime); @@ -61,6 +61,10 @@ class PluginContext : public BasePluginContext const cell_t* init) override; void EnterHeapScope() override; void LeaveHeapScope() override; + cell_t GetNullFunctionValue() override; + bool IsNullFunctionId(funcid_t func) override; + bool GetFunctionByIdOrNull(funcid_t func, IPluginFunction** out) override; + IPluginFunction* GetFunctionByIdOrError(funcid_t func_id) override; bool Invoke(funcid_t fnid, const cell_t* params, unsigned int num_params, cell_t* result); size_t HeapSize() const { diff --git a/vm/shell/shell.cpp b/vm/shell/shell.cpp index d7bda51e0..b6ad79b1c 100644 --- a/vm/shell/shell.cpp +++ b/vm/shell/shell.cpp @@ -185,11 +185,12 @@ static cell_t DoExecute(IPluginContext* cx, const cell_t* params) { int32_t ok = 0; for (size_t i = 0; i < size_t(params[2]); i++) { - if (IPluginFunction* fn = cx->GetFunctionById(params[1])) { - if (fn->Execute(nullptr) != SP_ERROR_NONE) - continue; - ok++; - } + IPluginFunction* fn; + if (!cx->GetFunctionByIdOrNull(params[1], &fn) || !fn) + return 0; + if (fn->Execute(nullptr) != SP_ERROR_NONE) + continue; + ok++; } return ok; } @@ -197,10 +198,11 @@ static cell_t DoExecute(IPluginContext* cx, const cell_t* params) static cell_t DoInvoke(IPluginContext* cx, const cell_t* params) { for (size_t i = 0; i < size_t(params[2]); i++) { - if (IPluginFunction* fn = cx->GetFunctionById(params[1])) { - if (!fn->Invoke()) - return 0; - } + IPluginFunction* fn; + if (!cx->GetFunctionByIdOrNull(params[1], &fn) || !fn) + return 0; + if (!fn->Invoke()) + return 0; } return 1; } diff --git a/vm/smx-v1-image.cpp b/vm/smx-v1-image.cpp index 271ee4c1b..83c591319 100644 --- a/vm/smx-v1-image.cpp +++ b/vm/smx-v1-image.cpp @@ -280,7 +280,8 @@ SmxV1Image::validateCode() uint32_t supported_features = SmxConsts::kCodeFeatureDirectArrays | - SmxConsts::kCodeFeatureHeapScopes; + SmxConsts::kCodeFeatureHeapScopes | + SmxConsts::kCodeFeatureNullFunctions; if (features & ~supported_features) return error("unsupported feature set; code is too new");