diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 09c8d195e..4bd3218df 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -89,6 +89,34 @@ static const FLOP FxFlops[] = { NAME_TanH, FLOP_TANH, [](double v) { return g_tanh(v); } }, }; + +//========================================================================== +// +// [ZZ] Magic methods to be used in vmexec.h for runtime checking of scope +// +//========================================================================== + +// this can be imported in vmexec.h +void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc) +{ + int outerside = FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags); + int innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context" + ThrowAbortException(X_OTHER, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside)); +} +// this can be imported in vmexec.h +void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, PClass* selftype) +{ + int outerside = FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags); + if (outerside == FScopeBarrier::Side_Virtual) + outerside = FScopeBarrier::Side_PlainData; + int innerside = FScopeBarrier::SideFromFlags(calledfunc->Variants[0].Flags); + if (innerside == FScopeBarrier::Side_Virtual) + innerside = FScopeBarrier::SideFromObjectFlags(selftype->ObjectFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) + ThrowAbortException(X_OTHER, "Cannot call %s function %s from %s context", FScopeBarrier::StringFromSide(innerside), calledfunc->SymbolName.GetChars(), FScopeBarrier::StringFromSide(outerside)); +} + //========================================================================== // // FCompileContext @@ -8300,6 +8328,7 @@ FxVMFunctionCall::FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumen ArgList = std::move(args); EmitTail = false; NoVirtual = novirtual; + CallingFunction = nullptr; } //========================================================================== @@ -8373,6 +8402,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) return nullptr; } + CallingFunction = ctx.Function; if (ArgList.Size() > 0) { bool foundvarargs = false; @@ -8580,6 +8610,15 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) ExpEmit selfemit; if (Function->Variants[0].Flags & VARF_Method) { + // [ZZ] + if (Function->Variants[0].Implementation && Function->Variants[0].Implementation->BarrierSide == FScopeBarrier::Side_Virtual) + { + // pass this even before Self, because otherwise we can't silently advance the arguments. + // this is not even implicit arguments. + build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(Function, ATAG_OBJECT)); + build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(CallingFunction, ATAG_OBJECT)); + count += 2; + } assert(Self != nullptr); selfemit = Self->Emit(build); assert((selfemit.RegType == REGT_POINTER) || (selfemit.Fixed && selfemit.Target)); diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index 5dc23dcf7..c9e23c980 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -70,7 +70,8 @@ class FxCompoundStatement; class FxLocalVariableDeclaration; typedef TDeletingArray FArgumentList; -// [ZZ] this is kind of related to compile context as well +// +// [ZZ] this really should be in codegen.h, but vmexec needs to access it struct FScopeBarrier { bool callable; @@ -86,9 +87,9 @@ struct FScopeBarrier enum Side { Side_PlainData = 0, - Side_UI, - Side_Play, - Side_Virtual + Side_UI = 1, + Side_Play = 2, + Side_Virtual = 3 // do NOT change the value }; int sidefrom; int sidelast; @@ -152,7 +153,7 @@ struct FScopeBarrier // this modifies VARF_ flags and sets the side properly. static int ChangeSideInFlags(int flags, int side) { - flags &= ~(VARF_UI | VARF_Play); + flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope); flags |= FlagsFromSide(side); return flags; } @@ -208,28 +209,31 @@ struct FScopeBarrier if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable { readable = false; - readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + if (name) readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); } if (!readable) { writable = false; callable = false; - writeerror.Format("Can't write %s field %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom)); - callerror.Format("Can't call %s function %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom)); + if (name) + { + writeerror.Format("Can't write %s field %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom)); + callerror.Format("Can't call %s function %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } return; } if (writable && (sidefrom != sideto)) // only matching types are writable (plain data implicitly takes context type by default, unless overridden) { writable = false; - writeerror.Format("Can't write %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + if (name) writeerror.Format("Can't write %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); } if (callable && (sidefrom != sideto) && !(flags2 & VARF_ReadOnly)) // readonly on methods is used for plain data stuff that can be called from ui/play context. { callable = false; - callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + if (name) callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); } } }; @@ -1873,6 +1877,7 @@ class FxVMFunctionCall : public FxExpression // for multi assignment int AssignCount = 0; TArray ReturnRegs; + PFunction *CallingFunction; public: FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual); diff --git a/src/scripting/thingdef.cpp b/src/scripting/thingdef.cpp index 5b842ce07..d9eca5fca 100644 --- a/src/scripting/thingdef.cpp +++ b/src/scripting/thingdef.cpp @@ -163,6 +163,10 @@ PFunction *CreateAnonymousFunction(PClass *containingclass, PType *returntype, i // Functions that only get flagged for actors do not need the additional two context parameters. int fflags = (flags& (SUF_OVERLAY | SUF_WEAPON | SUF_ITEM)) ? VARF_Action | VARF_Method : VARF_Method; + // [ZZ] give anonymous functions the scope of their class + // (just give them VARF_Play, whatever) + fflags |= VARF_Play; + rets[0] = returntype != nullptr? returntype : TypeError; // Use TypeError as placeholder if we do not know the return type yet. SetImplicitArgs(&args, &argflags, &argnames, containingclass, fflags, flags); diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index 36b25a40c..ba1db7aa9 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -8,6 +8,11 @@ #include "doomerrors.h" #include "memarena.h" +// [ZZ] there are serious circular references between this and the rest of ZScript code, so it needs to be done like this +// these are used in vmexec.h +void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc); +void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, PClass* selftype); + extern FMemArena ClassDataAllocator; #define MAX_RETURNS 8 // Maximum number of results a function called by script code can return @@ -712,7 +717,7 @@ public: class PPrototype *Proto; - VMFunction(FName name = NAME_None) : Native(false), ImplicitArgs(0), Name(name), Proto(NULL) + VMFunction(FName name = NAME_None) : Native(false), ImplicitArgs(0), Name(name), Proto(NULL) { AllFunctions.Push(this); } diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 6c2eef8a1..d47a3bf48 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -648,13 +648,25 @@ begin: VMReturn returns[MAX_RETURNS]; int numret; + // [ZZ] hax! + b = B; + if (call->BarrierSide == 3) // :( - this is Side_Virtual. Side_Virtual should receive special arguments. + { + PFunction* calledfunc = (PFunction*)(reg.param + f->NumParam - b)[0].a; + PFunction* callingfunc = (PFunction*)(reg.param + f->NumParam - b)[1].a; + DObject* dobj = (DObject*)(reg.param + f->NumParam - b)[2].a; // this is the self pointer. it should be in, since Side_Virtual functions are always non-static methods. + PClass* selftype = dobj->GetClass(); + FScopeBarrier_ValidateCall(calledfunc, callingfunc, selftype); + b -= 2; + } + FillReturns(reg, f, returns, pc+1, C); if (call->Native) { try { VMCycles[0].Unclock(); - numret = static_cast(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, returns, C); + numret = static_cast(call)->NativeCall(reg.param + f->NumParam - b, call->DefaultArgs, b, returns, C); VMCycles[0].Clock(); } catch (CVMAbortException &err) @@ -670,7 +682,7 @@ begin: VMCalls[0]++; VMScriptFunction *script = static_cast(call); VMFrame *newf = stack->AllocFrame(script); - VMFillParams(reg.param + f->NumParam - B, newf, B); + VMFillParams(reg.param + f->NumParam - b, newf, b); try { numret = Exec(stack, script->Code, returns, C); @@ -801,6 +813,9 @@ begin: if (!callingfunc || pcls != callingfunc->OwningClass) ThrowAbortException(X_OTHER, "Cannot instantiate class %s directly", cls->TypeName.GetChars()); } + // [ZZ] validate readonly and between scope construction + if (callingfunc) + FScopeBarrier_ValidateNew(cls, callingfunc); reg.a[a] = cls->CreateNew(); reg.atag[a] = ATAG_OBJECT; NEXTOP; diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index e6be47f81..c19d14b1e 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2401,6 +2401,17 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool sym->Variants[0].Implementation->DefaultArgs = std::move(argdefaults); } + if (sym->Variants[0].Implementation != nullptr) + { + // [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed. + if (varflags & VARF_UI) + sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::Side_UI; + if (varflags & VARF_Play) + sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::Side_Play; + if (varflags & VARF_VirtualScope) + sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::Side_Virtual; + } + PClass *clstype = static_cast(c->Type()); if (varflags & VARF_Virtual) { @@ -2412,13 +2423,6 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (varflags & VARF_Final) sym->Variants[0].Implementation->Final = true; - // [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed. - if (varflags & VARF_UI) - sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::Side_UI; - if (varflags & VARF_Play) - sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::Side_Play; - if (varflags & VARF_VirtualScope) - sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::Side_Virtual; if (varflags & VARF_ReadOnly) sym->Variants[0].Implementation->FuncConst = true;