Dynamic virtualscope checking. May yet be buggy.

This commit is contained in:
ZZYZX 2017-02-18 06:27:28 +02:00
parent b5ab011bb9
commit e0ae0fdb2e
6 changed files with 92 additions and 20 deletions

View file

@ -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));

View file

@ -70,7 +70,8 @@ class FxCompoundStatement;
class FxLocalVariableDeclaration;
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
{
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<ExpEmit> ReturnRegs;
PFunction *CallingFunction;
public:
FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual);

View file

@ -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);

View file

@ -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);
}

View file

@ -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<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();
}
catch (CVMAbortException &err)
@ -670,7 +682,7 @@ begin:
VMCalls[0]++;
VMScriptFunction *script = static_cast<VMScriptFunction *>(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;

View file

@ -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<PClass *>(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;