mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2025-02-09 06:20:49 +00:00
Dynamic virtualscope checking. May yet be buggy.
This commit is contained in:
parent
b5ab011bb9
commit
e0ae0fdb2e
6 changed files with 92 additions and 20 deletions
|
@ -89,6 +89,34 @@ static const FLOP FxFlops[] =
|
||||||
{ NAME_TanH, FLOP_TANH, [](double v) { return g_tanh(v); } },
|
{ 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
|
// FCompileContext
|
||||||
|
@ -8300,6 +8328,7 @@ FxVMFunctionCall::FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumen
|
||||||
ArgList = std::move(args);
|
ArgList = std::move(args);
|
||||||
EmitTail = false;
|
EmitTail = false;
|
||||||
NoVirtual = novirtual;
|
NoVirtual = novirtual;
|
||||||
|
CallingFunction = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
@ -8373,6 +8402,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CallingFunction = ctx.Function;
|
||||||
if (ArgList.Size() > 0)
|
if (ArgList.Size() > 0)
|
||||||
{
|
{
|
||||||
bool foundvarargs = false;
|
bool foundvarargs = false;
|
||||||
|
@ -8580,6 +8610,15 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
|
||||||
ExpEmit selfemit;
|
ExpEmit selfemit;
|
||||||
if (Function->Variants[0].Flags & VARF_Method)
|
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);
|
assert(Self != nullptr);
|
||||||
selfemit = Self->Emit(build);
|
selfemit = Self->Emit(build);
|
||||||
assert((selfemit.RegType == REGT_POINTER) || (selfemit.Fixed && selfemit.Target));
|
assert((selfemit.RegType == REGT_POINTER) || (selfemit.Fixed && selfemit.Target));
|
||||||
|
|
|
@ -70,7 +70,8 @@ class FxCompoundStatement;
|
||||||
class FxLocalVariableDeclaration;
|
class FxLocalVariableDeclaration;
|
||||||
typedef TDeletingArray<FxExpression*> FArgumentList;
|
typedef TDeletingArray<FxExpression*> 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
|
struct FScopeBarrier
|
||||||
{
|
{
|
||||||
bool callable;
|
bool callable;
|
||||||
|
@ -86,9 +87,9 @@ struct FScopeBarrier
|
||||||
enum Side
|
enum Side
|
||||||
{
|
{
|
||||||
Side_PlainData = 0,
|
Side_PlainData = 0,
|
||||||
Side_UI,
|
Side_UI = 1,
|
||||||
Side_Play,
|
Side_Play = 2,
|
||||||
Side_Virtual
|
Side_Virtual = 3 // do NOT change the value
|
||||||
};
|
};
|
||||||
int sidefrom;
|
int sidefrom;
|
||||||
int sidelast;
|
int sidelast;
|
||||||
|
@ -152,7 +153,7 @@ struct FScopeBarrier
|
||||||
// this modifies VARF_ flags and sets the side properly.
|
// this modifies VARF_ flags and sets the side properly.
|
||||||
static int ChangeSideInFlags(int flags, int side)
|
static int ChangeSideInFlags(int flags, int side)
|
||||||
{
|
{
|
||||||
flags &= ~(VARF_UI | VARF_Play);
|
flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope);
|
||||||
flags |= FlagsFromSide(side);
|
flags |= FlagsFromSide(side);
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
@ -208,28 +209,31 @@ struct FScopeBarrier
|
||||||
if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable
|
if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable
|
||||||
{
|
{
|
||||||
readable = false;
|
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)
|
if (!readable)
|
||||||
{
|
{
|
||||||
writable = false;
|
writable = false;
|
||||||
callable = false;
|
callable = false;
|
||||||
writeerror.Format("Can't write %s field %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom));
|
if (name)
|
||||||
callerror.Format("Can't call %s function %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom));
|
{
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writable && (sidefrom != sideto)) // only matching types are writable (plain data implicitly takes context type by default, unless overridden)
|
if (writable && (sidefrom != sideto)) // only matching types are writable (plain data implicitly takes context type by default, unless overridden)
|
||||||
{
|
{
|
||||||
writable = false;
|
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.
|
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;
|
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
|
// for multi assignment
|
||||||
int AssignCount = 0;
|
int AssignCount = 0;
|
||||||
TArray<ExpEmit> ReturnRegs;
|
TArray<ExpEmit> ReturnRegs;
|
||||||
|
PFunction *CallingFunction;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual);
|
FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual);
|
||||||
|
|
|
@ -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.
|
// 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;
|
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.
|
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);
|
SetImplicitArgs(&args, &argflags, &argnames, containingclass, fflags, flags);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
#include "doomerrors.h"
|
#include "doomerrors.h"
|
||||||
#include "memarena.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;
|
extern FMemArena ClassDataAllocator;
|
||||||
|
|
||||||
#define MAX_RETURNS 8 // Maximum number of results a function called by script code can return
|
#define MAX_RETURNS 8 // Maximum number of results a function called by script code can return
|
||||||
|
@ -712,7 +717,7 @@ public:
|
||||||
|
|
||||||
class PPrototype *Proto;
|
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);
|
AllFunctions.Push(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -648,13 +648,25 @@ begin:
|
||||||
VMReturn returns[MAX_RETURNS];
|
VMReturn returns[MAX_RETURNS];
|
||||||
int numret;
|
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);
|
FillReturns(reg, f, returns, pc+1, C);
|
||||||
if (call->Native)
|
if (call->Native)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
VMCycles[0].Unclock();
|
VMCycles[0].Unclock();
|
||||||
numret = static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, returns, C);
|
numret = static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - b, call->DefaultArgs, b, returns, C);
|
||||||
VMCycles[0].Clock();
|
VMCycles[0].Clock();
|
||||||
}
|
}
|
||||||
catch (CVMAbortException &err)
|
catch (CVMAbortException &err)
|
||||||
|
@ -670,7 +682,7 @@ begin:
|
||||||
VMCalls[0]++;
|
VMCalls[0]++;
|
||||||
VMScriptFunction *script = static_cast<VMScriptFunction *>(call);
|
VMScriptFunction *script = static_cast<VMScriptFunction *>(call);
|
||||||
VMFrame *newf = stack->AllocFrame(script);
|
VMFrame *newf = stack->AllocFrame(script);
|
||||||
VMFillParams(reg.param + f->NumParam - B, newf, B);
|
VMFillParams(reg.param + f->NumParam - b, newf, b);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
numret = Exec(stack, script->Code, returns, C);
|
numret = Exec(stack, script->Code, returns, C);
|
||||||
|
@ -801,6 +813,9 @@ begin:
|
||||||
if (!callingfunc || pcls != callingfunc->OwningClass)
|
if (!callingfunc || pcls != callingfunc->OwningClass)
|
||||||
ThrowAbortException(X_OTHER, "Cannot instantiate class %s directly", cls->TypeName.GetChars());
|
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.a[a] = cls->CreateNew();
|
||||||
reg.atag[a] = ATAG_OBJECT;
|
reg.atag[a] = ATAG_OBJECT;
|
||||||
NEXTOP;
|
NEXTOP;
|
||||||
|
|
|
@ -2401,6 +2401,17 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
|
||||||
sym->Variants[0].Implementation->DefaultArgs = std::move(argdefaults);
|
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<PClass *>(c->Type());
|
PClass *clstype = static_cast<PClass *>(c->Type());
|
||||||
if (varflags & VARF_Virtual)
|
if (varflags & VARF_Virtual)
|
||||||
{
|
{
|
||||||
|
@ -2412,13 +2423,6 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
|
||||||
|
|
||||||
if (varflags & VARF_Final)
|
if (varflags & VARF_Final)
|
||||||
sym->Variants[0].Implementation->Final = true;
|
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)
|
if (varflags & VARF_ReadOnly)
|
||||||
sym->Variants[0].Implementation->FuncConst = true;
|
sym->Variants[0].Implementation->FuncConst = true;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue