From b5ab011bb987e6961d5ec84ba4616127602e9431 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 04:07:12 +0200 Subject: [PATCH] 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) --- src/dobjtype.h | 1 + src/sc_man_scanner.re | 1 + src/sc_man_tokens.h | 1 + src/scripting/backend/codegen.cpp | 11 +++++-- src/scripting/backend/codegen.h | 20 ++++++++++++- src/scripting/vm/vm.h | 5 ++-- src/scripting/zscript/zcc-parse.lemon | 1 + src/scripting/zscript/zcc_compile.cpp | 41 ++++++++++++++++----------- src/scripting/zscript/zcc_parser.cpp | 1 + src/scripting/zscript/zcc_parser.h | 1 + wadsrc/static/zscript/base.txt | 2 +- 11 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/dobjtype.h b/src/dobjtype.h index cc023c66f..776d66ce3 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -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 ------------------------------------------------------- diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 560c2b077..f4aac0cb5 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -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); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index b98c1108b..fdcb72229 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -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'") diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 5a77b4a01..09c8d195e 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -7684,6 +7684,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) auto id = static_cast(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(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(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()); diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index cbf02f954..5dc23dcf7 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -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; diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index bcdf8d17b..36b25a40c 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -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; diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 930f2a9ca..2f412f5cf 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -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; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 52899e44f..e6be47f81 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -1273,7 +1273,7 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray &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; diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index d77028d60..9f1117193 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -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); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 20ebcabae..7cc41318d 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -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 diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 208023b54..394450f22 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -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() {}