diff --git a/src/common/scripting/backend/codegen.cpp b/src/common/scripting/backend/codegen.cpp index 49999ae68..2e4bb7e9e 100644 --- a/src/common/scripting/backend/codegen.cpp +++ b/src/common/scripting/backend/codegen.cpp @@ -268,6 +268,8 @@ PFunction *FindBuiltinFunction(FName funcname) // //========================================================================== +static bool AreCompatibleFnPtrTypes(PPrototype *to, PPrototype *from); + bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare) { if (dest->isPointer() && source->isPointer()) @@ -301,8 +303,9 @@ bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare) { auto from = static_cast(source); auto to = static_cast(dest); - return to->PointedType == TypeVoid || (from->PointedType == to->PointedType && from->ArgFlags == to->ArgFlags && FScopeBarrier::CheckSidesForFunctionPointer(from->Scope, to->Scope)); - // TODO allow narrowing argument types and widening return types via cast, ex.: Function)> to Function)> + if(from->PointedType == TypeVoid) return false; + + return to->PointedType == TypeVoid || (AreCompatibleFnPtrTypes((PPrototype *)to->PointedType, (PPrototype *)from->PointedType) && from->ArgFlags == to->ArgFlags && FScopeBarrier::CheckSidesForFunctionPointer(from->Scope, to->Scope)); } } return false; @@ -1615,6 +1618,35 @@ FxTypeCast::~FxTypeCast() // //========================================================================== +FxConstant * FxTypeCast::convertRawFunctionToFunctionPointer(FxExpression * in, FScriptPosition &ScriptPosition) +{ + assert(in->isConstant() && in->ValueType == TypeRawFunction); + FxConstant *val = static_cast(in); + PFunction * fn = static_cast(val->value.pointer); + if(fn && (fn->Variants[0].Flags & (VARF_Virtual | VARF_Action | VARF_Method)) == 0) + { + val->ValueType = val->value.Type = NewFunctionPointer(fn->Variants[0].Proto, TArray(fn->Variants[0].ArgFlags), FScopeBarrier::SideFromFlags(fn->Variants[0].Flags)); + return val; + } + else if(fn && (fn->Variants[0].Flags & (VARF_Virtual | VARF_Action | VARF_Method)) == VARF_Method) + { + TArray flags(fn->Variants[0].ArgFlags); + flags[0] = 0; + val->ValueType = val->value.Type = NewFunctionPointer(fn->Variants[0].Proto, std::move(flags), FScopeBarrier::SideFromFlags(fn->Variants[0].Flags)); + return val; + } + else if(!fn) + { + val->ValueType = val->value.Type = NewFunctionPointer(nullptr, {}, -1); // Function + return val; + } + else + { + ScriptPosition.Message(MSG_ERROR, "virtual/action function pointers are not allowed"); + return nullptr; + } +} + FxExpression *FxTypeCast::Resolve(FCompileContext &ctx) { CHECKRESOLVED(); @@ -1626,6 +1658,22 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx) if (result != this) return result; } + if (basex->isConstant() && basex->ValueType == TypeRawFunction && ValueType->isFunctionPointer()) + { + FxConstant *val = convertRawFunctionToFunctionPointer(basex, ScriptPosition); + if(!val) + { + delete this; + return nullptr; + } + } + else if (basex->isConstant() && basex->ValueType == TypeRawFunction && ValueType == TypeVMFunction) + { + FxConstant *val = static_cast(basex); + val->ValueType = val->value.Type = TypeVMFunction; + val->value.pointer = static_cast(val->value.pointer)->Variants[0].Implementation; + } + // first deal with the simple types if (ValueType == TypeError || basex->ValueType == TypeError || basex->ValueType == nullptr) { @@ -6370,9 +6418,8 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx) if (ctx.Version >= MakeVersion(4, 11, 100)) { // VMFunction is only supported since 4.12 and Raze 1.8. - newex = new FxConstant(static_cast(sym)->Variants[0].Implementation, ScriptPosition); + newex = new FxConstant(static_cast(sym), ScriptPosition); goto foundit; - } } } @@ -6391,7 +6438,7 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx) if (ctx.Version >= MakeVersion(4, 11, 100)) { // VMFunction is only supported since 4.12 and Raze 1.8. - newex = new FxConstant(static_cast(sym)->Variants[0].Implementation, ScriptPosition); + newex = new FxConstant(static_cast(sym), ScriptPosition); goto foundit; } } @@ -6539,7 +6586,6 @@ FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PContainerType * if (result != this) return result; } - if (objtype != nullptr && (sym = objtype->Symbols.FindSymbolInTable(Identifier, symtbl)) != nullptr) { if (sym->IsKindOf(RUNTIME_CLASS(PSymbolConst))) @@ -6755,7 +6801,7 @@ FxExpression *FxMemberIdentifier::Resolve(FCompileContext& ctx) SAFE_RESOLVE(Object, ctx); - // check for class or struct constants if the left side is a type name. + // check for class or struct constants/functions if the left side is a type name. if (Object->ValueType == TypeError) { if (ccls != nullptr) @@ -6769,6 +6815,16 @@ FxExpression *FxMemberIdentifier::Resolve(FCompileContext& ctx) delete this; return FxConstant::MakeConstant(sym, ScriptPosition); } + else if(sym->IsKindOf(RUNTIME_CLASS(PFunction))) + { + if (ctx.Version >= MakeVersion(4, 11, 100)) + { + // VMFunction is only supported since 4.12 and Raze 1.8. + auto x = new FxConstant(static_cast(sym), ScriptPosition); + delete this; + return x->Resolve(ctx); + } + } else { auto f = dyn_cast(sym); @@ -9604,6 +9660,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) assert(Self != nullptr); selfemit = Self->Emit(build); assert(selfemit.RegType == REGT_POINTER); + build->Emit(OP_NULLCHECK, selfemit.RegNum, 0, 0); staticcall = false; } else staticcall = true; @@ -11704,11 +11761,118 @@ FxFunctionPtrCast::~FxFunctionPtrCast() // //========================================================================== +static bool AreCompatibleFnPtrs(PFunctionPointer * to, PFunctionPointer * from); + +bool CanNarrowTo(PClass * from, PClass * to) +{ + return from->IsAncestorOf(to); +} + +bool CanWidenTo(PClass * from, PClass * to) +{ + return to->IsAncestorOf(from); +} + +static bool AreCompatibleFnPtrTypes(PPrototype *to, PPrototype *from) +{ + if(to->ArgumentTypes.Size() != from->ArgumentTypes.Size() + || to->ReturnTypes.Size() != from->ReturnTypes.Size()) return false; + int n = to->ArgumentTypes.Size(); + + //allow narrowing of arguments + for(int i = 0; i < n; i++) + { + PType * fromType = from->ArgumentTypes[i]; + PType * toType = to->ArgumentTypes[i]; + if(fromType->isFunctionPointer() && toType->isFunctionPointer()) + { + if(!AreCompatibleFnPtrs(static_cast(toType), static_cast(fromType))) return false; + } + else if(fromType->isClassPointer() && toType->isClassPointer()) + { + PClassPointer * fromClass = static_cast(fromType); + PClassPointer * toClass = static_cast(toType); + //allow narrowing parameters + if(!CanNarrowTo(fromClass->ClassRestriction, toClass->ClassRestriction)) return false; + } + else if(fromType->isObjectPointer() && toType->isObjectPointer()) + { + PObjectPointer * fromObj = static_cast(fromType); + PObjectPointer * toObj = static_cast(toType); + //allow narrowing parameters + if(!CanNarrowTo(fromObj->PointedClass(), toObj->PointedClass())) return false; + } + else if(fromType != toType) + { + return false; + } + } + + n = to->ReturnTypes.Size(); + + for(int i = 0; i < n; i++) + { + PType * fromType = from->ReturnTypes[i]; + PType * toType = to->ReturnTypes[i]; + if(fromType->isFunctionPointer() && toType->isFunctionPointer()) + { + if(!AreCompatibleFnPtrs(static_cast(toType), static_cast(fromType))) return false; + } + else if(fromType->isClassPointer() && toType->isClassPointer()) + { + PClassPointer * fromClass = static_cast(fromType); + PClassPointer * toClass = static_cast(toType); + //allow widening returns + if(!CanWidenTo(fromClass->ClassRestriction, toClass->ClassRestriction)) return false; + } + else if(fromType->isObjectPointer() && toType->isObjectPointer()) + { + PObjectPointer * fromObj = static_cast(fromType); + PObjectPointer * toObj = static_cast(toType); + //allow widening returns + if(!CanWidenTo(fromObj->PointedClass(), toObj->PointedClass())) return false; + } + else if(fromType != toType) + { + return false; + } + } + return true; +} + +static bool AreCompatibleFnPtrs(PFunctionPointer * to, PFunctionPointer * from) +{ + if(to->PointedType == TypeVoid) return true; + else if(from->PointedType == TypeVoid) return false; + + PPrototype * toProto = (PPrototype *)to->PointedType; + PPrototype * fromProto = (PPrototype *)from->PointedType; + return + ( FScopeBarrier::CheckSidesForFunctionPointer(from->Scope, to->Scope) + /* + && toProto->ArgumentTypes == fromProto->ArgumentTypes + && toProto->ReturnTypes == fromProto->ReturnTypes + */ + && AreCompatibleFnPtrTypes(toProto, fromProto) + && to->ArgFlags == from->ArgFlags + ); +} + FxExpression *FxFunctionPtrCast::Resolve(FCompileContext &ctx) { CHECKRESOLVED(); SAFE_RESOLVE(basex, ctx); + if (basex->isConstant() && basex->ValueType == TypeRawFunction) + { + FxConstant *val = FxTypeCast::convertRawFunctionToFunctionPointer(basex, ScriptPosition); + if(!val) + { + delete this; + return nullptr; + } + } + if (!(basex->ValueType && basex->ValueType->isFunctionPointer())) { delete this; @@ -11717,21 +11881,20 @@ FxExpression *FxFunctionPtrCast::Resolve(FCompileContext &ctx) auto to = static_cast(ValueType); auto from = static_cast(basex->ValueType); - if(to->PointedType == TypeVoid) - { // no need to do anything for (FunctionPointedType == TypeVoid) + { // nothing to check at compile-time for casts from Function + return this; + } + else if(AreCompatibleFnPtrs(to, from)) + { // no need to do anything for (Function)(...) or compatible casts basex->ValueType = ValueType; auto x = basex; basex = nullptr; delete this; return x; } - else if(from->PointedType == TypeVoid) - { // nothing to check at compile-time for casts from Function - return this; - } else { - // TODO allow narrowing argument types and widening return types via cast, ex.: Function)> to Function)> ScriptPosition.Message(MSG_ERROR, "Cannot cast %s to %s. The types are incompatible.", basex->ValueType->DescriptiveName(), to->DescriptiveName()); delete this; return nullptr; @@ -11746,12 +11909,27 @@ FxExpression *FxFunctionPtrCast::Resolve(FCompileContext &ctx) PFunction *NativeFunctionPointerCast(PFunction *from, const PFunctionPointer *to) { - // TODO allow narrowing argument types and widening return types via cast, ex.: Function)> to Function)> - return (to->PointedType == TypeVoid || (from && - ( from->Variants[0].Proto == static_cast(to->PointedType) - && from->Variants[0].ArgFlags == to->ArgFlags - && FScopeBarrier::CheckSidesForFunctionPointer(FScopeBarrier::SideFromFlags(from->Variants[0].Flags), to->Scope) - ))) ? from : nullptr; + if(to->PointedType == TypeVoid) + { + return from; + } + else if(from && ((from->Variants[0].Flags & (VARF_Virtual | VARF_Action)) == 0) && FScopeBarrier::CheckSidesForFunctionPointer(FScopeBarrier::SideFromFlags(from->Variants[0].Flags), to->Scope)) + { + if(to->ArgFlags.Size() != from->Variants[0].ArgFlags.Size()) return nullptr; + int n = to->ArgFlags.Size(); + for(int i = from->GetImplicitArgs(); i < n; i++) // skip checking flags for implicit self + { + if(from->Variants[0].ArgFlags[i] != to->ArgFlags[i]) + { + return nullptr; + } + } + return AreCompatibleFnPtrTypes(static_cast(to->PointedType), from->Variants[0].Proto) ? from : nullptr; + } + else + { // cannot cast virtual/action functions to anything + return nullptr; + } } DEFINE_ACTION_FUNCTION_NATIVE(DObject, BuiltinFunctionPtrCast, NativeFunctionPointerCast) @@ -11841,6 +12019,17 @@ FxExpression *FxLocalVariableDeclaration::Resolve(FCompileContext &ctx) return nullptr; } SAFE_RESOLVE(Init, ctx); + + if(Init->isConstant() && Init->ValueType == TypeRawFunction) + { + FxConstant *val = FxTypeCast::convertRawFunctionToFunctionPointer(Init, ScriptPosition); + if(!val) + { + delete this; + return nullptr; + } + } + ValueType = Init->ValueType; if (ValueType->RegType == REGT_NIL) { diff --git a/src/common/scripting/backend/codegen.h b/src/common/scripting/backend/codegen.h index 2b2257a7a..6aff3b75d 100644 --- a/src/common/scripting/backend/codegen.h +++ b/src/common/scripting/backend/codegen.h @@ -513,6 +513,13 @@ public: ValueType = value.Type = TypeVMFunction; isresolved = true; } + + FxConstant(PFunction* rawptr, const FScriptPosition& pos) : FxExpression(EFX_Constant, pos) + { + value.pointer = rawptr; + ValueType = value.Type = TypeRawFunction; + isresolved = true; + } FxConstant(const FScriptPosition &pos) : FxExpression(EFX_Constant, pos) { @@ -558,6 +565,8 @@ public: return value; } ExpEmit Emit(VMFunctionBuilder *build); + + friend class FxTypeCast; }; //========================================================================== @@ -736,6 +745,8 @@ public: FxExpression *Resolve(FCompileContext&); ExpEmit Emit(VMFunctionBuilder *build); + + static FxConstant * convertRawFunctionToFunctionPointer(FxExpression * in, FScriptPosition &ScriptPosition); }; //========================================================================== diff --git a/src/common/scripting/core/types.cpp b/src/common/scripting/core/types.cpp index 0b9666e30..a2caeea3d 100644 --- a/src/common/scripting/core/types.cpp +++ b/src/common/scripting/core/types.cpp @@ -75,6 +75,7 @@ PStruct *TypeStringStruct; PStruct* TypeQuaternionStruct; PPointer *TypeNullPtr; PPointer *TypeVoidPtr; +PPointer *TypeRawFunction; PPointer* TypeVMFunction; @@ -323,6 +324,8 @@ void PType::StaticInit() TypeTable.AddType(TypeTextureID = new PTextureID, NAME_TextureID); TypeVoidPtr = NewPointer(TypeVoid, false); + TypeRawFunction = new PPointer; + TypeRawFunction->mDescriptiveName = "Raw Function Pointer"; TypeVMFunction = NewPointer(NewStruct("VMFunction", nullptr, true)); TypeColorStruct = NewStruct("@ColorStruct", nullptr); //This name is intentionally obfuscated so that it cannot be used explicitly. The point of this type is to gain access to the single channels of a color value. TypeStringStruct = NewStruct("Stringstruct", nullptr, true); diff --git a/src/common/scripting/core/types.h b/src/common/scripting/core/types.h index 7fdd8706d..d33840596 100644 --- a/src/common/scripting/core/types.h +++ b/src/common/scripting/core/types.h @@ -727,6 +727,7 @@ extern PPointer *TypeFont; extern PStateLabel *TypeStateLabel; extern PPointer *TypeNullPtr; extern PPointer *TypeVoidPtr; +extern PPointer* TypeRawFunction; extern PPointer* TypeVMFunction; diff --git a/src/common/scripting/core/vmdisasm.cpp b/src/common/scripting/core/vmdisasm.cpp index 86d566985..09524d3b5 100644 --- a/src/common/scripting/core/vmdisasm.cpp +++ b/src/common/scripting/core/vmdisasm.cpp @@ -46,6 +46,9 @@ #define LKP MODE_AP | MODE_BCJOINT | MODE_BCKP #define LFP MODE_AP | MODE_BUNUSED | MODE_CUNUSED + +#define RP MODE_AP | MODE_BUNUSED | MODE_CUNUSED + #define RIRPKI MODE_AI | MODE_BP | MODE_CKI #define RIRPRI MODE_AI | MODE_BP | MODE_CI #define RFRPKI MODE_AF | MODE_BP | MODE_CKI diff --git a/src/common/scripting/jit/jit_call.cpp b/src/common/scripting/jit/jit_call.cpp index d7074edc9..7154d9e27 100644 --- a/src/common/scripting/jit/jit_call.cpp +++ b/src/common/scripting/jit/jit_call.cpp @@ -719,3 +719,8 @@ asmjit::FuncSignature JitCompiler::CreateFuncSignature() signature.init(CallConv::kIdHost, rettype, cachedArgs->Data(), cachedArgs->Size()); return signature; } + +void JitCompiler::EmitNULLCHECK() +{ + EmitNullPointerThrow(A, X_READ_NIL); +} \ No newline at end of file diff --git a/src/common/scripting/vm/vmexec.h b/src/common/scripting/vm/vmexec.h index f1e0cb235..0d5737d25 100644 --- a/src/common/scripting/vm/vmexec.h +++ b/src/common/scripting/vm/vmexec.h @@ -1937,6 +1937,15 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret) CMPJMP(reg.a[B] == konsta[C].v); NEXTOP; + OP(NULLCHECK): + ASSERTA(a); + if (PA == nullptr) + { + ThrowAbortException(X_WRITE_NIL, nullptr); + return 0; + } + NEXTOP; + OP(NOP): NEXTOP; } diff --git a/src/common/scripting/vm/vmops.h b/src/common/scripting/vm/vmops.h index 1782e78b7..9280b746c 100644 --- a/src/common/scripting/vm/vmops.h +++ b/src/common/scripting/vm/vmops.h @@ -289,4 +289,7 @@ xx(SUBA, sub, RIRPRP, NOP, 0, 0) // dA = pB - pC xx(EQA_R, beq, CPRR, NOP, 0, 0) // if ((pB == pkC) != A) then pc++ xx(EQA_K, beq, CPRK, EQA_R, 4, REGT_POINTER) +// Null check +xx(NULLCHECK, nullcheck, RP, NOP, 0, 0) // EmitNullPointerThrow(pA) + #undef xx