Static virtualscope checking. This is possible, because virtualscope can't produce false positives (data readable for everyone), only false negatives (which are handled at runtime later)

This commit is contained in:
ZZYZX 2017-02-18 04:07:12 +02:00
parent 12aa18a92b
commit b5ab011bb9
11 changed files with 61 additions and 24 deletions

View file

@ -39,6 +39,7 @@ enum
VARF_VarArg = (1<<19), // [ZZ] vararg: don't typecheck values after ... in function signature
VARF_UI = (1<<20), // [ZZ] ui: object is ui-scope only (can't modify playsim)
VARF_Play = (1<<21), // [ZZ] play: object is playsim-scope only (can't access ui)
VARF_VirtualScope = (1<<22), // [ZZ] virtualscope: object should use the scope of the particular class it's being used with (methods only)
};
// An action function -------------------------------------------------------

View file

@ -174,6 +174,7 @@ std2:
'ui' { RET(TK_UI); }
'play' { RET(TK_Play); }
'clearscope' { RET(TK_ClearScope); }
'virtualscope' { RET(TK_VirtualScope); }
'super' { RET(TK_Super); }
'global' { RET(TK_Global); }
'stop' { RET(TK_Stop); }

View file

@ -116,6 +116,7 @@ xx(TK_NoNew, "'nonew'")
xx(TK_UI, "'ui'")
xx(TK_Play, "'play'")
xx(TK_ClearScope, "'clearscope'")
xx(TK_VirtualScope, "'virtualscope'")
xx(TK_Override, "'override'")
xx(TK_Super, "'super'")
xx(TK_Null, "'null'")

View file

@ -7684,6 +7684,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
auto id = static_cast<FxIdentifier *>(Self)->Identifier;
// If the left side is a class name for a static member function call it needs to be resolved manually
// because the resulting value type would cause problems in nearly every other place where identifiers are being used.
// [ZZ] substitute ccls for String internal type.
if (id == NAME_String) ccls = TypeStringStruct;
else ccls = FindStructType(id, ctx);
if (ccls != nullptr) static_cast<FxIdentifier *>(Self)->noglobal = true;
@ -7695,7 +7696,6 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (ccls != nullptr)
{
// [ZZ] substitute ccls for String internal type.
if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast<PClass *>(ccls)->bExported)
{
cls = ccls;
@ -8029,10 +8029,17 @@ isresolved:
if (ctx.Function)
outerflags = ctx.Function->Variants[0].Flags;
int innerflags = afd->Variants[0].Flags;
int innerside = FScopeBarrier::SideFromFlags(innerflags);
// [ZZ] check this at compile time. this would work for most legit cases.
if (innerside == FScopeBarrier::Side_Virtual)
{
innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags);
innerflags = FScopeBarrier::FlagsFromSide(innerside);
}
if (Self->ExprType == EFX_StructMember)
{
FxStructMember* pmember = (FxStructMember*)Self;
if (FScopeBarrier::SideFromFlags(innerflags) == FScopeBarrier::Side_PlainData)
if (innerside == FScopeBarrier::Side_PlainData)
innerflags = FScopeBarrier::ChangeSideInFlags(innerflags, pmember->BarrierSide);
}
FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars());

View file

@ -87,7 +87,8 @@ struct FScopeBarrier
{
Side_PlainData = 0,
Side_UI,
Side_Play
Side_Play,
Side_Virtual
};
int sidefrom;
int sidelast;
@ -99,6 +100,18 @@ struct FScopeBarrier
return Side_UI;
if (flags & VARF_Play)
return Side_Play;
if (flags & VARF_VirtualScope)
return Side_Virtual;
return Side_PlainData;
}
// same as above, but from object flags
static int SideFromObjectFlags(int flags)
{
if (flags & OF_UI)
return Side_UI;
if (flags & OF_Play)
return Side_Play;
return Side_PlainData;
}
@ -111,6 +124,8 @@ struct FScopeBarrier
return VARF_Play;
case Side_UI:
return VARF_UI;
case Side_Virtual:
return VARF_VirtualScope;
default:
return 0;
}
@ -127,6 +142,8 @@ struct FScopeBarrier
return "ui";
case Side_Play:
return "play";
case Side_Virtual:
return "virtual"; // should not happen!
default:
return "unknown";
}
@ -170,6 +187,7 @@ struct FScopeBarrier
return;
// we aren't interested in any other flags
// - update: including VARF_VirtualScope. inside the function itself, we treat it as if it's PlainData.
flags1 &= VARF_UI | VARF_Play;
flags2 &= VARF_UI | VARF_Play | VARF_ReadOnly;

View file

@ -702,9 +702,8 @@ public:
bool Native;
bool Final = false; // cannot be overridden
bool Unsafe = false; // Contains references to class fields that are unsafe for psp and item state calls.
bool ScopeUI = false; // [ZZ] 'ui' method
bool ScopePlay = false; // [ZZ] 'play' method
bool FuncConst = false; // [ZZ] const qualifier for methods - these can be called on readonly
bool FuncConst = false; // [ZZ] readonly function
int BarrierSide = -1; // [ZZ] FScopeBarrier::Side
BYTE ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action
unsigned VirtualIndex = ~0u;
FName Name;

View file

@ -1007,6 +1007,7 @@ decl_flag(X) ::= VARARG(T). { X.Int = ZCC_VarArg; X.SourceLoc = T.SourceLoc;
decl_flag(X) ::= UI(T). { X.Int = ZCC_UIFlag; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= PLAY(T). { X.Int = ZCC_Play; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= CLEARSCOPE(T). { X.Int = ZCC_ClearScope; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= VIRTUALSCOPE(T). { X.Int = ZCC_VirtualScope; X.SourceLoc = T.SourceLoc; }
func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); }
func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; }

View file

@ -1273,7 +1273,7 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray<ZCC_Property *> &Proper
FString ZCCCompiler::FlagsToString(uint32_t flags)
{
const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "nonew", "ui", "play", "clearscope" };
const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "nonew", "ui", "play", "clearscope", "virtualscope" };
FString build;
for (size_t i = 0; i < countof(flagnames); i++)
@ -2130,11 +2130,13 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
if (f->Flags & ZCC_FuncConst)
varflags = (varflags&~(VARF_Play | VARF_UI)); // const implies clearscope. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag.
if (f->Flags & ZCC_UIFlag)
varflags = (varflags&~VARF_Play) | VARF_UI;
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI);
if (f->Flags & ZCC_Play)
varflags = (varflags&~VARF_UI) | VARF_Play;
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play);
if (f->Flags & ZCC_ClearScope)
varflags = (varflags&~(VARF_Play | VARF_UI));
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData);
if (f->Flags & ZCC_VirtualScope)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Virtual);
// [ZZ] supporting const self for actors is quite a cumbersome task because there's no concept of a const pointer (?)
// either way, it doesn't make sense, because you can call any method on a readonly class instance.
@ -2194,14 +2196,20 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
Error(f, "'Const' on a static method is not supported");
}
// you can't have a const function belonging to either ui or play.
// const is intended for plain data to signify that you can call a method on readonly variable.
if ((f->Flags & ZCC_FuncConst) && (f->Flags & (ZCC_UIFlag | ZCC_Play)))
// [ZZ] neither this
if ((varflags&(VARF_VirtualScope | VARF_Method)) == VARF_VirtualScope) // non-method virtualscope function
{
Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(f->Flags&(ZCC_FuncConst | ZCC_UIFlag | ZCC_Play)).GetChars(), FName(f->Name).GetChars());
Error(f, "'VirtualScope' on a static method is not supported");
}
static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope };
// you can't have a const function belonging to either ui or play.
// const is intended for plain data to signify that you can call a method on readonly variable.
if ((f->Flags & ZCC_FuncConst) && (f->Flags & (ZCC_UIFlag | ZCC_Play | ZCC_VirtualScope)))
{
Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(f->Flags&(ZCC_FuncConst | ZCC_UIFlag | ZCC_Play | ZCC_VirtualScope)).GetChars(), FName(f->Name).GetChars());
}
static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope, ZCC_VirtualScope };
excludeflags = 0;
fc = 0;
for (int i = 0; i < countof(excludescope); i++)
@ -2406,9 +2414,11 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
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->ScopeUI = true;
sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::Side_UI;
if (varflags & VARF_Play)
sym->Variants[0].Implementation->ScopePlay = true;
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;
@ -2430,7 +2440,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
Error(f, "Attempt to override final function %s", FName(f->Name).GetChars());
}
// you can't change ui/play/clearscope for a virtual method.
if (f->Flags & (ZCC_UIFlag|ZCC_Play|ZCC_ClearScope))
if (f->Flags & (ZCC_UIFlag|ZCC_Play|ZCC_ClearScope|ZCC_VirtualScope))
{
Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars());
}
@ -2440,11 +2450,8 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
Error(f, "Attempt to change const qualifier for virtual function %s", FName(f->Name).GetChars());
}
// inherit scope of original function if override not specified
if (sym->Variants[0].Implementation->ScopeUI = oldfunc->ScopeUI)
sym->Variants[0].Flags = (sym->Variants[0].Flags&~(VARF_Play)) | VARF_UI;
else if (sym->Variants[0].Implementation->ScopePlay = oldfunc->ScopePlay)
sym->Variants[0].Flags = (sym->Variants[0].Flags&~(VARF_UI)) | VARF_Play;
else sym->Variants[0].Flags = (sym->Variants[0].Flags&~(VARF_UI | VARF_Play));
sym->Variants[0].Implementation->BarrierSide = oldfunc->BarrierSide;
sym->Variants[0].Flags = FScopeBarrier::ChangeSideInFlags(sym->Variants[0].Flags, oldfunc->BarrierSide);
// inherit const from original function
if (sym->Variants[0].Implementation->FuncConst = oldfunc->FuncConst)
sym->Variants[0].Flags |= VARF_ReadOnly;

View file

@ -140,6 +140,7 @@ static void InitTokenMap()
TOKENDEF (TK_UI, ZCC_UI);
TOKENDEF (TK_Play, ZCC_PLAY);
TOKENDEF (TK_ClearScope, ZCC_CLEARSCOPE);
TOKENDEF (TK_VirtualScope, ZCC_VIRTUALSCOPE);
TOKENDEF (TK_NoNew, ZCC_NONEW);
TOKENDEF (TK_Override, ZCC_OVERRIDE);
TOKENDEF (TK_Final, ZCC_FINAL);

View file

@ -41,6 +41,7 @@ enum
ZCC_UIFlag = 1 << 18, // there's also token called ZCC_UI
ZCC_Play = 1 << 19,
ZCC_ClearScope = 1 << 20,
ZCC_VirtualScope = 1 << 21,
};
// Function parameter modifiers

View file

@ -329,7 +329,7 @@ class Object native
native static uint MSTime();
native Name GetClassName();
native void Destroy();
native virtualscope void Destroy();
// This does not call into the native method of the same name to avoid problems with objects that get garbage collected late on shutdown.
virtual void OnDestroy() {}