From 3577e4eddcaf4708734b9e857a5580a0e72aa157 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Thu, 16 Feb 2017 02:14:49 +0200 Subject: [PATCH 01/48] Fixed possible nullptr dereferencing on c->Type() == nullptr --- src/scripting/zscript/zcc_compile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index a240c1973..bd73fa06c 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -598,11 +598,11 @@ void ZCCCompiler::CreateClassTypes() c->cls->Type = nullptr; } } + if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName()); if (c->cls->Flags & ZCC_Abstract) { c->Type()->ObjectFlags |= OF_Abstract; } - if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName()); c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); From 363990a105ca7088a257b4563fdd2442b952305b Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Thu, 16 Feb 2017 02:31:20 +0200 Subject: [PATCH 02/48] Class abstractness should be inherited --- src/scripting/thingdef.cpp | 2 +- src/scripting/zscript/zcc_compile.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/scripting/thingdef.cpp b/src/scripting/thingdef.cpp index d0b7df449..5b842ce07 100644 --- a/src/scripting/thingdef.cpp +++ b/src/scripting/thingdef.cpp @@ -194,7 +194,7 @@ PFunction *FindClassMemberFunction(PStruct *selfcls, PStruct *funccls, FName nam { sc.Message(MSG_ERROR, "%s is not a member function of %s", name.GetChars(), selfcls->TypeName.GetChars()); } - else if (funcsym->Variants[0].Flags & VARF_Private && symtable != &funccls->Symbols) + else if ((funcsym->Variants[0].Flags & VARF_Private) && symtable != &funccls->Symbols) { // private access is only allowed if the symbol table belongs to the class in which the current function is being defined. sc.Message(MSG_ERROR, "%s is declared private and not accessible", symbol->SymbolName.GetChars()); diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index bd73fa06c..187e2f341 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -599,10 +599,9 @@ void ZCCCompiler::CreateClassTypes() } } if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName()); - if (c->cls->Flags & ZCC_Abstract) - { + // [ZZ] if parent class is abstract, this one should be abstract as well - otherwise we can subclass Actor and be able to new() our subclass + if ((c->cls->Flags & ZCC_Abstract) || (parent && parent->ObjectFlags & OF_Abstract)) c->Type()->ObjectFlags |= OF_Abstract; - } c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); From 7d3663500f5f9f47aebacb3dc3be274a99263bc3 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Thu, 16 Feb 2017 02:39:49 +0200 Subject: [PATCH 03/48] Disallow creation of abstract classes outside of their own class (this is so that modders can create their own factory methods, not just for native) --- src/dobject.h | 2 +- src/scripting/backend/codegen.cpp | 17 +++++++++++++++-- src/scripting/backend/codegen.h | 1 + src/scripting/vm/vmexec.h | 3 ++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/dobject.h b/src/dobject.h index ce4714010..01ee76fb2 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -205,7 +205,7 @@ enum EObjectFlags OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function - OF_Abstract = 1 << 14, // Marks a class that cannot be created with CreateNew + OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function }; template class TObjPtr; diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index db7d7425f..551378d8b 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -5023,6 +5023,7 @@ FxNew::FxNew(FxExpression *v) { val = new FxClassTypeCast(NewClassPointer(RUNTIME_CLASS(DObject)), v, false); ValueType = NewPointer(RUNTIME_CLASS(DObject)); + CallingClass = nullptr; } //========================================================================== @@ -5047,6 +5048,7 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) CHECKRESOLVED(); SAFE_RESOLVE(val, ctx); + CallingClass = (PClass*)ctx.Class; if (!val->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer))) { ScriptPosition.Message(MSG_ERROR, "Class type expected"); @@ -5058,6 +5060,7 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) auto cls = static_cast(static_cast(val)->GetValue().GetPointer()); ValueType = NewPointer(cls); } + return this; } @@ -5072,7 +5075,7 @@ ExpEmit FxNew::Emit(VMFunctionBuilder *build) ExpEmit from = val->Emit(build); from.Free(build); ExpEmit to(build, REGT_POINTER); - build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum); + build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum, build->GetConstantAddress(CallingClass, ATAG_OBJECT)); return to; } @@ -7527,8 +7530,18 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx) break; case NAME_New: - if (CheckArgSize(MethodName, ArgList, 1, 1, ScriptPosition)) + if (CheckArgSize(MethodName, ArgList, 0, 1, ScriptPosition)) { + // [ZZ] allow implicit new() call to mean "create current class instance" + if (!ArgList.Size() && !ctx.Class->IsKindOf(RUNTIME_CLASS(PClass))) + { + ScriptPosition.Message(MSG_ERROR, "Cannot use implicit new() in a struct"); + delete this; + return nullptr; + } + else if (!ArgList.Size()) + ArgList.Push(new FxConstant((PClass*)ctx.Class, NewClassPointer((PClass*)ctx.Class), ScriptPosition)); + func = new FxNew(ArgList[0]); ArgList[0] = nullptr; } diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index abbcbd54e..90c474170 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -1208,6 +1208,7 @@ private: class FxNew : public FxExpression { FxExpression *val; + PClass *CallingClass; public: diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 8da2c4d8d..9f93af802 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -787,7 +787,8 @@ begin: { b = B; PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v); - if (cls->ObjectFlags & OF_Abstract) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); + PClass *callingcls = (PClass*)konsta[C].o; // [ZZ] due to how this is set, it's always const + if ((cls->ObjectFlags & OF_Abstract) && callingcls != cls) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); reg.a[a] = cls->CreateNew(); reg.atag[a] = ATAG_OBJECT; NEXTOP; From 0819dd8d89d2120a70894f27476c9c0105159d28 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Thu, 16 Feb 2017 04:14:21 +0200 Subject: [PATCH 04/48] Actually pass PFunction to OP_NEW --- src/scripting/backend/codegen.cpp | 6 +++--- src/scripting/backend/codegen.h | 2 +- src/scripting/vm/vmexec.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 551378d8b..77ac9498c 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -5023,7 +5023,7 @@ FxNew::FxNew(FxExpression *v) { val = new FxClassTypeCast(NewClassPointer(RUNTIME_CLASS(DObject)), v, false); ValueType = NewPointer(RUNTIME_CLASS(DObject)); - CallingClass = nullptr; + CallingFunction = nullptr; } //========================================================================== @@ -5048,7 +5048,7 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) CHECKRESOLVED(); SAFE_RESOLVE(val, ctx); - CallingClass = (PClass*)ctx.Class; + CallingFunction = ctx.Function; if (!val->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer))) { ScriptPosition.Message(MSG_ERROR, "Class type expected"); @@ -5075,7 +5075,7 @@ ExpEmit FxNew::Emit(VMFunctionBuilder *build) ExpEmit from = val->Emit(build); from.Free(build); ExpEmit to(build, REGT_POINTER); - build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum, build->GetConstantAddress(CallingClass, ATAG_OBJECT)); + build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum, build->GetConstantAddress(CallingFunction, ATAG_OBJECT)); return to; } diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index 90c474170..3c4377716 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -1208,7 +1208,7 @@ private: class FxNew : public FxExpression { FxExpression *val; - PClass *CallingClass; + PFunction *CallingFunction; public: diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 9f93af802..01ad0111a 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -787,8 +787,8 @@ begin: { b = B; PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v); - PClass *callingcls = (PClass*)konsta[C].o; // [ZZ] due to how this is set, it's always const - if ((cls->ObjectFlags & OF_Abstract) && callingcls != cls) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); + PFunction *callingfunc = (PFunction*)konsta[C].o; // [ZZ] due to how this is set, it's always const + if ((cls->ObjectFlags & OF_Abstract) && (!callingfunc || callingfunc->OwningClass != cls)) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); reg.a[a] = cls->CreateNew(); reg.atag[a] = ATAG_OBJECT; NEXTOP; From 0803faf5961b49be168c933d64089bffaa4d488f Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Thu, 16 Feb 2017 04:22:14 +0200 Subject: [PATCH 05/48] Compile time check for abstract class instantiation to reduce programming errors ASAP --- src/scripting/backend/codegen.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 77ac9498c..1f2b038f1 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -5058,6 +5058,13 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) if (val->isConstant()) { auto cls = static_cast(static_cast(val)->GetValue().GetPointer()); + if ((cls->ObjectFlags & OF_Abstract) && cls != ctx.Class) + { + ScriptPosition.Message(MSG_ERROR, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); + delete this; + return nullptr; + } + ValueType = NewPointer(cls); } From 5e5d0d3e57e661786db3e7e8390926f82a17ca5c Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Thu, 16 Feb 2017 12:40:09 +0200 Subject: [PATCH 06/48] Using separate keyword 'nonew' as reverse abstract; nonew is inherited, and nonew class can only be created from the first nonew class in the hierarchy --- src/dobject.h | 3 ++- src/sc_man_scanner.re | 1 + src/sc_man_tokens.h | 1 + src/scripting/backend/codegen.cpp | 17 +++++++++++++++-- src/scripting/vm/vmexec.h | 14 +++++++++++++- src/scripting/zscript/zcc-parse.lemon | 1 + src/scripting/zscript/zcc_compile.cpp | 6 ++++-- src/scripting/zscript/zcc_parser.cpp | 1 + src/scripting/zscript/zcc_parser.h | 3 ++- 9 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/dobject.h b/src/dobject.h index 01ee76fb2..6605cea5a 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -205,7 +205,8 @@ enum EObjectFlags OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function - OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function + OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function at all + OF_NoNew = 1 << 15, // Marks a class that can only be created with new() in the exact class that has this keyword }; template class TObjPtr; diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index d4a1254d6..57a3940ef 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -170,6 +170,7 @@ std2: 'virtual' { RET(TK_Virtual); } 'override' { RET(TK_Override); } 'vararg' { RET(TK_VarArg); } + 'nonew' { RET(TK_NoNew); } '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 0227dffa9..b278de38e 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -112,6 +112,7 @@ xx(TK_Optional, "'optional'") xx(TK_Export, "'expert'") xx(TK_Virtual, "'virtual'") xx(TK_VarArg, "'vararg'") +xx(TK_NoNew, "'nonew'") 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 1f2b038f1..fc18f481b 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -5058,13 +5058,26 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) if (val->isConstant()) { auto cls = static_cast(static_cast(val)->GetValue().GetPointer()); - if ((cls->ObjectFlags & OF_Abstract) && cls != ctx.Class) + if (cls->ObjectFlags & OF_Abstract) { - ScriptPosition.Message(MSG_ERROR, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); delete this; return nullptr; } + if (cls->ObjectFlags & OF_NoNew) + { + PClass* pcls = cls; + while (pcls && pcls->ParentClass && (pcls->ParentClass->ObjectFlags & OF_NoNew)) + pcls = pcls->ParentClass; + if (pcls != ctx.Class) + { + ScriptPosition.Message(MSG_ERROR, "Cannot instantiate class %s directly", cls->TypeName.GetChars()); + delete this; + return nullptr; + } + } + ValueType = NewPointer(cls); } diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 01ad0111a..6c2eef8a1 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -788,7 +788,19 @@ begin: b = B; PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v); PFunction *callingfunc = (PFunction*)konsta[C].o; // [ZZ] due to how this is set, it's always const - if ((cls->ObjectFlags & OF_Abstract) && (!callingfunc || callingfunc->OwningClass != cls)) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); + if (cls->ObjectFlags & OF_Abstract) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); + if (cls->ObjectFlags & OF_NoNew) + { + // trace to the first nonew class in the hierarchy. + // compare that class to the context class. + // if not matching, disallow creation. + // this ensures that only the root class can have a static factory method that can also create instances of subclasses via new(). + PClass* pcls = cls; + while (pcls && pcls->ParentClass && (pcls->ParentClass->ObjectFlags & OF_NoNew)) + pcls = pcls->ParentClass; + if (!callingfunc || pcls != callingfunc->OwningClass) + ThrowAbortException(X_OTHER, "Cannot instantiate class %s directly", cls->TypeName.GetChars()); + } reg.a[a] = cls->CreateNew(); reg.atag[a] = ATAG_OBJECT; NEXTOP; diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 6d3e8a856..f158bff2d 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -212,6 +212,7 @@ class_ancestry(X) ::= COLON dottable_id(A). { X = A; /*X-overwrites-A*/ } %type class_flags{ClassFlagsBlock} class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; } class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) NONEW. { X.Flags = A.Flags | ZCC_NoNew; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 187e2f341..cb93a5965 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -599,9 +599,11 @@ void ZCCCompiler::CreateClassTypes() } } if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName()); - // [ZZ] if parent class is abstract, this one should be abstract as well - otherwise we can subclass Actor and be able to new() our subclass - if ((c->cls->Flags & ZCC_Abstract) || (parent && parent->ObjectFlags & OF_Abstract)) + if (c->cls->Flags & ZCC_Abstract) c->Type()->ObjectFlags |= OF_Abstract; + // [ZZ] inherit nonew keyword + if (c->cls->Flags & ZCC_NoNew || (parent && parent->ObjectFlags & OF_NoNew)) + c->Type()->ObjectFlags |= OF_NoNew; c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 501a7b2ea..4bfb8d0f2 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -137,6 +137,7 @@ static void InitTokenMap() TOKENDEF (TK_Latent, ZCC_LATENT); TOKENDEF (TK_Virtual, ZCC_VIRTUAL); TOKENDEF (TK_VarArg, ZCC_VARARG); + TOKENDEF (TK_NoNew, ZCC_NONEW); TOKENDEF (TK_Override, ZCC_OVERRIDE); TOKENDEF (TK_Final, ZCC_FINAL); TOKENDEF (TK_Meta, ZCC_META); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 95bafd54f..12a5be06b 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -36,7 +36,8 @@ enum ZCC_Virtual = 1 << 13, ZCC_Override = 1 << 14, ZCC_Transient = 1 << 15, - ZCC_VarArg = 1 << 16 + ZCC_VarArg = 1 << 16, + ZCC_NoNew = 1 << 17, }; // Function parameter modifiers From 4fe9c7d8c874fa506714c3e45aa523e3e3b682ce Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 17:58:16 +0200 Subject: [PATCH 07/48] Added parsing of ui, play, allowui and const qualifiers --- src/dobject.h | 2 + src/dobjtype.h | 2 + src/sc_man_scanner.re | 3 + src/sc_man_tokens.h | 3 + src/scripting/backend/codegen.h | 124 +++++++++++++++++++++ src/scripting/vm/vm.h | 3 + src/scripting/zscript/zcc-parse.lemon | 11 +- src/scripting/zscript/zcc_compile.cpp | 152 +++++++++++++++++++++++--- src/scripting/zscript/zcc_parser.cpp | 3 + src/scripting/zscript/zcc_parser.h | 3 + 10 files changed, 287 insertions(+), 19 deletions(-) diff --git a/src/dobject.h b/src/dobject.h index 6605cea5a..b5f7ef8c4 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -207,6 +207,8 @@ enum EObjectFlags OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function at all OF_NoNew = 1 << 15, // Marks a class that can only be created with new() in the exact class that has this keyword + OF_UI = 1 << 16, // Marks a class that defaults to VARF_UI for it's fields/methods + OF_Play = 1 << 17, // Marks a class that defaults to VARF_Play for it's fields/methods }; template class TObjPtr; diff --git a/src/dobjtype.h b/src/dobjtype.h index 0151e6c11..cc023c66f 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -37,6 +37,8 @@ enum VARF_Transient = (1<<17), // don't auto serialize field. VARF_Meta = (1<<18), // static class data (by necessity read only.) 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) }; // An action function ------------------------------------------------------- diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 57a3940ef..69dcd4397 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -171,6 +171,9 @@ std2: 'override' { RET(TK_Override); } 'vararg' { RET(TK_VarArg); } 'nonew' { RET(TK_NoNew); } + 'ui' { RET(TK_UI); } + 'play' { RET(TK_Play); } + 'allowui' { RET(TK_AllowUI); } '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 b278de38e..1dc205504 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -113,6 +113,9 @@ xx(TK_Export, "'expert'") xx(TK_Virtual, "'virtual'") xx(TK_VarArg, "'vararg'") xx(TK_NoNew, "'nonew'") +xx(TK_UI, "'ui'") +xx(TK_Play, "'play'") +xx(TK_AllowUI, "'allowui'") xx(TK_Override, "'override'") xx(TK_Super, "'super'") xx(TK_Null, "'null'") diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index 3c4377716..429cfd186 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -70,6 +70,130 @@ class FxCompoundStatement; class FxLocalVariableDeclaration; typedef TDeletingArray FArgumentList; +// [ZZ] this is kind of related to compile context as well +struct FPlayUIBarrier +{ + bool callable; + bool readable; + bool writable; + + // this is the error message + FString callerror; + FString readerror; + FString writeerror; + + // this is used to make the error message. + enum Side + { + Side_PlainData = 0, + Side_UI, + Side_Play + }; + int sidefrom; + int sidelast; + + // Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found. + int SideFromFlags(int flags) + { + if (flags & VARF_UI) + return Side_UI; + if (flags & VARF_Play) + return Side_Play; + return Side_PlainData; + } + + // used for errors + const char* StringFromSide(int side) + { + switch (side) + { + case Side_PlainData: + return "data"; + case Side_UI: + return "ui"; + case Side_Play: + return "play"; + default: + return "unknown"; + } + } + + FPlayUIBarrier() + { + sidefrom = -1; + sidelast = -1; + callable = true; + readable = true; + writable = true; + } + + FPlayUIBarrier(int flags1, int flags2, const char* name) + { + sidefrom = -1; + sidelast = -1; + callable = true; + readable = true; + writable = true; + + AddFlags(flags1, flags2, name); + } + + // AddFlags modifies ALLOWED actions by flags1->flags2. + // This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed. + // This struct is used so that the logic is in a single place. + void AddFlags(int flags1, int flags2, const char* name) + { + // note: if it's already non-readable, don't even try advancing + if (!readable) + return; + + // we aren't interested in any other flags + flags1 &= VARF_UI | VARF_Play; + flags2 &= VARF_UI | VARF_Play | VARF_ReadOnly; + + if (sidefrom < 0) sidefrom = SideFromFlags(flags1); + if (sidelast < 0) sidelast = sidefrom; + + // flags1 = what's trying to access + // flags2 = what's being accessed + + int sideto = SideFromFlags(flags2); + + // plain data inherits whatever scope modifiers that context or field container has. + // i.e. play String bla; is play, and all non-specified methods/fields inside it are play as well. + if (sideto != Side_PlainData) + sidelast = sideto; + else sideto = sidelast; + + 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), 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)); + 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 (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 field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } + } +}; + struct FCompileContext { FxExpression *ControlStmt = nullptr; diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index aa191c34e..bcdf8d17b 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -702,6 +702,9 @@ 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 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 f158bff2d..c801950b5 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -214,6 +214,9 @@ class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; } class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) NONEW. { X.Flags = A.Flags | ZCC_NoNew; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) ALLOWUI. { X.Flags = A.Flags | ZCC_AllowUI; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } /*----- Dottable Identifier -----*/ @@ -327,7 +330,10 @@ struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body %type struct_flags{ClassFlagsBlock} struct_flags(X) ::= . { X.Flags = 0; } -struct_flags(X) ::= NATIVE. { X.Flags = ZCC_Native; } +struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; } +struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; } +struct_flags(X) ::= struct_flags(A) ALLOWUI. { X.Flags = A.Flags | ZCC_AllowUI; } +struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; } opt_struct_body(X) ::= . { X = NULL; } opt_struct_body(X) ::= struct_body(X). @@ -1000,6 +1006,9 @@ decl_flag(X) ::= DEPRECATED(T). { X.Int = ZCC_Deprecated; X.SourceLoc = T.Sourc decl_flag(X) ::= VIRTUAL(T). { X.Int = ZCC_Virtual; X.SourceLoc = T.SourceLoc; } decl_flag(X) ::= OVERRIDE(T). { X.Int = ZCC_Override; X.SourceLoc = T.SourceLoc; } 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) ::= ALLOWUI(T). { X.Int = ZCC_AllowUI; 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 cb93a5965..81764a2df 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -496,6 +496,16 @@ void ZCCCompiler::CreateStructTypes() { s->strct->Type = NewStruct(s->NodeName(), outer); } + + if ((s->strct->Flags & (ZCC_UIFlag | ZCC_Play)) == (ZCC_UIFlag | ZCC_Play)) + { + Error(s->strct, "Struct %s has incompatible flags", s->NodeName().GetChars()); + } + + if (s->strct->Flags & ZCC_UIFlag) + s->Type()->ObjectFlags |= OF_UI; + if (s->strct->Flags & ZCC_Play) + s->Type()->ObjectFlags |= OF_Play; s->strct->Symbol = new PSymbolType(s->NodeName(), s->Type()); syms->AddSymbol(s->strct->Symbol); @@ -602,8 +612,24 @@ void ZCCCompiler::CreateClassTypes() if (c->cls->Flags & ZCC_Abstract) c->Type()->ObjectFlags |= OF_Abstract; // [ZZ] inherit nonew keyword - if (c->cls->Flags & ZCC_NoNew || (parent && parent->ObjectFlags & OF_NoNew)) + if (c->cls->Flags & ZCC_NoNew || (parent->ObjectFlags & OF_NoNew)) c->Type()->ObjectFlags |= OF_NoNew; + // + static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + int incompatiblecnt = 0; + for (int k = 0; k < countof(incompatible); k++) + if (incompatible[k] & c->cls->Flags) incompatiblecnt++; + + if (incompatiblecnt > 1) + { + Error(c->cls, "Class %s has incompatible flags", c->NodeName().GetChars()); + } + + if (c->cls->Flags & ZCC_UIFlag || ((parent->ObjectFlags & OF_UI) && !(c->cls->Flags & ZCC_AllowUI))) + c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_Play) | OF_UI; + if (c->cls->Flags & ZCC_Play || ((parent->ObjectFlags & OF_Play) && !(c->cls->Flags & ZCC_AllowUI))) + c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_UI) | OF_Play; + c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); @@ -1063,12 +1089,39 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel if (field->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated; if (field->Flags & ZCC_ReadOnly) varflags |= VARF_ReadOnly; if (field->Flags & ZCC_Transient) varflags |= VARF_Transient; + if (type->ObjectFlags & OF_UI) + varflags |= VARF_UI; + if (type->ObjectFlags & OF_Play) + varflags |= VARF_Play; + if (field->Flags & ZCC_UIFlag) + varflags = (varflags&~VARF_Play) | VARF_UI; + if (field->Flags & ZCC_Play) + varflags = (varflags&~VARF_UI) | VARF_Play; + if (field->Flags & ZCC_AllowUI) + varflags = (varflags&~(VARF_UI | VARF_Play)); if (field->Flags & ZCC_Native) { varflags |= VARF_Native | VARF_Transient; } + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + int excludeflags = 0; + int fc = 0; + for (int i = 0; i < countof(excludescope); i++) + { + if (field->Flags & excludescope[i]) + { + fc++; + excludeflags |= excludescope[i]; + } + } + if (fc > 1) + { + Error(field, "Invalid combination of scope qualifiers %s on field %s", FlagsToString(excludeflags).GetChars(), FName(field->Names->Name).GetChars()); + varflags &= ~(VARF_UI | VARF_Play); // make plain data + } + if (field->Flags & ZCC_Meta) { varflags |= VARF_Meta | VARF_Static | VARF_ReadOnly; // metadata implies readonly @@ -1101,11 +1154,11 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel fd = FindField(querytype, FName(name->Name).GetChars()); if (fd == nullptr) { - Error(field, "The member variable '%s.%s' has not been exported from the executable.", type->TypeName.GetChars(), FName(name->Name).GetChars()); + Error(field, "The member variable '%s.%s' has not been exported from the executable", type->TypeName.GetChars(), FName(name->Name).GetChars()); } else if (thisfieldtype->Size != fd->FieldSize && fd->BitValue == 0) { - Error(field, "The member variable '%s.%s' has mismatching sizes in internal and external declaration. (Internal = %d, External = %d)", type->TypeName.GetChars(), FName(name->Name).GetChars(), fd->FieldSize, thisfieldtype->Size); + Error(field, "The member variable '%s.%s' has mismatching sizes in internal and external declaration (Internal = %d, External = %d)", type->TypeName.GetChars(), FName(name->Name).GetChars(), fd->FieldSize, thisfieldtype->Size); } // Q: Should we check alignment, too? A mismatch may be an indicator for bad assumptions. else @@ -1117,7 +1170,7 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel } else if (hasnativechildren) { - Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change.", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars()); + Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars()); } else { @@ -1212,7 +1265,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", "funcconst", "abstract", "extension", "virtual", "override", "transient", "vararg" }; + const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "nonew", "ui", "play", "allowui" }; FString build; for (size_t i = 0; i < countof(flagnames); i++) @@ -2014,7 +2067,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } while (t != f->Type); } - int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_FuncConst | ZCC_Abstract; + int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract; if (f->Flags & notallowed) { @@ -2061,6 +2114,20 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual; if (f->Flags & ZCC_Override) varflags |= VARF_Override; if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg; + if (f->Flags & ZCC_FuncConst) varflags |= VARF_ReadOnly; // FuncConst method is internally marked as VARF_ReadOnly + if (c->Type()->ObjectFlags & OF_UI) + varflags |= VARF_UI; + if (c->Type()->ObjectFlags & OF_Play) + varflags |= VARF_Play; + if (f->Flags & ZCC_FuncConst) + varflags = (varflags&~(VARF_Play | VARF_UI)); // const implies allowui. 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; + if (f->Flags & ZCC_Play) + varflags = (varflags&~VARF_UI) | VARF_Play; + if (f->Flags & ZCC_AllowUI) + varflags = (varflags&~(VARF_Play | VARF_UI)); + if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) { Error(f, "'VarArg' can only be used with native methods"); @@ -2086,36 +2153,57 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final, implicitargs = 0; // Static implies Final. - if (varflags & VARF_Override) varflags &= ~VARF_Virtual; // allow 'virtual override'. // Only one of these flags may be used. static int exclude[] = { ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static }; - static const char * print[] = { "virtual", "override", "action", "static" }; + int excludeflags = 0; int fc = 0; - FString build; - for (int i = 0; i < 4; i++) + for (int i = 0; i < countof(exclude); i++) { if (f->Flags & exclude[i]) { fc++; - if (build.Len() > 0) build += ", "; - build += print[i]; + excludeflags |= exclude[i]; } } if (fc > 1) { - Error(f, "Invalid combination of qualifiers %s on function %s.", FName(f->Name).GetChars(), build.GetChars()); + Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); varflags |= VARF_Method; } if (varflags & VARF_Override) varflags |= VARF_Virtual; // Now that the flags are checked, make all override functions virtual as well. + // 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))) + { + Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(f->Flags&(ZCC_FuncConst | ZCC_UIFlag | ZCC_Play)).GetChars(), FName(f->Name).GetChars()); + } + + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + excludeflags = 0; + fc = 0; + for (int i = 0; i < countof(excludescope); i++) + { + if (f->Flags & excludescope[i]) + { + fc++; + excludeflags |= excludescope[i]; + } + } + if (fc > 1) + { + Error(f, "Invalid combination of scope qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); + varflags &= ~(VARF_UI | VARF_Play); // make plain data + } + if (f->Flags & ZCC_Native) { varflags |= VARF_Native; afd = FindFunction(c->Type(), FName(f->Name).GetChars()); if (afd == nullptr) { - Error(f, "The function '%s.%s' has not been exported from the executable.", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); + Error(f, "The function '%s.%s' has not been exported from the executable", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); } else { @@ -2229,7 +2317,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } else if (hasoptionals) { - Error(p, "All arguments after the first optional one need also be optional."); + Error(p, "All arguments after the first optional one need also be optional"); } // TBD: disallow certain types? For now, let everything pass that isn't an array. args.Push(type); @@ -2289,13 +2377,20 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool { if (sym->Variants[0].Implementation == nullptr) { - Error(f, "Virtual function %s.%s not present.", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); + Error(f, "Virtual function %s.%s not present", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); return; } + 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 (f->Flags & ZCC_UIFlag) // only direct specification here (varflags can also have owning class scope applied, we don't want that) + sym->Variants[0].Implementation->ScopeUI = true; + if (f->Flags & ZCC_Play) // only direct specification here + sym->Variants[0].Implementation->ScopePlay = true; + if (varflags & VARF_ReadOnly) + sym->Variants[0].Implementation->FuncConst = true; + if (forclass) { int vindex = clstype->FindVirtualIndex(sym->SymbolName, sym->Variants[0].Proto); @@ -2313,6 +2408,27 @@ 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/allowui for a virtual method. + if ((oldfunc->ScopePlay != sym->Variants[0].Implementation->ScopePlay) || + (oldfunc->ScopeUI != sym->Variants[0].Implementation->ScopeUI)) + { + Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars()); + } + // you can't change const qualifier for a virtual method + if (oldfunc->FuncConst != (varflags & VARF_ReadOnly)) + { + Error(f, "Attempt to change const qualifier for virtual function %s", FName(f->Name).GetChars()); + } + // inherit scope of original function + 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)); + // inherit const from original function + if (sym->Variants[0].Implementation->FuncConst = oldfunc->FuncConst) + sym->Variants[0].Flags |= VARF_ReadOnly; + clstype->Virtuals[vindex] = sym->Variants[0].Implementation; sym->Variants[0].Implementation->VirtualIndex = vindex; } diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 4bfb8d0f2..2586cb8c5 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -137,6 +137,9 @@ static void InitTokenMap() TOKENDEF (TK_Latent, ZCC_LATENT); TOKENDEF (TK_Virtual, ZCC_VIRTUAL); TOKENDEF (TK_VarArg, ZCC_VARARG); + TOKENDEF (TK_UI, ZCC_UI); + TOKENDEF (TK_Play, ZCC_PLAY); + TOKENDEF (TK_AllowUI, ZCC_ALLOWUI); 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 12a5be06b..72f81c1cc 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -38,6 +38,9 @@ enum ZCC_Transient = 1 << 15, ZCC_VarArg = 1 << 16, ZCC_NoNew = 1 << 17, + ZCC_UIFlag = 1 << 18, // there's also token called ZCC_UI + ZCC_Play = 1 << 19, + ZCC_AllowUI = 1 << 20, }; // Function parameter modifiers From 2b20abff028b330a99131998969f21b76d343c63 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 18:01:39 +0200 Subject: [PATCH 08/48] Fixed consistency of checks --- src/scripting/zscript/zcc_compile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 81764a2df..1418377c0 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2415,7 +2415,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars()); } // you can't change const qualifier for a virtual method - if (oldfunc->FuncConst != (varflags & VARF_ReadOnly)) + if (oldfunc->FuncConst != sym->Variants[0].Implementation->FuncConst) { Error(f, "Attempt to change const qualifier for virtual function %s", FName(f->Name).GetChars()); } From 0f031c5f22a95dfc3bbbe56d63b2a21d5ba9ccfd Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 18:24:01 +0200 Subject: [PATCH 09/48] Renamed 'allowui' to 'clearscope'. 'allowui' doesn't reflect the real meaning of the keyword which clears implicit 'play' or 'ui' inherited from parent class (for classes) or owning type (for methods/fields) --- src/sc_man_scanner.re | 2 +- src/sc_man_tokens.h | 2 +- src/scripting/backend/codegen.h | 6 +++--- src/scripting/zscript/zcc-parse.lemon | 6 +++--- src/scripting/zscript/zcc_compile.cpp | 20 ++++++++++---------- src/scripting/zscript/zcc_parser.cpp | 2 +- src/scripting/zscript/zcc_parser.h | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 69dcd4397..560c2b077 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -173,7 +173,7 @@ std2: 'nonew' { RET(TK_NoNew); } 'ui' { RET(TK_UI); } 'play' { RET(TK_Play); } - 'allowui' { RET(TK_AllowUI); } + 'clearscope' { RET(TK_ClearScope); } '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 1dc205504..b98c1108b 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -115,7 +115,7 @@ xx(TK_VarArg, "'vararg'") xx(TK_NoNew, "'nonew'") xx(TK_UI, "'ui'") xx(TK_Play, "'play'") -xx(TK_AllowUI, "'allowui'") +xx(TK_ClearScope, "'clearscope'") xx(TK_Override, "'override'") xx(TK_Super, "'super'") xx(TK_Null, "'null'") diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index 429cfd186..ecdeaec15 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -71,7 +71,7 @@ class FxLocalVariableDeclaration; typedef TDeletingArray FArgumentList; // [ZZ] this is kind of related to compile context as well -struct FPlayUIBarrier +struct FScopeBarrier { bool callable; bool readable; @@ -118,7 +118,7 @@ struct FPlayUIBarrier } } - FPlayUIBarrier() + FScopeBarrier() { sidefrom = -1; sidelast = -1; @@ -127,7 +127,7 @@ struct FPlayUIBarrier writable = true; } - FPlayUIBarrier(int flags1, int flags2, const char* name) + FScopeBarrier(int flags1, int flags2, const char* name) { sidefrom = -1; sidelast = -1; diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index c801950b5..59924b634 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -216,7 +216,7 @@ class_flags(X) ::= class_flags(A) NONEW. { X.Flags = A.Flags | ZCC_NoNew; X. class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; } -class_flags(X) ::= class_flags(A) ALLOWUI. { X.Flags = A.Flags | ZCC_AllowUI; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) CLEARSCOPE. { X.Flags = A.Flags | ZCC_ClearScope; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } /*----- Dottable Identifier -----*/ @@ -332,7 +332,7 @@ struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body struct_flags(X) ::= . { X.Flags = 0; } struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; } struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; } -struct_flags(X) ::= struct_flags(A) ALLOWUI. { X.Flags = A.Flags | ZCC_AllowUI; } +struct_flags(X) ::= struct_flags(A) CLEARSCOPE. { X.Flags = A.Flags | ZCC_ClearScope; } struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; } opt_struct_body(X) ::= . { X = NULL; } @@ -1008,7 +1008,7 @@ decl_flag(X) ::= OVERRIDE(T). { X.Int = ZCC_Override; X.SourceLoc = T.SourceLoc 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) ::= ALLOWUI(T). { X.Int = ZCC_AllowUI; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= CLEARSCOPE(T). { X.Int = ZCC_ClearScope; 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 1418377c0..2d2f84492 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -615,7 +615,7 @@ void ZCCCompiler::CreateClassTypes() if (c->cls->Flags & ZCC_NoNew || (parent->ObjectFlags & OF_NoNew)) c->Type()->ObjectFlags |= OF_NoNew; // - static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; int incompatiblecnt = 0; for (int k = 0; k < countof(incompatible); k++) if (incompatible[k] & c->cls->Flags) incompatiblecnt++; @@ -625,9 +625,9 @@ void ZCCCompiler::CreateClassTypes() Error(c->cls, "Class %s has incompatible flags", c->NodeName().GetChars()); } - if (c->cls->Flags & ZCC_UIFlag || ((parent->ObjectFlags & OF_UI) && !(c->cls->Flags & ZCC_AllowUI))) + if (c->cls->Flags & ZCC_UIFlag || ((parent->ObjectFlags & OF_UI) && !(c->cls->Flags & ZCC_ClearScope))) c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_Play) | OF_UI; - if (c->cls->Flags & ZCC_Play || ((parent->ObjectFlags & OF_Play) && !(c->cls->Flags & ZCC_AllowUI))) + if (c->cls->Flags & ZCC_Play || ((parent->ObjectFlags & OF_Play) && !(c->cls->Flags & ZCC_ClearScope))) c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_UI) | OF_Play; c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) @@ -1097,7 +1097,7 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel varflags = (varflags&~VARF_Play) | VARF_UI; if (field->Flags & ZCC_Play) varflags = (varflags&~VARF_UI) | VARF_Play; - if (field->Flags & ZCC_AllowUI) + if (field->Flags & ZCC_ClearScope) varflags = (varflags&~(VARF_UI | VARF_Play)); if (field->Flags & ZCC_Native) @@ -1105,7 +1105,7 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel varflags |= VARF_Native | VARF_Transient; } - static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; int excludeflags = 0; int fc = 0; for (int i = 0; i < countof(excludescope); i++) @@ -1265,7 +1265,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", "allowui" }; + const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "nonew", "ui", "play", "clearscope" }; FString build; for (size_t i = 0; i < countof(flagnames); i++) @@ -2120,12 +2120,12 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (c->Type()->ObjectFlags & OF_Play) varflags |= VARF_Play; if (f->Flags & ZCC_FuncConst) - varflags = (varflags&~(VARF_Play | VARF_UI)); // const implies allowui. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag. + 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; if (f->Flags & ZCC_Play) varflags = (varflags&~VARF_UI) | VARF_Play; - if (f->Flags & ZCC_AllowUI) + if (f->Flags & ZCC_ClearScope) varflags = (varflags&~(VARF_Play | VARF_UI)); if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) @@ -2180,7 +2180,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(f->Flags&(ZCC_FuncConst | ZCC_UIFlag | ZCC_Play)).GetChars(), FName(f->Name).GetChars()); } - static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; excludeflags = 0; fc = 0; for (int i = 0; i < countof(excludescope); i++) @@ -2408,7 +2408,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/allowui for a virtual method. + // you can't change ui/play/clearscope for a virtual method. if ((oldfunc->ScopePlay != sym->Variants[0].Implementation->ScopePlay) || (oldfunc->ScopeUI != sym->Variants[0].Implementation->ScopeUI)) { diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 2586cb8c5..d77028d60 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -139,7 +139,7 @@ static void InitTokenMap() TOKENDEF (TK_VarArg, ZCC_VARARG); TOKENDEF (TK_UI, ZCC_UI); TOKENDEF (TK_Play, ZCC_PLAY); - TOKENDEF (TK_AllowUI, ZCC_ALLOWUI); + TOKENDEF (TK_ClearScope, ZCC_CLEARSCOPE); 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 72f81c1cc..20ebcabae 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -40,7 +40,7 @@ enum ZCC_NoNew = 1 << 17, ZCC_UIFlag = 1 << 18, // there's also token called ZCC_UI ZCC_Play = 1 << 19, - ZCC_AllowUI = 1 << 20, + ZCC_ClearScope = 1 << 20, }; // Function parameter modifiers From 338e676e73910cf82f3f995972e9a5a098103f8b Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 19:25:29 +0200 Subject: [PATCH 10/48] Allow calling const methods on readonly structs --- src/scripting/backend/codegen.cpp | 40 +++++++++++++++++-------------- src/scripting/backend/codegen.h | 2 +- wadsrc/static/zscript/base.txt | 8 +++---- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index fc18f481b..1ea2565cb 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -167,7 +167,8 @@ void FCompileContext::CheckReturn(PPrototype *proto, FScriptPosition &pos) } } -bool FCompileContext::CheckReadOnly(int flags) +// [ZZ] I find it really dumb that something called CheckReadOnly returns false for readonly. renamed. +bool FCompileContext::CheckWritable(int flags) { if (!(flags & VARF_ReadOnly)) return false; if (!(flags & VARF_InternalAccess)) return true; @@ -6202,7 +6203,7 @@ FxExpression *FxLocalVariable::Resolve(FCompileContext &ctx) bool FxLocalVariable::RequestAddress(FCompileContext &ctx, bool *writable) { AddressRequested = true; - if (writable != nullptr) *writable = !ctx.CheckReadOnly(Variable->VarFlags); + if (writable != nullptr) *writable = !ctx.CheckWritable(Variable->VarFlags); return true; } @@ -6420,7 +6421,7 @@ FxGlobalVariable::FxGlobalVariable(PField* mem, const FScriptPosition &pos) bool FxGlobalVariable::RequestAddress(FCompileContext &ctx, bool *writable) { AddressRequested = true; - if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags); + if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags); return true; } @@ -6610,7 +6611,7 @@ FxStackVariable::~FxStackVariable() bool FxStackVariable::RequestAddress(FCompileContext &ctx, bool *writable) { AddressRequested = true; - if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags); + if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags); return true; } @@ -6708,7 +6709,7 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) return false; } AddressRequested = true; - if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckReadOnly(membervar->Flags) && + if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckWritable(membervar->Flags) && (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->IsConst)); return true; } @@ -7618,6 +7619,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) PStruct *cls; bool staticonly = false; bool novirtual = false; + bool isreadonly = false; PStruct *ccls = nullptr; @@ -7912,7 +7914,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) auto x = new FxGetParentClass(Self); return x->Resolve(ctx); } - + if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && !Self->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer))) { auto ptype = static_cast(Self->ValueType)->PointedType; @@ -7943,18 +7945,11 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PStruct))) { bool writable; - if (Self->RequestAddress(ctx, &writable) && writable) - { - cls = static_cast(Self->ValueType); - Self->ValueType = NewPointer(Self->ValueType); - } - else - { - // Cannot be made writable so we cannot use its methods. - ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars()); - delete this; - return nullptr; - } + + // [ZZ] allow const method to be called on a readonly struct + isreadonly = !(Self->RequestAddress(ctx, &writable) && writable); + cls = static_cast(Self->ValueType); + Self->ValueType = NewPointer(Self->ValueType); } else { @@ -7981,6 +7976,15 @@ isresolved: return nullptr; } + if (isreadonly && !(afd->Variants[0].Flags & VARF_ReadOnly)) + { + // Cannot be made writable so we cannot use its methods. + // [ZZ] Why this esoteric message? + ScriptPosition.Message(MSG_ERROR, "Readonly struct on left hand side of %s not allowed\n", MethodName.GetChars()); + delete this; + return nullptr; + } + if (staticonly && (afd->Variants[0].Flags & VARF_Method)) { if (!novirtual || !(afd->Variants[0].Flags & VARF_Virtual)) diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index ecdeaec15..c8120f4d1 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -219,7 +219,7 @@ struct FCompileContext void HandleJumps(int token, FxExpression *handler); void CheckReturn(PPrototype *proto, FScriptPosition &pos); - bool CheckReadOnly(int flags); + bool CheckWritable(int flags); FxLocalVariableDeclaration *FindLocalVariable(FName name); }; diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index bffee91f0..208023b54 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -579,10 +579,10 @@ struct StringStruct native native vararg void AppendFormat(String fmt, ...); native void Replace(String pattern, String replacement); - native String Mid(int pos = 0, int len = 2147483647); - native int Len(); - native String CharAt(int pos); - native int CharCodeAt(int pos); + native String Mid(int pos = 0, int len = 2147483647) const; + native int Len() const; + native String CharAt(int pos) const; + native int CharCodeAt(int pos) const; } class Floor : Thinker native From 195ae24dcb23c1d432838dde3fc32d05e7059cff Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 19:54:59 +0200 Subject: [PATCH 11/48] const method cannot write to self --- src/scripting/backend/codegen.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 1ea2565cb..f69205997 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6709,8 +6709,14 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) return false; } AddressRequested = true; - if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckWritable(membervar->Flags) && - (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->IsConst)); + if (writable != nullptr) + { + *writable = (AddressWritable && !ctx.CheckWritable(membervar->Flags) && + (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->IsConst)); + // [ZZ] self in a const function is not writable. + if ((classx->ExprType == EFX_Self) && (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_ReadOnly))) + *writable = false; + } return true; } From 496b2a74ce324405f1e96eb9c1d32565dcfee4ad Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 20:04:40 +0200 Subject: [PATCH 12/48] Disallow const qualifier for classes --- src/scripting/zscript/zcc_compile.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 2d2f84492..0c77b46f8 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2128,6 +2128,13 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (f->Flags & ZCC_ClearScope) varflags = (varflags&~(VARF_Play | VARF_UI)); + // [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. + if ((f->Flags & ZCC_FuncConst) && !(c->Type()->IsKindOf(RUNTIME_CLASS(PStruct)))) + { + Error(f, "'Const' on a method can only be used in structs"); + } + if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) { Error(f, "'VarArg' can only be used with native methods"); @@ -2173,6 +2180,12 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } if (varflags & VARF_Override) varflags |= VARF_Virtual; // Now that the flags are checked, make all override functions virtual as well. + // [ZZ] this doesn't make sense either. + if ((varflags&(VARF_ReadOnly | VARF_Method)) == VARF_ReadOnly) // non-method const function + { + 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))) From ecd4f5a32d9a585f95dd8012ae93726806d364ac Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 20:25:11 +0200 Subject: [PATCH 13/48] Removed supposedly debug value added in commit b0f3121bec0e9b265b11f002f93a18f8ffa829d2 --- src/scripting/backend/codegen.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index f69205997..4d30e8459 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6120,11 +6120,6 @@ FxExpression *FxMemberIdentifier::Resolve(FCompileContext& ctx) SAFE_RESOLVE(Object, ctx); - if (Identifier == FName("allmap")) - { - int a = 2; - } - // check for class or struct constants if the left side is a type name. if (Object->ValueType == TypeError) { From 8b0dee6f66a6a5e15630c44b9b8ac30a4aeb9f64 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 21:59:03 +0200 Subject: [PATCH 14/48] My am dumb: PStruct is a base for PClass --- src/scripting/zscript/zcc_compile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 0c77b46f8..3afb80ff3 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2130,7 +2130,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool // [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. - if ((f->Flags & ZCC_FuncConst) && !(c->Type()->IsKindOf(RUNTIME_CLASS(PStruct)))) + if ((f->Flags & ZCC_FuncConst) && (c->Type()->IsKindOf(RUNTIME_CLASS(PClass)))) { Error(f, "'Const' on a method can only be used in structs"); } From a7a4406bb14ec2b24c753ae38472bdd933faee95 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 22:41:04 +0200 Subject: [PATCH 15/48] Supposedly implemented a write check between ui/play/data --- src/scripting/backend/codegen.cpp | 32 ++++++++++++++++++++++++++++--- src/scripting/backend/codegen.h | 19 ++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 4d30e8459..635488f26 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6706,11 +6706,28 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) AddressRequested = true; if (writable != nullptr) { - *writable = (AddressWritable && !ctx.CheckWritable(membervar->Flags) && + // [ZZ] original check. + bool bWritable = (AddressWritable && !ctx.CheckWritable(membervar->Flags) && (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->IsConst)); // [ZZ] self in a const function is not writable. - if ((classx->ExprType == EFX_Self) && (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_ReadOnly))) - *writable = false; + if (bWritable) // don't do complex checks on early fail + { + if ((classx->ExprType == EFX_Self) && (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_ReadOnly))) + bWritable = false; + } + // [ZZ] plain data "inherits" scope of whatever it was defined in. + if (bWritable) // don't do complex checks on early fail + { + if (ctx.Function && FString(ctx.Function->SymbolName) == FString("DrawPowerup")) + Printf("field type = %d\n", BarrierSide); + int outerflags = 0; + if (ctx.Function) + outerflags = ctx.Function->Variants[0].Flags; + FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), membervar->SymbolName.GetChars()); + if (!scopeBarrier.writable) + bWritable = false; + } + *writable = bWritable; } return true; } @@ -6741,6 +6758,15 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) return x->Resolve(ctx); } + // [ZZ] support magic + BarrierSide = FScopeBarrier::SideFromFlags(membervar->Flags); + if (classx->ExprType == EFX_StructMember || classx->ExprType == EFX_ClassMember) + { + FxStructMember* pmember = (FxStructMember*)classx; + if (BarrierSide == FScopeBarrier::Side_PlainData && pmember) + BarrierSide = pmember->BarrierSide; + } + if (classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))) { PPointer *ptrtype = dyn_cast(classx->ValueType); diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index c8120f4d1..ee43a0052 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -93,7 +93,7 @@ struct FScopeBarrier int sidelast; // Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found. - int SideFromFlags(int flags) + static int SideFromFlags(int flags) { if (flags & VARF_UI) return Side_UI; @@ -102,8 +102,22 @@ struct FScopeBarrier return Side_PlainData; } + // + static int FlagsFromSide(int side) + { + switch (side) + { + case Side_Play: + return VARF_Play; + case Side_UI: + return VARF_UI; + default: + return 0; + } + } + // used for errors - const char* StringFromSide(int side) + static const char* StringFromSide(int side) { switch (side) { @@ -1451,6 +1465,7 @@ public: PField *membervar; bool AddressRequested = false; bool AddressWritable = true; + int BarrierSide = -1; // [ZZ] some magic FxMemberBase(EFxType type, PField *f, const FScriptPosition &p); }; From a2f3d8511de0120cd569ee17c5163c0c2fbe6564 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 22:46:18 +0200 Subject: [PATCH 16/48] Implemented a read check between ui/play/data fields with a meaningful error --- src/scripting/backend/codegen.cpp | 21 +++++++++++++++------ src/scripting/backend/codegen.h | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 635488f26..f53612b41 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6718,8 +6718,6 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) // [ZZ] plain data "inherits" scope of whatever it was defined in. if (bWritable) // don't do complex checks on early fail { - if (ctx.Function && FString(ctx.Function->SymbolName) == FString("DrawPowerup")) - Printf("field type = %d\n", BarrierSide); int outerflags = 0; if (ctx.Function) outerflags = ctx.Function->Variants[0].Flags; @@ -6748,7 +6746,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) if (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(AActor))) { - ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type."); + ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type"); delete this; return nullptr; } @@ -6759,7 +6757,18 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) } // [ZZ] support magic - BarrierSide = FScopeBarrier::SideFromFlags(membervar->Flags); + int outerflags = 0; + if (ctx.Function) + outerflags = ctx.Function->Variants[0].Flags; + FScopeBarrier scopeBarrier(outerflags, membervar->Flags, membervar->SymbolName.GetChars()); + if (!scopeBarrier.readable) + { + ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.readerror.GetChars()); + delete this; + return nullptr; + } + + BarrierSide = scopeBarrier.sidelast; if (classx->ExprType == EFX_StructMember || classx->ExprType == EFX_ClassMember) { FxStructMember* pmember = (FxStructMember*)classx; @@ -6772,7 +6781,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) PPointer *ptrtype = dyn_cast(classx->ValueType); if (ptrtype == nullptr || !ptrtype->PointedType->IsKindOf(RUNTIME_CLASS(PStruct))) { - ScriptPosition.Message(MSG_ERROR, "Member variable requires a struct or class object."); + ScriptPosition.Message(MSG_ERROR, "Member variable requires a struct or class object"); delete this; return nullptr; } @@ -6826,7 +6835,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) { if (!(classx->RequestAddress(ctx, &AddressWritable))) { - ScriptPosition.Message(MSG_ERROR, "unable to dereference left side of %s", membervar->SymbolName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Unable to dereference left side of %s", membervar->SymbolName.GetChars()); delete this; return nullptr; } diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index ee43a0052..f9291274d 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -182,7 +182,7 @@ 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), StringFromSide(sidefrom)); + readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); } if (!readable) From 3056570ea9b5e0dbccc66d009b5ba3d3f34719d2 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 23:02:43 +0200 Subject: [PATCH 17/48] Implemented a call check between ui/play/data --- src/scripting/backend/codegen.cpp | 46 ++++++++++++++++++++++--------- src/scripting/backend/codegen.h | 10 ++++++- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index f53612b41..d452fedd9 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -7662,7 +7662,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) if (ctx.Class == nullptr) { // There's no way that a member function call can resolve to a constant so abort right away. - ScriptPosition.Message(MSG_ERROR, "Expression is not constant."); + ScriptPosition.Message(MSG_ERROR, "Expression is not constant"); delete this; return nullptr; } @@ -7671,7 +7671,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (a == nullptr) { - ScriptPosition.Message(MSG_ERROR, "Empty function argument."); + ScriptPosition.Message(MSG_ERROR, "Empty function argument"); delete this; return nullptr; } @@ -7762,7 +7762,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7806,7 +7806,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7900,7 +7900,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7960,7 +7960,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7973,7 +7973,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) } else { - ScriptPosition.Message(MSG_ERROR, "Left hand side of %s must point to a class object\n", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Left hand side of %s must point to a class object", MethodName.GetChars()); delete this; return nullptr; } @@ -7989,7 +7989,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) } else { - ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s", MethodName.GetChars()); delete this; return nullptr; } @@ -8007,7 +8007,7 @@ isresolved: if (afd == nullptr) { - ScriptPosition.Message(MSG_ERROR, "Unknown function %s\n", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Unknown function %s", MethodName.GetChars()); delete this; return nullptr; } @@ -8016,11 +8016,31 @@ isresolved: { // Cannot be made writable so we cannot use its methods. // [ZZ] Why this esoteric message? - ScriptPosition.Message(MSG_ERROR, "Readonly struct on left hand side of %s not allowed\n", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Readonly struct on left hand side of %s not allowed", MethodName.GetChars()); delete this; return nullptr; } + // [ZZ] if self is a struct or a class member, check if it's valid to call this function at all. + // implement more magic + if (Self->ExprType == EFX_ClassMember || Self->ExprType == EFX_StructMember) + { + FxStructMember* pmember = (FxStructMember*)Self; + int outerflags = 0; + if (ctx.Function) + outerflags = ctx.Function->Variants[0].Flags; + int innerflags = afd->Variants[0].Flags; + if (FScopeBarrier::SideFromFlags(innerflags) == FScopeBarrier::Side_PlainData) + innerflags = FScopeBarrier::ChangeSideInFlags(innerflags, pmember->BarrierSide); + FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars()); + if (!scopeBarrier.callable) + { + ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars()); + delete this; + return nullptr; + } + } + if (staticonly && (afd->Variants[0].Flags & VARF_Method)) { if (!novirtual || !(afd->Variants[0].Flags & VARF_Virtual)) @@ -8029,14 +8049,14 @@ isresolved: auto ccls = dyn_cast(cls); if (clstype == nullptr || ccls == nullptr || !clstype->IsDescendantOf(ccls)) { - ScriptPosition.Message(MSG_ERROR, "Cannot call non-static function %s::%s from here\n", cls->TypeName.GetChars(), MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Cannot call non-static function %s::%s from here", cls->TypeName.GetChars(), MethodName.GetChars()); delete this; return nullptr; } else { // Todo: If this is a qualified call to a parent class function, let it through (but this needs to disable virtual calls later.) - ScriptPosition.Message(MSG_ERROR, "Qualified member call to parent class %s::%s is not yet implemented\n", cls->TypeName.GetChars(), MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Qualified member call to parent class %s::%s is not yet implemented", cls->TypeName.GetChars(), MethodName.GetChars()); delete this; return nullptr; } @@ -8064,7 +8084,7 @@ isresolved: // Functions with no Actor usage may not be called through a pointer because they will lose their context. if (!(afd->Variants[0].UseFlags & SUF_ACTOR)) { - ScriptPosition.Message(MSG_ERROR, "Function %s cannot be used with a non-self object\n", afd->SymbolName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Function %s cannot be used with a non-self object", afd->SymbolName.GetChars()); delete this; return nullptr; } diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index f9291274d..39c893e33 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -132,6 +132,14 @@ struct FScopeBarrier } } + // this modifies VARF_ flags and sets the side properly. + static int ChangeSideInFlags(int flags, int side) + { + flags &= ~(VARF_UI | VARF_Play); + flags |= FlagsFromSide(side); + return flags; + } + FScopeBarrier() { sidefrom = -1; @@ -203,7 +211,7 @@ struct FScopeBarrier 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 field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); } } }; From 014db18f2a509e8d8d2ec93498f1ebabc7d83bd8 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 17 Feb 2017 23:36:16 +0200 Subject: [PATCH 18/48] Fixed some things around --- src/scripting/backend/codegen.cpp | 36 ++++++++++++++------------- src/scripting/backend/codegen.h | 2 +- src/scripting/zscript/zcc_compile.cpp | 9 +++---- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index d452fedd9..5a77b4a01 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6715,16 +6715,17 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) if ((classx->ExprType == EFX_Self) && (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_ReadOnly))) bWritable = false; } - // [ZZ] plain data "inherits" scope of whatever it was defined in. - if (bWritable) // don't do complex checks on early fail + // [ZZ] implement write barrier between different scopes + if (bWritable) { int outerflags = 0; if (ctx.Function) outerflags = ctx.Function->Variants[0].Flags; - FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), membervar->SymbolName.GetChars()); + FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), ""); if (!scopeBarrier.writable) bWritable = false; } + *writable = bWritable; } return true; @@ -6769,7 +6770,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) } BarrierSide = scopeBarrier.sidelast; - if (classx->ExprType == EFX_StructMember || classx->ExprType == EFX_ClassMember) + if (classx->ExprType == EFX_StructMember) // note: only do this for structs now { FxStructMember* pmember = (FxStructMember*)classx; if (BarrierSide == FScopeBarrier::Side_PlainData && pmember) @@ -6793,7 +6794,8 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) { auto parentfield = static_cast(classx)->membervar; // PFields are garbage collected so this will be automatically taken care of later. - auto newfield = new PField(NAME_None, membervar->Type, membervar->Flags | parentfield->Flags, membervar->Offset + parentfield->Offset); + // [ZZ] call ChangeSideInFlags to ensure that we don't get ui+play + auto newfield = new PField(NAME_None, membervar->Type, FScopeBarrier::ChangeSideInFlags(membervar->Flags | parentfield->Flags, BarrierSide), membervar->Offset + parentfield->Offset); newfield->BitValue = membervar->BitValue; static_cast(classx)->membervar = newfield; classx->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. @@ -8023,22 +8025,22 @@ isresolved: // [ZZ] if self is a struct or a class member, check if it's valid to call this function at all. // implement more magic - if (Self->ExprType == EFX_ClassMember || Self->ExprType == EFX_StructMember) + int outerflags = 0; + if (ctx.Function) + outerflags = ctx.Function->Variants[0].Flags; + int innerflags = afd->Variants[0].Flags; + if (Self->ExprType == EFX_StructMember) { FxStructMember* pmember = (FxStructMember*)Self; - int outerflags = 0; - if (ctx.Function) - outerflags = ctx.Function->Variants[0].Flags; - int innerflags = afd->Variants[0].Flags; if (FScopeBarrier::SideFromFlags(innerflags) == FScopeBarrier::Side_PlainData) innerflags = FScopeBarrier::ChangeSideInFlags(innerflags, pmember->BarrierSide); - FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars()); - if (!scopeBarrier.callable) - { - ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars()); - delete this; - return nullptr; - } + } + FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars()); + if (!scopeBarrier.callable) + { + ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars()); + delete this; + return nullptr; } if (staticonly && (afd->Variants[0].Flags & VARF_Method)) diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index 39c893e33..cbf02f954 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -187,7 +187,7 @@ struct FScopeBarrier sidelast = sideto; else sideto = sidelast; - 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; readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 3afb80ff3..3eb36624a 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2397,9 +2397,9 @@ 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 (f->Flags & ZCC_UIFlag) // only direct specification here (varflags can also have owning class scope applied, we don't want that) + if (varflags & VARF_UI) sym->Variants[0].Implementation->ScopeUI = true; - if (f->Flags & ZCC_Play) // only direct specification here + if (varflags & VARF_Play) sym->Variants[0].Implementation->ScopePlay = true; if (varflags & VARF_ReadOnly) sym->Variants[0].Implementation->FuncConst = true; @@ -2422,8 +2422,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 ((oldfunc->ScopePlay != sym->Variants[0].Implementation->ScopePlay) || - (oldfunc->ScopeUI != sym->Variants[0].Implementation->ScopeUI)) + if (f->Flags & (ZCC_UIFlag|ZCC_Play|ZCC_ClearScope)) { Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars()); } @@ -2432,7 +2431,7 @@ 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 + // 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) From 12aa18a92b03c6a151a8caacf1fde5b4048f1f11 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 00:34:24 +0200 Subject: [PATCH 19/48] Preparing to do class-based method scopes: can't change class scope once defined (play stays play, ui stays ui) --- src/scripting/zscript/zcc-parse.lemon | 2 -- src/scripting/zscript/zcc_compile.cpp | 12 ++++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 59924b634..930f2a9ca 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -216,7 +216,6 @@ class_flags(X) ::= class_flags(A) NONEW. { X.Flags = A.Flags | ZCC_NoNew; X. class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; } -class_flags(X) ::= class_flags(A) CLEARSCOPE. { X.Flags = A.Flags | ZCC_ClearScope; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } /*----- Dottable Identifier -----*/ @@ -332,7 +331,6 @@ struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body struct_flags(X) ::= . { X.Flags = 0; } struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; } struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; } -struct_flags(X) ::= struct_flags(A) CLEARSCOPE. { X.Flags = A.Flags | ZCC_ClearScope; } struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; } opt_struct_body(X) ::= . { X = NULL; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 3eb36624a..52899e44f 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -625,10 +625,18 @@ void ZCCCompiler::CreateClassTypes() Error(c->cls, "Class %s has incompatible flags", c->NodeName().GetChars()); } - if (c->cls->Flags & ZCC_UIFlag || ((parent->ObjectFlags & OF_UI) && !(c->cls->Flags & ZCC_ClearScope))) + if (c->cls->Flags & ZCC_UIFlag) c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_Play) | OF_UI; - if (c->cls->Flags & ZCC_Play || ((parent->ObjectFlags & OF_Play) && !(c->cls->Flags & ZCC_ClearScope))) + if (c->cls->Flags & ZCC_Play) c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_UI) | OF_Play; + if (parent->ObjectFlags & (OF_UI | OF_Play)) // parent is either ui or play + { + if (c->cls->Flags & (ZCC_UIFlag | ZCC_Play)) + { + Error(c->cls, "Can't change class scope in class %s", c->NodeName().GetChars()); + } + c->Type()->ObjectFlags = (c->Type()->ObjectFlags & ~(OF_UI | OF_Play)) | (parent->ObjectFlags & (OF_UI | OF_Play)); + } c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type()); From b5ab011bb987e6961d5ec84ba4616127602e9431 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 04:07:12 +0200 Subject: [PATCH 20/48] 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() {} From e0ae0fdb2e0294119c8dfc5af58a3e458f12ebac Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 06:27:28 +0200 Subject: [PATCH 21/48] Dynamic virtualscope checking. May yet be buggy. --- src/scripting/backend/codegen.cpp | 39 +++++++++++++++++++++++++++ src/scripting/backend/codegen.h | 25 ++++++++++------- src/scripting/thingdef.cpp | 4 +++ src/scripting/vm/vm.h | 7 ++++- src/scripting/vm/vmexec.h | 19 +++++++++++-- src/scripting/zscript/zcc_compile.cpp | 18 ++++++++----- 6 files changed, 92 insertions(+), 20 deletions(-) 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; From afc9050a43ae43d89b91e0d4f0d23f1d9fc277b0 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 06:56:00 +0200 Subject: [PATCH 22/48] Marked Thinker play. Marked OnDestroy and Destroy virtualscope. It compiles :D --- src/scripting/backend/codegen.cpp | 14 +++++++++++++- wadsrc/static/zscript/base.txt | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 4bd3218df..99b83ceae 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -109,7 +109,7 @@ void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, P { int outerside = FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags); if (outerside == FScopeBarrier::Side_Virtual) - outerside = FScopeBarrier::Side_PlainData; + outerside = FScopeBarrier::SideFromObjectFlags(callingfunc->OwningClass->ObjectFlags); int innerside = FScopeBarrier::SideFromFlags(calledfunc->Variants[0].Flags); if (innerside == FScopeBarrier::Side_Virtual) innerside = FScopeBarrier::SideFromObjectFlags(selftype->ObjectFlags); @@ -6748,7 +6748,11 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) { int outerflags = 0; if (ctx.Function) + { outerflags = ctx.Function->Variants[0].Flags; + if ((outerflags & (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), ""); if (!scopeBarrier.writable) bWritable = false; @@ -6788,7 +6792,11 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) // [ZZ] support magic int outerflags = 0; if (ctx.Function) + { outerflags = ctx.Function->Variants[0].Flags; + if ((outerflags & (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } FScopeBarrier scopeBarrier(outerflags, membervar->Flags, membervar->SymbolName.GetChars()); if (!scopeBarrier.readable) { @@ -8055,7 +8063,11 @@ isresolved: // implement more magic int outerflags = 0; if (ctx.Function) + { outerflags = ctx.Function->Variants[0].Flags; + if ((outerflags & (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } int innerflags = afd->Variants[0].Flags; int innerside = FScopeBarrier::SideFromFlags(innerflags); // [ZZ] check this at compile time. this would work for most legit cases. diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 394450f22..14aec9b1d 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -332,10 +332,10 @@ class Object native 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() {} + virtual virtualscope void OnDestroy() {} } -class Thinker : Object native +class Thinker : Object play native { enum EStatnums { From dbc595f886b833609125c2ca958639f959c3050b Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 07:30:08 +0200 Subject: [PATCH 23/48] Marked DrawPowerup as ui, still compiles and somewhat works :D --- src/scripting/backend/codegen.cpp | 6 +++--- src/scripting/vm/vm.h | 2 +- src/scripting/zscript/zcc_compile.cpp | 12 ++++++------ wadsrc/static/zscript/inventory/inventory.txt | 2 +- wadsrc/static/zscript/inventory/powerups.txt | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 99b83ceae..0ec7c1381 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6750,7 +6750,7 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) if (ctx.Function) { outerflags = ctx.Function->Variants[0].Flags; - if ((outerflags & (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); } FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), ""); @@ -6794,7 +6794,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) if (ctx.Function) { outerflags = ctx.Function->Variants[0].Flags; - if ((outerflags & (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); } FScopeBarrier scopeBarrier(outerflags, membervar->Flags, membervar->SymbolName.GetChars()); @@ -8065,7 +8065,7 @@ isresolved: if (ctx.Function) { outerflags = ctx.Function->Variants[0].Flags; - if ((outerflags & (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); } int innerflags = afd->Variants[0].Flags; diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index ba1db7aa9..b13f17777 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -708,7 +708,7 @@ public: bool Final = false; // cannot be overridden bool Unsafe = false; // Contains references to class fields that are unsafe for psp and item state calls. bool FuncConst = false; // [ZZ] readonly function - int BarrierSide = -1; // [ZZ] FScopeBarrier::Side + int BarrierSide = 0; // [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_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index c19d14b1e..0cfd2d66a 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -1081,8 +1081,8 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel // For structs only allow 'deprecated', for classes exclude function qualifiers. int notallowed = forstruct? - ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension : - ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension; + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope : + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope; if (field->Flags & notallowed) { @@ -1102,11 +1102,11 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel if (type->ObjectFlags & OF_Play) varflags |= VARF_Play; if (field->Flags & ZCC_UIFlag) - varflags = (varflags&~VARF_Play) | VARF_UI; + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); if (field->Flags & ZCC_Play) - varflags = (varflags&~VARF_UI) | VARF_Play; + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); if (field->Flags & ZCC_ClearScope) - varflags = (varflags&~(VARF_UI | VARF_Play)); + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); if (field->Flags & ZCC_Native) { @@ -2128,7 +2128,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (c->Type()->ObjectFlags & OF_Play) varflags |= VARF_Play; 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. + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); // const implies clearscope. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag. if (f->Flags & ZCC_UIFlag) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); if (f->Flags & ZCC_Play) diff --git a/wadsrc/static/zscript/inventory/inventory.txt b/wadsrc/static/zscript/inventory/inventory.txt index 28b20cdd5..f24a2de09 100644 --- a/wadsrc/static/zscript/inventory/inventory.txt +++ b/wadsrc/static/zscript/inventory/inventory.txt @@ -813,7 +813,7 @@ class Inventory : Actor native // //=========================================================================== - virtual bool DrawPowerup(int x, int y) { return false; } + virtual ui bool DrawPowerup(int x, int y) { return false; } //=========================================================================== // diff --git a/wadsrc/static/zscript/inventory/powerups.txt b/wadsrc/static/zscript/inventory/powerups.txt index d355a5e14..df0812355 100644 --- a/wadsrc/static/zscript/inventory/powerups.txt +++ b/wadsrc/static/zscript/inventory/powerups.txt @@ -934,7 +934,7 @@ class PowerFlight : Powerup +INVENTORY.HUBPOWER } - bool HitCenterFrame; + ui bool HitCenterFrame; //=========================================================================== // From d7f5d8a403bfb76b58d44cc2d60fc7ad1e47533f Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 07:31:07 +0200 Subject: [PATCH 24/48] ClearScope for fields essentially means 'world-writable'. We don't want to support that - disallowed --- src/scripting/zscript/zcc_compile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 0cfd2d66a..68b22a05e 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -1081,8 +1081,8 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel // For structs only allow 'deprecated', for classes exclude function qualifiers. int notallowed = forstruct? - ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope : - ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope; + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope : + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope; if (field->Flags & notallowed) { From 463620c3b8405fa0d44caace0921a2f0fcfe3d50 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 07:33:27 +0200 Subject: [PATCH 25/48] ClearScope is not needed for methods either. Disabled, but not removed - reenable if needed later --- src/scripting/zscript/zcc_compile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 68b22a05e..1db3fbdda 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2075,7 +2075,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } while (t != f->Type); } - int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract; + int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract | ZCC_ClearScope; if (f->Flags & notallowed) { From ab07b30d5e0158696fe8cab33dbbbd7e329048c4 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 07:48:28 +0200 Subject: [PATCH 26/48] Fixed: implicit method call without specifying self should be checked as well --- src/scripting/backend/codegen.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 0ec7c1381..f0011de81 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -7403,6 +7403,32 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx) return nullptr; } + // [ZZ] validate call + PClass* cls = (PClass*)ctx.Class; + int outerflags = 0; + if (ctx.Function) + { + outerflags = ctx.Function->Variants[0].Flags; + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } + 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); + } + FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars()); + if (!scopeBarrier.callable) + { + ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars()); + delete this; + return nullptr; + } + + // [ZZ] this is only checked for VARF_Methods in the other place. bug? if (!CheckFunctionCompatiblity(ScriptPosition, ctx.Function, afd)) { delete this; From fb9b8c8870e6505310361964757e78038d6b178d Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 07:51:29 +0200 Subject: [PATCH 27/48] Re-enabled clearscope for methods; made isBlinking() clearscope --- src/scripting/zscript/zcc_compile.cpp | 2 +- wadsrc/static/zscript/inventory/powerups.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 1db3fbdda..68b22a05e 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2075,7 +2075,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } while (t != f->Type); } - int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract | ZCC_ClearScope; + int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract; if (f->Flags & notallowed) { diff --git a/wadsrc/static/zscript/inventory/powerups.txt b/wadsrc/static/zscript/inventory/powerups.txt index df0812355..5885100e6 100644 --- a/wadsrc/static/zscript/inventory/powerups.txt +++ b/wadsrc/static/zscript/inventory/powerups.txt @@ -285,7 +285,7 @@ class Powerup : Inventory // //=========================================================================== - virtual bool isBlinking() + virtual clearscope bool isBlinking() { return (EffectTics <= BLINKTHRESHOLD && (EffectTics & 8) && !bNoScreenBlink); } From 6ec7d9af59cf3ab72ec032a0445fc54631a1c3be Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 18 Feb 2017 08:02:38 +0200 Subject: [PATCH 28/48] No more crashing in anonymous functions during runtime check of virtualscope --- src/scripting/backend/codegen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index f0011de81..fe8243d28 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -107,7 +107,8 @@ void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc) // this can be imported in vmexec.h void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, PClass* selftype) { - int outerside = FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags); + // [ZZ] anonymous blocks have 0 variants, so give them Side_Virtual. + int outerside = callingfunc->Variants.Size() ? FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags) : FScopeBarrier::Side_Virtual; if (outerside == FScopeBarrier::Side_Virtual) outerside = FScopeBarrier::SideFromObjectFlags(callingfunc->OwningClass->ObjectFlags); int innerside = FScopeBarrier::SideFromFlags(calledfunc->Variants[0].Flags); From 78533cc316d545d6cad0cb98667fcb4ece81db4d Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 3 Mar 2017 22:38:33 +0200 Subject: [PATCH 29/48] Fixed: the name of accessed field is now properly retrieved in FxStructMember::RequestAddress --- src/scripting/backend/codegen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index fe8243d28..edc5066fb 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6754,7 +6754,7 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); } - FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), ""); + FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), membervar->SymbolName.GetChars()); if (!scopeBarrier.writable) bWritable = false; } From 3a57a9809f997001f745cc2169ab76dc05a83f7a Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 3 Mar 2017 22:42:12 +0200 Subject: [PATCH 30/48] Removed nonew class modifier --- src/dobject.h | 5 ++--- src/sc_man_scanner.re | 1 - src/sc_man_tokens.h | 1 - src/scripting/backend/codegen.cpp | 13 ------------- src/scripting/vm/vmexec.h | 12 ------------ src/scripting/zscript/zcc-parse.lemon | 1 - src/scripting/zscript/zcc_compile.cpp | 5 +---- src/scripting/zscript/zcc_parser.cpp | 1 - src/scripting/zscript/zcc_parser.h | 9 ++++----- 9 files changed, 7 insertions(+), 41 deletions(-) diff --git a/src/dobject.h b/src/dobject.h index b5f7ef8c4..5bcb81c50 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -206,9 +206,8 @@ enum EObjectFlags OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function at all - OF_NoNew = 1 << 15, // Marks a class that can only be created with new() in the exact class that has this keyword - OF_UI = 1 << 16, // Marks a class that defaults to VARF_UI for it's fields/methods - OF_Play = 1 << 17, // Marks a class that defaults to VARF_Play for it's fields/methods + OF_UI = 1 << 15, // Marks a class that defaults to VARF_UI for it's fields/methods + OF_Play = 1 << 16, // Marks a class that defaults to VARF_Play for it's fields/methods }; template class TObjPtr; diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index f4aac0cb5..ba839813f 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -170,7 +170,6 @@ std2: 'virtual' { RET(TK_Virtual); } 'override' { RET(TK_Override); } 'vararg' { RET(TK_VarArg); } - 'nonew' { RET(TK_NoNew); } 'ui' { RET(TK_UI); } 'play' { RET(TK_Play); } 'clearscope' { RET(TK_ClearScope); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index fdcb72229..320601414 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -112,7 +112,6 @@ xx(TK_Optional, "'optional'") xx(TK_Export, "'expert'") xx(TK_Virtual, "'virtual'") xx(TK_VarArg, "'vararg'") -xx(TK_NoNew, "'nonew'") xx(TK_UI, "'ui'") xx(TK_Play, "'play'") xx(TK_ClearScope, "'clearscope'") diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index edc5066fb..1700dd3e6 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -5095,19 +5095,6 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) return nullptr; } - if (cls->ObjectFlags & OF_NoNew) - { - PClass* pcls = cls; - while (pcls && pcls->ParentClass && (pcls->ParentClass->ObjectFlags & OF_NoNew)) - pcls = pcls->ParentClass; - if (pcls != ctx.Class) - { - ScriptPosition.Message(MSG_ERROR, "Cannot instantiate class %s directly", cls->TypeName.GetChars()); - delete this; - return nullptr; - } - } - ValueType = NewPointer(cls); } diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index d47a3bf48..64ea0b7c3 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -801,18 +801,6 @@ begin: PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v); PFunction *callingfunc = (PFunction*)konsta[C].o; // [ZZ] due to how this is set, it's always const if (cls->ObjectFlags & OF_Abstract) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); - if (cls->ObjectFlags & OF_NoNew) - { - // trace to the first nonew class in the hierarchy. - // compare that class to the context class. - // if not matching, disallow creation. - // this ensures that only the root class can have a static factory method that can also create instances of subclasses via new(). - PClass* pcls = cls; - while (pcls && pcls->ParentClass && (pcls->ParentClass->ObjectFlags & OF_NoNew)) - pcls = pcls->ParentClass; - 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); diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 2f412f5cf..f80a09974 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -212,7 +212,6 @@ class_ancestry(X) ::= COLON dottable_id(A). { X = A; /*X-overwrites-A*/ } %type class_flags{ClassFlagsBlock} class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; } class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; } -class_flags(X) ::= class_flags(A) NONEW. { X.Flags = A.Flags | ZCC_NoNew; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 68b22a05e..923b52cfd 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -611,9 +611,6 @@ void ZCCCompiler::CreateClassTypes() if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName()); if (c->cls->Flags & ZCC_Abstract) c->Type()->ObjectFlags |= OF_Abstract; - // [ZZ] inherit nonew keyword - if (c->cls->Flags & ZCC_NoNew || (parent->ObjectFlags & OF_NoNew)) - c->Type()->ObjectFlags |= OF_NoNew; // static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; int incompatiblecnt = 0; @@ -1273,7 +1270,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", "virtualscope" }; + const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "ui", "play", "clearscope", "virtualscope" }; FString build; for (size_t i = 0; i < countof(flagnames); i++) diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 9f1117193..e48033bcc 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -141,7 +141,6 @@ static void InitTokenMap() 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); TOKENDEF (TK_Meta, ZCC_META); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 7cc41318d..c414c0e9c 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -37,11 +37,10 @@ enum ZCC_Override = 1 << 14, ZCC_Transient = 1 << 15, ZCC_VarArg = 1 << 16, - ZCC_NoNew = 1 << 17, - ZCC_UIFlag = 1 << 18, // there's also token called ZCC_UI - ZCC_Play = 1 << 19, - ZCC_ClearScope = 1 << 20, - ZCC_VirtualScope = 1 << 21, + ZCC_UIFlag = 1 << 17, // there's also token called ZCC_UI + ZCC_Play = 1 << 18, + ZCC_ClearScope = 1 << 19, + ZCC_VirtualScope = 1 << 20, }; // Function parameter modifiers From 421f78c771c12e2e030b70197fa08953c6ba245e Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 3 Mar 2017 22:52:35 +0200 Subject: [PATCH 31/48] Fixed potential crash with new() in anonymous functions (is that even allowed?); Added compile-time check for disallowed new() --- src/scripting/backend/codegen.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 1700dd3e6..dca4fdbf1 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -99,7 +99,9 @@ static const FLOP FxFlops[] = // this can be imported in vmexec.h void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc) { - int outerside = FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags); + int outerside = callingfunc->Variants.Size() ? FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags) : FScopeBarrier::Side_Virtual; + if (outerside == FScopeBarrier::Side_Virtual) + outerside = FScopeBarrier::SideFromObjectFlags(callingfunc->OwningClass->ObjectFlags); 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)); @@ -5095,6 +5097,18 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) return nullptr; } + // + int outerside = ctx.Function && ctx.Function->Variants.Size() ? FScopeBarrier::SideFromFlags(ctx.Function->Variants[0].Flags) : FScopeBarrier::Side_Virtual; + if (outerside == FScopeBarrier::Side_Virtual) + outerside = FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags); + int innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context" + { + ScriptPosition.Message(MSG_ERROR, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside)); + delete this; + return nullptr; + } + ValueType = NewPointer(cls); } From f4a546aee6554368e18a8da5dac9cabc3d39106d Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 3 Mar 2017 23:15:18 +0200 Subject: [PATCH 32/48] Moved menus to ui side, moved AlterWeaponSprite to ui side, moved StaticEventHandler to play side (except ui virtuals) --- wadsrc/static/zscript/events.txt | 8 ++++---- wadsrc/static/zscript/inventory/inventory.txt | 2 +- wadsrc/static/zscript/menu/menu.txt | 2 +- wadsrc/static/zscript/menu/menuitembase.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wadsrc/static/zscript/events.txt b/wadsrc/static/zscript/events.txt index 24707055f..0e1237dfe 100755 --- a/wadsrc/static/zscript/events.txt +++ b/wadsrc/static/zscript/events.txt @@ -279,7 +279,7 @@ class ConsoleEvent : BaseEvent native native readonly int Args[3]; } -class StaticEventHandler : Object native +class StaticEventHandler : Object native play { // static event handlers CAN register other static event handlers. // unlike EventHandler.Create that will not create them. @@ -307,8 +307,8 @@ class StaticEventHandler : Object native virtual native void WorldTick(WorldEvent e); // - virtual native void RenderFrame(RenderEvent e); - virtual native void RenderOverlay(RenderEvent e); + virtual native ui void RenderFrame(RenderEvent e); + virtual native ui void RenderOverlay(RenderEvent e); // virtual native void PlayerEntered(PlayerEvent e); @@ -317,7 +317,7 @@ class StaticEventHandler : Object native virtual native void PlayerDisconnected(PlayerEvent e); // - virtual native bool UiProcess(UiEvent e); + virtual native ui bool UiProcess(UiEvent e); virtual native bool InputProcess(InputEvent e); // diff --git a/wadsrc/static/zscript/inventory/inventory.txt b/wadsrc/static/zscript/inventory/inventory.txt index f24a2de09..8b142c9a5 100644 --- a/wadsrc/static/zscript/inventory/inventory.txt +++ b/wadsrc/static/zscript/inventory/inventory.txt @@ -746,7 +746,7 @@ class Inventory : Actor native virtual bool Use (bool pickup) { return false; } virtual double GetSpeedFactor() { return 1; } virtual bool GetNoTeleportFreeze() { return false; } - virtual void AlterWeaponSprite(VisStyle vis, in out int changed) {} + virtual ui void AlterWeaponSprite(VisStyle vis, in out int changed) {} virtual void OwnerDied() {} virtual Color GetBlend () { return 0; } diff --git a/wadsrc/static/zscript/menu/menu.txt b/wadsrc/static/zscript/menu/menu.txt index 1640e9d35..db105fa32 100644 --- a/wadsrc/static/zscript/menu/menu.txt +++ b/wadsrc/static/zscript/menu/menu.txt @@ -48,7 +48,7 @@ struct JoystickConfig native } -class Menu : Object native +class Menu : Object native ui { enum EMenuKey { diff --git a/wadsrc/static/zscript/menu/menuitembase.txt b/wadsrc/static/zscript/menu/menuitembase.txt index de5f05a42..901de9d4f 100644 --- a/wadsrc/static/zscript/menu/menuitembase.txt +++ b/wadsrc/static/zscript/menu/menuitembase.txt @@ -4,7 +4,7 @@ // //============================================================================= -class MenuItemBase : Object native +class MenuItemBase : Object native ui { protected native int mXpos, mYpos; protected native Name mAction; From 43d3e3e5c3cae98a65422c28f08eacf546f5f790 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 3 Mar 2017 23:17:21 +0200 Subject: [PATCH 33/48] Assigned barrier sides to Event structures, added clearscope to ConsoleProcess because it handles both sides --- wadsrc/static/zscript/events.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wadsrc/static/zscript/events.txt b/wadsrc/static/zscript/events.txt index 0e1237dfe..664438f0d 100755 --- a/wadsrc/static/zscript/events.txt +++ b/wadsrc/static/zscript/events.txt @@ -1,6 +1,6 @@ class BaseEvent native { } // just a base class. it doesn't inherit from Object on the scripting side so you can't call Destroy() on it and break everything. -class RenderEvent : BaseEvent native +class RenderEvent : BaseEvent native ui { native readonly Vector3 ViewPos; native readonly double ViewAngle; @@ -10,7 +10,7 @@ class RenderEvent : BaseEvent native native readonly Actor Camera; } -class WorldEvent : BaseEvent native +class WorldEvent : BaseEvent native play { // for loaded/unloaded native readonly bool IsSaveGame; @@ -28,7 +28,7 @@ class WorldEvent : BaseEvent native native readonly double DamageAngle; } -class PlayerEvent : BaseEvent native +class PlayerEvent : BaseEvent native play { // this is the player number that caused the event. // note: you can get player struct from this by using players[e.PlayerNumber] @@ -37,7 +37,7 @@ class PlayerEvent : BaseEvent native native readonly bool IsReturn; } -class UiEvent : BaseEvent native +class UiEvent : BaseEvent native ui { // d_gui.h enum EGUIEvent @@ -121,7 +121,7 @@ class UiEvent : BaseEvent native native readonly bool IsAlt; } -class InputEvent : BaseEvent native +class InputEvent : BaseEvent native play { enum EGenericEvent { @@ -321,7 +321,7 @@ class StaticEventHandler : Object native play virtual native bool InputProcess(InputEvent e); // - virtual native void ConsoleProcess(ConsoleEvent e); + virtual native clearscope void ConsoleProcess(ConsoleEvent e); // this value will be queried on Register() to decide the relative order of this handler to every other. // this is most useful in UI systems. From a924564bf34ecd3eb1a35197000237ff477b72a8 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 3 Mar 2017 23:21:12 +0200 Subject: [PATCH 34/48] Implemented hard separation between playsim ConsoleEvent and networked ConsoleEvent (ConsoleProcess, NetworkProcess) --- src/events.cpp | 50 +++++++++++++++++++++++--------- wadsrc/static/zscript/events.txt | 3 +- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/events.cpp b/src/events.cpp index 4522f1989..bb7b34f66 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -630,6 +630,7 @@ DEFINE_EMPTY_HANDLER(DStaticEventHandler, UiProcess); DEFINE_EMPTY_HANDLER(DStaticEventHandler, InputProcess); DEFINE_EMPTY_HANDLER(DStaticEventHandler, ConsoleProcess); +DEFINE_EMPTY_HANDLER(DStaticEventHandler, NetworkProcess); // =========================================== // @@ -1040,22 +1041,45 @@ static DConsoleEvent* E_SetupConsoleEvent() void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3) { - IFVIRTUAL(DStaticEventHandler, ConsoleProcess) + if (player < 0) { - // don't create excessive DObjects if not going to be processed anyway - if (func == DStaticEventHandler_ConsoleProcess_VMPtr) - return; - DConsoleEvent* e = E_SetupConsoleEvent(); + IFVIRTUAL(DStaticEventHandler, ConsoleProcess) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_ConsoleProcess_VMPtr) + return; + DConsoleEvent* e = E_SetupConsoleEvent(); - // - e->Player = player; - e->Name = name; - e->Args[0] = arg1; - e->Args[1] = arg2; - e->Args[2] = arg3; + // + e->Player = player; + e->Name = name; + e->Args[0] = arg1; + e->Args[1] = arg2; + e->Args[2] = arg3; - VMValue params[2] = { (DStaticEventHandler*)this, e }; - GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } + } + else + { + IFVIRTUAL(DStaticEventHandler, NetworkProcess) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_NetworkProcess_VMPtr) + return; + DConsoleEvent* e = E_SetupConsoleEvent(); + + // + e->Player = player; + e->Name = name; + e->Args[0] = arg1; + e->Args[1] = arg2; + e->Args[2] = arg3; + + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } } } diff --git a/wadsrc/static/zscript/events.txt b/wadsrc/static/zscript/events.txt index 664438f0d..5b5e067f4 100755 --- a/wadsrc/static/zscript/events.txt +++ b/wadsrc/static/zscript/events.txt @@ -321,7 +321,8 @@ class StaticEventHandler : Object native play virtual native bool InputProcess(InputEvent e); // - virtual native clearscope void ConsoleProcess(ConsoleEvent e); + virtual native ui void ConsoleProcess(ConsoleEvent e); + virtual native void NetworkProcess(ConsoleEvent e); // this value will be queried on Register() to decide the relative order of this handler to every other. // this is most useful in UI systems. From 1433b783019d37af0ff1223f031d10fc737d8e0d Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 3 Mar 2017 23:26:06 +0200 Subject: [PATCH 35/48] Assigned all map data to play --- wadsrc/static/zscript/mapdata.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wadsrc/static/zscript/mapdata.txt b/wadsrc/static/zscript/mapdata.txt index 5a216ea5e..4bbfb7097 100644 --- a/wadsrc/static/zscript/mapdata.txt +++ b/wadsrc/static/zscript/mapdata.txt @@ -1,5 +1,5 @@ -struct SectorPortal native +struct SectorPortal native play { enum EType { @@ -31,12 +31,12 @@ struct SectorPortal native }; -struct Vertex native +struct Vertex native play { native readonly Vector2 p; } -struct Side native +struct Side native play { enum ETexpart { @@ -100,7 +100,7 @@ struct Side native }; -struct Line native +struct Line native play { enum ELineFlags { @@ -171,7 +171,7 @@ struct Line native } } -struct SecPlane native +struct SecPlane native play { native Vector3 Normal; native double D; @@ -189,7 +189,7 @@ struct SecPlane native } // This encapsulates all info Doom's original 'special' field contained - for saving and transferring. -struct SecSpecial +struct SecSpecial play { Name damagetype; int damageamount; @@ -199,7 +199,7 @@ struct SecSpecial int Flags; } -struct Sector native +struct Sector native play { //secplane_t floorplane, ceilingplane; // defined internally //FDynamicColormap *ColorMap; From c9a994a885c8ccbad15b3464bb974f1600f34341 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 4 Mar 2017 00:04:19 +0200 Subject: [PATCH 36/48] Fixed: clearscope should also clear the inherited scope (through struct member access chain); Fixed: struct member access chain should ONLY work for structs (forgot that FxClassMember inherits FxStructMember) --- src/dobjtype.h | 1 + src/scripting/backend/codegen.cpp | 13 ++++++---- src/scripting/backend/codegen.h | 13 +++++++--- src/scripting/zscript/zcc_compile.cpp | 9 ++----- wadsrc/static/zscript/actor.txt | 8 +++---- wadsrc/static/zscript/menu/menu.txt | 2 +- wadsrc/static/zscript/shared/player.txt | 32 ++++++++++++------------- 7 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/dobjtype.h b/src/dobjtype.h index de3b9c916..1d134bd07 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -40,6 +40,7 @@ enum 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) + VARF_ClearScope = (1<<23), // [ZZ] clearscope: this method ignores the member access chain that leads to it and is always plain data. }; // An action function ------------------------------------------------------- diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 1359df59a..ad3ed7664 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -6879,7 +6879,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) } BarrierSide = scopeBarrier.sidelast; - if (classx->ExprType == EFX_StructMember) // note: only do this for structs now + if (classx->ExprType == EFX_StructMember && ExprType == EFX_StructMember) // note: only do this for structs now { FxStructMember* pmember = (FxStructMember*)classx; if (BarrierSide == FScopeBarrier::Side_PlainData && pmember) @@ -8175,11 +8175,14 @@ isresolved: innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags); innerflags = FScopeBarrier::FlagsFromSide(innerside); } - if (Self->ExprType == EFX_StructMember) + else if (innerside != FScopeBarrier::Side_Clear) { - FxStructMember* pmember = (FxStructMember*)Self; - if (innerside == FScopeBarrier::Side_PlainData) - innerflags = FScopeBarrier::ChangeSideInFlags(innerflags, pmember->BarrierSide); + if (Self->ExprType == EFX_StructMember) + { + FxStructMember* pmember = (FxStructMember*)Self; + if (innerside == FScopeBarrier::Side_PlainData) + innerflags = FScopeBarrier::ChangeSideInFlags(innerflags, pmember->BarrierSide); + } } FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars()); if (!scopeBarrier.callable) diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index c9e23c980..07425bd90 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -89,7 +89,8 @@ struct FScopeBarrier Side_PlainData = 0, Side_UI = 1, Side_Play = 2, - Side_Virtual = 3 // do NOT change the value + Side_Virtual = 3, // do NOT change the value + Side_Clear = 4 }; int sidefrom; int sidelast; @@ -103,6 +104,8 @@ struct FScopeBarrier return Side_Play; if (flags & VARF_VirtualScope) return Side_Virtual; + if (flags & VARF_ClearScope) + return Side_Clear; return Side_PlainData; } @@ -127,6 +130,8 @@ struct FScopeBarrier return VARF_UI; case Side_Virtual: return VARF_VirtualScope; + case Side_Clear: + return VARF_ClearScope; default: return 0; } @@ -144,7 +149,9 @@ struct FScopeBarrier case Side_Play: return "play"; case Side_Virtual: - return "virtual"; // should not happen! + return "virtualscope"; // should not happen! + case Side_Clear: + return "clearscope"; // should not happen! default: return "unknown"; } @@ -153,7 +160,7 @@ struct FScopeBarrier // this modifies VARF_ flags and sets the side properly. static int ChangeSideInFlags(int flags, int side) { - flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope); + flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope | VARF_ClearScope); flags |= FlagsFromSide(side); return flags; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index e28f5d271..1f7e66442 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2135,7 +2135,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (f->Flags & ZCC_Play) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); if (f->Flags & ZCC_ClearScope) - varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Clear); if (f->Flags & ZCC_VirtualScope) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Virtual); @@ -2405,12 +2405,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool 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; + sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::SideFromFlags(varflags); } PClass *clstype = static_cast(c->Type()); diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 148fd4bd5..56d9072fa 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -425,7 +425,7 @@ class Actor : Thinker native } } - virtual String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) + clearscope virtual String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) { if (mod == 'Telefrag') { @@ -450,13 +450,13 @@ class Actor : Thinker native native static int FindUniqueTid(int start = 0, int limit = 0); native void SetShade(color col); - native string GetTag(string defstr = ""); + clearscope native string GetTag(string defstr = ""); native void SetTag(string defstr = ""); native double GetBobOffset(double frac = 0); native void ClearCounters(); native bool GiveBody (int num, int max=0); native bool HitFloor(); - native bool isTeammate(Actor other); + clearscope native bool isTeammate(Actor other); native int PlayerNumber(); native void SetFriendPlayer(PlayerInfo player); native void SoundAlert(Actor target, bool splash = false, double maxdist = 0); @@ -597,7 +597,7 @@ class Actor : Thinker native native void ClearInventory(); native bool GiveInventory(class type, int amount, bool givecheat = false); native bool TakeInventory(class itemclass, int amount, bool fromdecorate = false, bool notakeinfinite = false); - native Inventory FindInventory(class itemtype, bool subclass = false); + clearscope native Inventory FindInventory(class itemtype, bool subclass = false); native Inventory GiveInventoryType(class itemtype); native Inventory DropInventory (Inventory item, int amt = -1); native bool UseInventory(Inventory item); diff --git a/wadsrc/static/zscript/menu/menu.txt b/wadsrc/static/zscript/menu/menu.txt index b4e5c1ac8..81727ad2b 100644 --- a/wadsrc/static/zscript/menu/menu.txt +++ b/wadsrc/static/zscript/menu/menu.txt @@ -287,7 +287,7 @@ class Menu : Object native ui } -class MenuDescriptor : Object native +class MenuDescriptor : Object native ui { native Name mMenuName; native String mNetgameMessage; diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index a80210714..d6ea5b843 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -150,7 +150,7 @@ class PlayerPawn : Actor native } // This is for SBARINFO. - int, int GetEffectTicsForItem(class item) + clearscope int, int GetEffectTicsForItem(class item) { let pg = (class)(item); if (pg != null) @@ -167,10 +167,10 @@ class PlayerPawn : Actor native return -1, -1; } - native int GetMaxHealth(bool withupgrades = false); + clearscope native int GetMaxHealth(bool withupgrades = false); native bool ResetAirSupply (bool playgasp = false); native void CheckWeaponSwitch(class item); - native static String GetPrintableDisplayName(Class cls); + clearscope native static String GetPrintableDisplayName(Class cls); } @@ -192,7 +192,7 @@ class PlayerChunk : PlayerPawn } } -class PSprite : Object native +class PSprite : Object native play { enum PSPLayers { @@ -237,7 +237,7 @@ enum EPlayerState PST_GONE // Player has left the game } -struct PlayerInfo native // this is what internally is known as player_t +struct PlayerInfo native play // this is what internally is known as player_t { // technically engine constants but the only part of the playsim using them is the player. const NOFIXEDCOLORMAP = -1; @@ -338,22 +338,22 @@ usercmd_t original_cmd; native void PoisonDamage(Actor source, int damage, bool playPainSound); native void SetPsprite(int id, State stat, bool pending = false); native void SetSafeFlash(Weapon weap, State flashstate, int index); - native PSprite GetPSprite(int id); - native PSprite FindPSprite(int id); + native PSprite GetPSprite(int id) const; + native PSprite FindPSprite(int id) const; native void SetLogNumber (int text); native void SetLogText (String text); native void DropWeapon(); native void BringUpWeapon(); - native String GetUserName(); - native Color GetColor(); - native int GetColorSet(); - native int GetPlayerClassNum(); - native int GetSkin(); - native bool GetNeverSwitch(); - native int GetGender(); - native int GetTeam(); - native float GetAutoaim(); + native String GetUserName() const; + native Color GetColor() const; + native int GetColorSet() const; + native int GetPlayerClassNum() const; + native int GetSkin() const; + native bool GetNeverSwitch() const; + native int GetGender() const; + native int GetTeam() const; + native float GetAutoaim() const; native void SetFOV(float fov); } From 3338fb7f3321eff4e0180acca01c3072e79a4fba Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Sat, 4 Mar 2017 00:57:41 +0200 Subject: [PATCH 37/48] Added SendNetworkEvent static method to EventHandler; Fixed qualified static method call from own class (previously was 'shadowed' by qualified virtual method call) --- src/events.cpp | 33 ++++++++++++++++++++++++++----- src/events.h | 3 +++ src/scripting/backend/codegen.cpp | 1 + wadsrc/static/zscript/events.txt | 2 ++ 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/events.cpp b/src/events.cpp index 75dd8cde8..9946a1059 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -117,6 +117,21 @@ bool E_UnregisterHandler(DStaticEventHandler* handler) return true; } +bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3) +{ + if (gamestate != GS_LEVEL) + return false; + + Net_WriteByte(DEM_NETEVENT); + Net_WriteString(name); + Net_WriteByte(3); + Net_WriteLong(arg1); + Net_WriteLong(arg2); + Net_WriteLong(arg3); + + return true; +} + bool E_CheckHandler(DStaticEventHandler* handler) { for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next) @@ -521,6 +536,18 @@ DEFINE_ACTION_FUNCTION(DStaticEventHandler, SetOrder) return 0; } +DEFINE_ACTION_FUNCTION(DEventHandler, SendNetworkEvent) +{ + PARAM_PROLOGUE; + PARAM_STRING(name); + PARAM_INT(arg1); + PARAM_INT(arg2); + PARAM_INT(arg3); + // + + ACTION_RETURN_BOOL(E_SendNetworkEvent(name, arg1, arg2, arg3)); +} + DEFINE_ACTION_FUNCTION(DEventHandler, Create) { PARAM_PROLOGUE; @@ -1160,10 +1187,6 @@ CCMD(netevent) for (int i = 0; i < argn; i++) arg[i] = atoi(argv[2 + i]); // call networked - Net_WriteByte(DEM_NETEVENT); - Net_WriteString(argv[1]); - Net_WriteByte(argn); - for (int i = 0; i < 3; i++) - Net_WriteLong(arg[i]); + E_SendNetworkEvent(argv[1], arg[0], arg[1], arg[2]); } } diff --git a/src/events.h b/src/events.h index fa63e7325..b83890223 100755 --- a/src/events.h +++ b/src/events.h @@ -60,6 +60,9 @@ bool E_Responder(event_t* ev); // splits events into InputProcess and UiProcess // this executes on console/net events. void E_Console(int player, FString name, int arg1, int arg2, int arg3); +// send networked event. unified function. +bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3); + // check if there is anything that should receive GUI events bool E_CheckUiProcessors(); // check if we need native mouse due to UiProcessors diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index ad3ed7664..1a212f5af 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -7859,6 +7859,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) Self = new FxSelf(ScriptPosition); Self->ValueType = NewPointer(cls); } + else novirtual = false; } } } diff --git a/wadsrc/static/zscript/events.txt b/wadsrc/static/zscript/events.txt index 5b5e067f4..6452d19e0 100755 --- a/wadsrc/static/zscript/events.txt +++ b/wadsrc/static/zscript/events.txt @@ -343,4 +343,6 @@ class EventHandler : StaticEventHandler native static native bool Register(StaticEventHandler handler); static native bool Unregister(StaticEventHandler handler); + + clearscope static native void SendNetworkEvent(String name, int arg1 = 0, int arg2 = 0, int arg3 = 0); } From fd4727e70121c3bacaa5c5538a4f46feb634e7d7 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 10:28:51 +0100 Subject: [PATCH 38/48] - did a bit of cleanup. - disabled the runtime check in OP_CALL because as implemented it doesn't clean up properly and is not fully implemented. --- src/CMakeLists.txt | 1 + src/scripting/backend/codegen.cpp | 2 + src/scripting/backend/codegen.h | 176 +------------------------ src/scripting/backend/scopebarrier.cpp | 152 +++++++++++++++++++++ src/scripting/backend/scopebarrier.h | 52 ++++++++ src/scripting/vm/vmexec.h | 2 + 6 files changed, 210 insertions(+), 175 deletions(-) create mode 100644 src/scripting/backend/scopebarrier.cpp create mode 100644 src/scripting/backend/scopebarrier.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ff73e32c..8913ef6ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1168,6 +1168,7 @@ set (PCH_SOURCES scripting/thingdef_data.cpp scripting/thingdef_properties.cpp scripting/backend/codegen.cpp + scripting/backend/scopebarrier.cpp scripting/backend/dynarrays.cpp scripting/backend/vmbuilder.cpp scripting/backend/vmdisasm.cpp diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 1a212f5af..eb92fe974 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -8725,6 +8725,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) ExpEmit selfemit; if (Function->Variants[0].Flags & VARF_Method) { +#if 0 // [ZZ] if (Function->Variants[0].Implementation && Function->Variants[0].Implementation->BarrierSide == FScopeBarrier::Side_Virtual) { @@ -8734,6 +8735,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(CallingFunction, ATAG_OBJECT)); count += 2; } +#endif 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 07425bd90..000688341 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -45,6 +45,7 @@ #include "s_sound.h" #include "actor.h" #include "vmbuilder.h" +#include "scopebarrier.h" #define CHECKRESOLVED() if (isresolved) return this; isresolved=true; @@ -70,181 +71,6 @@ class FxCompoundStatement; class FxLocalVariableDeclaration; typedef TDeletingArray FArgumentList; -// -// [ZZ] this really should be in codegen.h, but vmexec needs to access it -struct FScopeBarrier -{ - bool callable; - bool readable; - bool writable; - - // this is the error message - FString callerror; - FString readerror; - FString writeerror; - - // this is used to make the error message. - enum Side - { - Side_PlainData = 0, - Side_UI = 1, - Side_Play = 2, - Side_Virtual = 3, // do NOT change the value - Side_Clear = 4 - }; - int sidefrom; - int sidelast; - - // Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found. - static int SideFromFlags(int flags) - { - if (flags & VARF_UI) - return Side_UI; - if (flags & VARF_Play) - return Side_Play; - if (flags & VARF_VirtualScope) - return Side_Virtual; - if (flags & VARF_ClearScope) - return Side_Clear; - 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; - } - - // - static int FlagsFromSide(int side) - { - switch (side) - { - case Side_Play: - return VARF_Play; - case Side_UI: - return VARF_UI; - case Side_Virtual: - return VARF_VirtualScope; - case Side_Clear: - return VARF_ClearScope; - default: - return 0; - } - } - - // used for errors - static const char* StringFromSide(int side) - { - switch (side) - { - case Side_PlainData: - return "data"; - case Side_UI: - return "ui"; - case Side_Play: - return "play"; - case Side_Virtual: - return "virtualscope"; // should not happen! - case Side_Clear: - return "clearscope"; // should not happen! - default: - return "unknown"; - } - } - - // this modifies VARF_ flags and sets the side properly. - static int ChangeSideInFlags(int flags, int side) - { - flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope | VARF_ClearScope); - flags |= FlagsFromSide(side); - return flags; - } - - FScopeBarrier() - { - sidefrom = -1; - sidelast = -1; - callable = true; - readable = true; - writable = true; - } - - FScopeBarrier(int flags1, int flags2, const char* name) - { - sidefrom = -1; - sidelast = -1; - callable = true; - readable = true; - writable = true; - - AddFlags(flags1, flags2, name); - } - - // AddFlags modifies ALLOWED actions by flags1->flags2. - // This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed. - // This struct is used so that the logic is in a single place. - void AddFlags(int flags1, int flags2, const char* name) - { - // note: if it's already non-readable, don't even try advancing - if (!readable) - 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; - - if (sidefrom < 0) sidefrom = SideFromFlags(flags1); - if (sidelast < 0) sidelast = sidefrom; - - // flags1 = what's trying to access - // flags2 = what's being accessed - - int sideto = SideFromFlags(flags2); - - // plain data inherits whatever scope modifiers that context or field container has. - // i.e. play String bla; is play, and all non-specified methods/fields inside it are play as well. - if (sideto != Side_PlainData) - sidelast = sideto; - else sideto = sidelast; - - if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable - { - readable = false; - if (name) readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); - } - - if (!readable) - { - writable = false; - callable = false; - 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; - 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; - if (name) callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); - } - } -}; - struct FCompileContext { FxExpression *ControlStmt = nullptr; diff --git a/src/scripting/backend/scopebarrier.cpp b/src/scripting/backend/scopebarrier.cpp new file mode 100644 index 000000000..4d9d6d2fc --- /dev/null +++ b/src/scripting/backend/scopebarrier.cpp @@ -0,0 +1,152 @@ +#include "scopebarrier.h" +#include "dobject.h" + + +// Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found. +int FScopeBarrier::SideFromFlags(int flags) +{ + if (flags & VARF_UI) + return Side_UI; + if (flags & VARF_Play) + return Side_Play; + if (flags & VARF_VirtualScope) + return Side_Virtual; + if (flags & VARF_ClearScope) + return Side_Clear; + return Side_PlainData; +} + +// same as above, but from object flags +int FScopeBarrier::SideFromObjectFlags(int flags) +{ + if (flags & OF_UI) + return Side_UI; + if (flags & OF_Play) + return Side_Play; + return Side_PlainData; +} + +// +int FScopeBarrier::FlagsFromSide(int side) +{ + switch (side) + { + case Side_Play: + return VARF_Play; + case Side_UI: + return VARF_UI; + case Side_Virtual: + return VARF_VirtualScope; + case Side_Clear: + return VARF_ClearScope; + default: + return 0; + } +} + +// used for errors +const char* FScopeBarrier::StringFromSide(int side) +{ + switch (side) + { + case Side_PlainData: + return "data"; + case Side_UI: + return "ui"; + case Side_Play: + return "play"; + case Side_Virtual: + return "virtualscope"; // should not happen! + case Side_Clear: + return "clearscope"; // should not happen! + default: + return "unknown"; + } +} + +// this modifies VARF_ flags and sets the side properly. +int FScopeBarrier::ChangeSideInFlags(int flags, int side) +{ + flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope | VARF_ClearScope); + flags |= FlagsFromSide(side); + return flags; +} + +FScopeBarrier::FScopeBarrier() +{ + sidefrom = -1; + sidelast = -1; + callable = true; + readable = true; + writable = true; +} + +FScopeBarrier::FScopeBarrier(int flags1, int flags2, const char* name) +{ + sidefrom = -1; + sidelast = -1; + callable = true; + readable = true; + writable = true; + + AddFlags(flags1, flags2, name); +} + +// AddFlags modifies ALLOWED actions by flags1->flags2. +// This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed. +// This struct is used so that the logic is in a single place. +void FScopeBarrier::AddFlags(int flags1, int flags2, const char* name) +{ + // note: if it's already non-readable, don't even try advancing + if (!readable) + 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; + + if (sidefrom < 0) sidefrom = SideFromFlags(flags1); + if (sidelast < 0) sidelast = sidefrom; + + // flags1 = what's trying to access + // flags2 = what's being accessed + + int sideto = SideFromFlags(flags2); + + // plain data inherits whatever scope modifiers that context or field container has. + // i.e. play String bla; is play, and all non-specified methods/fields inside it are play as well. + if (sideto != Side_PlainData) + sidelast = sideto; + else sideto = sidelast; + + if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable + { + readable = false; + if (name) readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } + + if (!readable) + { + writable = false; + callable = false; + 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; + 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; + if (name) callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } +} diff --git a/src/scripting/backend/scopebarrier.h b/src/scripting/backend/scopebarrier.h new file mode 100644 index 000000000..ed4f25a10 --- /dev/null +++ b/src/scripting/backend/scopebarrier.h @@ -0,0 +1,52 @@ +#pragma once + +#include "zstring.h" + +// +// [ZZ] this really should be in codegen.h, but vmexec needs to access it +struct FScopeBarrier +{ + bool callable; + bool readable; + bool writable; + + // this is the error message + FString callerror; + FString readerror; + FString writeerror; + + // this is used to make the error message. + enum Side + { + Side_PlainData = 0, + Side_UI = 1, + Side_Play = 2, + Side_Virtual = 3, // do NOT change the value + Side_Clear = 4 + }; + int sidefrom; + int sidelast; + + // Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found. + static int SideFromFlags(int flags); + + // same as above, but from object flags + static int SideFromObjectFlags(int flags); + + // + static int FlagsFromSide(int side); + + // used for errors + static const char* StringFromSide(int side); + + // this modifies VARF_ flags and sets the side properly. + static int ChangeSideInFlags(int flags, int side); + FScopeBarrier(); + FScopeBarrier(int flags1, int flags2, const char* name); + + // AddFlags modifies ALLOWED actions by flags1->flags2. + // This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed. + // This struct is used so that the logic is in a single place. + void AddFlags(int flags1, int flags2, const char* name); +}; + diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index b3d1b2426..c11b351c0 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -664,6 +664,7 @@ begin: VMReturn returns[MAX_RETURNS]; int numret; +#if 0 // [ZZ] hax! b = B; if (call->BarrierSide == 3) // :( - this is Side_Virtual. Side_Virtual should receive special arguments. @@ -675,6 +676,7 @@ begin: FScopeBarrier_ValidateCall(calledfunc, callingfunc, selftype); b -= 2; } +#endif FillReturns(reg, f, returns, pc+1, C); if (call->Native) From a7fdf4b90d99677acbe294abd6796fbcb5a8d3c0 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 10:58:34 +0100 Subject: [PATCH 39/48] - fixed: M_Init needs to catch VM exceptions from MENUDEF init to print out meaningful error messages in case something goes wrong. --- src/menu/menu.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index 1c8e0b39b..dbe5eb9fb 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -796,7 +796,16 @@ void M_ClearMenus() void M_Init (void) { - M_ParseMenuDefs(); + try + { + M_ParseMenuDefs(); + } + catch (CVMAbortException &err) + { + err.MaybePrintMessage(); + Printf("%s", err.stacktrace); + I_FatalError("Failed to initialize menus"); + } M_CreateMenus(); } From 8a1e0ee4b52ccaacac8c2fdb80a6985d7e3f76c5 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 4 Mar 2017 12:03:31 +0200 Subject: [PATCH 40/48] Fixed compilation with GCC/Clang src/menu/menu.cpp:806:20: error: cannot pass non-trivial object of type 'FString' to variadic function; expected type from format string was 'char *' [-Wnon-pod-varargs] --- src/menu/menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index dbe5eb9fb..a481dd24b 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -803,7 +803,7 @@ void M_Init (void) catch (CVMAbortException &err) { err.MaybePrintMessage(); - Printf("%s", err.stacktrace); + Printf("%s", err.stacktrace.GetChars()); I_FatalError("Failed to initialize menus"); } M_CreateMenus(); From d8cee4d3a5867525f6661f2fac3ea20586b467cf Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 4 Mar 2017 12:17:53 +0200 Subject: [PATCH 41/48] Fixed most of warnings reported by Clang src/scripting/zscript/zcc_compile.cpp: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare] src/p_acs.cpp: warning: format specifies type 'char *' but the argument has type 'PClass *' [-Wformat] --- src/p_acs.cpp | 12 ++++++------ src/scripting/zscript/zcc_compile.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 027b614ea..8c2859479 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -4765,24 +4765,24 @@ static int ScriptCall(unsigned argc, int32_t *args) auto cls = PClass::FindClass(clsname); if (!cls) { - I_Error("ACS call to unknown class in script function %s.%s", cls, funcname); + I_Error("ACS call to unknown class in script function %s.%s", clsname, funcname); } auto funcsym = dyn_cast(cls->Symbols.FindSymbol(funcname, true)); if (funcsym == nullptr) { - I_Error("ACS call to unknown script function %s.%s", cls, funcname); + I_Error("ACS call to unknown script function %s.%s", clsname, funcname); } auto func = funcsym->Variants[0].Implementation; if (func->ImplicitArgs > 0) { - I_Error("ACS call to non-static script function %s.%s", cls, funcname); + I_Error("ACS call to non-static script function %s.%s", clsname, funcname); } TArray params; for (unsigned i = 2; i < argc; i++) { if (func->Proto->ArgumentTypes.Size() < i - 1) { - I_Error("Too many parameters in call to %s.%s", cls, funcname); + I_Error("Too many parameters in call to %s.%s", clsname, funcname); } auto argtype = func->Proto->ArgumentTypes[i - 2]; // The only types allowed are int, bool, double, Name, Sound, Color and String @@ -4812,7 +4812,7 @@ static int ScriptCall(unsigned argc, int32_t *args) } else { - I_Error("Invalid type %s in call to %s.%s", argtype->DescriptiveName(), cls, funcname); + I_Error("Invalid type %s in call to %s.%s", argtype->DescriptiveName(), clsname, funcname); } } if (func->Proto->ArgumentTypes.Size() > params.Size()) @@ -4820,7 +4820,7 @@ static int ScriptCall(unsigned argc, int32_t *args) // Check if we got enough parameters. That means we either cover the full argument list of the function or the next argument is optional. if (!(funcsym->Variants[0].ArgFlags[params.Size()] & VARF_Optional)) { - I_Error("Insufficient parameters in call to %s.%s", cls, funcname); + I_Error("Insufficient parameters in call to %s.%s", clsname, funcname); } } // The return value can be the same types as the parameter types, plus void diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 1f7e66442..d1cb78282 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -613,7 +613,7 @@ void ZCCCompiler::CreateClassTypes() // static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; int incompatiblecnt = 0; - for (int k = 0; k < countof(incompatible); k++) + for (size_t k = 0; k < countof(incompatible); k++) if (incompatible[k] & c->cls->Flags) incompatiblecnt++; if (incompatiblecnt > 1) @@ -1117,7 +1117,7 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; int excludeflags = 0; int fc = 0; - for (int i = 0; i < countof(excludescope); i++) + for (size_t i = 0; i < countof(excludescope); i++) { if (field->Flags & excludescope[i]) { @@ -2176,7 +2176,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool static int exclude[] = { ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static }; int excludeflags = 0; int fc = 0; - for (int i = 0; i < countof(exclude); i++) + for (size_t i = 0; i < countof(exclude); i++) { if (f->Flags & exclude[i]) { @@ -2213,7 +2213,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope, ZCC_VirtualScope }; excludeflags = 0; fc = 0; - for (int i = 0; i < countof(excludescope); i++) + for (size_t i = 0; i < countof(excludescope); i++) { if (f->Flags & excludescope[i]) { From a5edd4899674ec34c836815b338a0e3eac72ccf5 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 4 Mar 2017 12:23:15 +0200 Subject: [PATCH 42/48] Fixed remaining warning reported by Clang src/scripting/zscript/zcc_compile.cpp:2456:54: warning: using the result of an assignment as a condition without parentheses [-Wparentheses] --- src/scripting/zscript/zcc_compile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index d1cb78282..2d5838b06 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2453,7 +2453,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool 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) + if ((sym->Variants[0].Implementation->FuncConst = oldfunc->FuncConst)) sym->Variants[0].Flags |= VARF_ReadOnly; clstype->Virtuals[vindex] = sym->Variants[0].Implementation; From 3879e67cee40493ce32be3912c95014d58f0adf9 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 11:33:57 +0100 Subject: [PATCH 43/48] - the #if was in the wrong place. --- src/scripting/vm/vmexec.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index c11b351c0..cd3925adc 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -664,9 +664,9 @@ begin: VMReturn returns[MAX_RETURNS]; int numret; + b = B; #if 0 // [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; From 5551f3a8c5423ac8a2ee87125f17747ba93d0a26 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 11:48:36 +0100 Subject: [PATCH 44/48] - declared the sectorplanes in Sector as read only and marked all relevant functions in the planes themselves const. This is to block modification of the planes directly. For future-proofness with renderer changes everything that alters these values should go through he function interface. --- src/scripting/thingdef_data.cpp | 4 ++-- wadsrc/static/zscript/base.txt | 1 - wadsrc/static/zscript/mapdata.txt | 16 ++++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index c50b7f65b..a09134d58 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -858,8 +858,8 @@ void InitThingdef() // As a result, the size has to be set to something large and arbritrary because it can change between maps. This will need some serious improvement when things get cleaned up. sectorstruct->AddNativeField("lines", NewPointer(NewResizableArray(NewPointer(linestruct, false)), false), myoffsetof(sector_t, Lines), VARF_Native); - sectorstruct->AddNativeField("ceilingplane", secplanestruct, myoffsetof(sector_t, ceilingplane), VARF_Native); - sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native); + sectorstruct->AddNativeField("ceilingplane", secplanestruct, myoffsetof(sector_t, ceilingplane), VARF_Native | VARF_ReadOnly); + sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native | VARF_ReadOnly); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 2c1fb6661..a15b78de8 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -386,7 +386,6 @@ class Thinker : Object native play class ThinkerIterator : Object native { - native static ThinkerIterator Create(class type = "Actor", int statnum=Thinker.MAX_STATNUM+1); native Thinker Next(bool exact = false); native void Reinit(); diff --git a/wadsrc/static/zscript/mapdata.txt b/wadsrc/static/zscript/mapdata.txt index 4bbfb7097..3d18f9f5e 100644 --- a/wadsrc/static/zscript/mapdata.txt +++ b/wadsrc/static/zscript/mapdata.txt @@ -143,7 +143,7 @@ struct Line native play native int special; native int args[5]; // <--- hexen-style arguments (expanded to ZDoom's full width) native double alpha; // <--- translucency (0=invisibile, FRACUNIT=opaque) - native Side sidedef[2]; + native readonly Side sidedef[2]; native readonly double bbox[4]; // bounding box, for the extent of the LineDef. native readonly Sector frontsector, backsector; native int validcount; // if == validcount, already checked @@ -177,15 +177,15 @@ struct SecPlane native play native double D; native double negiC; - native bool isSlope(); - native int PointOnSide(Vector3 pos); - native double ZatPoint (Vector2 v); - native double ZatPointDist(Vector2 v, double dist); - native bool isEqual(Secplane other); + native bool isSlope() const; + native int PointOnSide(Vector3 pos) const; + native double ZatPoint (Vector2 v) const; + native double ZatPointDist(Vector2 v, double dist) const; + native bool isEqual(Secplane other) const; native void ChangeHeight(double hdiff); - native double GetChangedHeight(double hdiff); + native double GetChangedHeight(double hdiff) const; native double HeightDiff(double oldd, double newd = 0.0); - native double PointToDist(Vector2 xy, double z); + native double PointToDist(Vector2 xy, double z) const; } // This encapsulates all info Doom's original 'special' field contained - for saving and transferring. From b3ba5bfe2c494f2737e9e8d5387173b1008e5bdd Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 12:07:45 +0100 Subject: [PATCH 45/48] - allow 'const' on class functions. This is preferable to 'clearscope' so that the UI code can call getter functions without having to declare things as 'clearscope'. Clearscope is a dangerous context and should be limited to the minimum extent possible and preferably be blocked in user code. This may still need some work on const functions but better have it in now. --- src/scripting/zscript/zcc_compile.cpp | 4 ++++ wadsrc/static/zscript/actor.txt | 8 ++++---- wadsrc/static/zscript/inventory/powerups.txt | 2 +- wadsrc/static/zscript/inventory/weapons.txt | 2 +- wadsrc/static/zscript/shared/player.txt | 8 ++++---- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 2d5838b06..2000075c9 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2141,10 +2141,14 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool // [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. + + // The above is nonsense. This needs to work and needs to be implemented properly. + /* if ((f->Flags & ZCC_FuncConst) && (c->Type()->IsKindOf(RUNTIME_CLASS(PClass)))) { Error(f, "'Const' on a method can only be used in structs"); } + */ if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) { diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 56d9072fa..233f75d40 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -425,7 +425,7 @@ class Actor : Thinker native } } - clearscope virtual String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) + virtual String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) const { if (mod == 'Telefrag') { @@ -450,13 +450,13 @@ class Actor : Thinker native native static int FindUniqueTid(int start = 0, int limit = 0); native void SetShade(color col); - clearscope native string GetTag(string defstr = ""); + native string GetTag(string defstr = "") const; native void SetTag(string defstr = ""); native double GetBobOffset(double frac = 0); native void ClearCounters(); native bool GiveBody (int num, int max=0); native bool HitFloor(); - clearscope native bool isTeammate(Actor other); + native bool isTeammate(Actor other) const; native int PlayerNumber(); native void SetFriendPlayer(PlayerInfo player); native void SoundAlert(Actor target, bool splash = false, double maxdist = 0); @@ -597,7 +597,7 @@ class Actor : Thinker native native void ClearInventory(); native bool GiveInventory(class type, int amount, bool givecheat = false); native bool TakeInventory(class itemclass, int amount, bool fromdecorate = false, bool notakeinfinite = false); - clearscope native Inventory FindInventory(class itemtype, bool subclass = false); + native Inventory FindInventory(class itemtype, bool subclass = false) const; native Inventory GiveInventoryType(class itemtype); native Inventory DropInventory (Inventory item, int amt = -1); native bool UseInventory(Inventory item); diff --git a/wadsrc/static/zscript/inventory/powerups.txt b/wadsrc/static/zscript/inventory/powerups.txt index d4d78d119..f6b14a051 100644 --- a/wadsrc/static/zscript/inventory/powerups.txt +++ b/wadsrc/static/zscript/inventory/powerups.txt @@ -285,7 +285,7 @@ class Powerup : Inventory // //=========================================================================== - virtual clearscope bool isBlinking() + virtual bool isBlinking() const { return (EffectTics <= BLINKTHRESHOLD && (EffectTics & 8) && !bNoScreenBlink); } diff --git a/wadsrc/static/zscript/inventory/weapons.txt b/wadsrc/static/zscript/inventory/weapons.txt index 860d387e2..0eba93bf8 100644 --- a/wadsrc/static/zscript/inventory/weapons.txt +++ b/wadsrc/static/zscript/inventory/weapons.txt @@ -89,7 +89,7 @@ class Weapon : StateProvider native return s; } - override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) + override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) const { // Weapons may never return HitObituary by default. Override this if it is needed. return Obituary; diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index d6ea5b843..cbb361687 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -119,7 +119,7 @@ class PlayerPawn : Actor native } } - override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) + override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) const { if (victim.player != player && victim.IsTeammate(self)) { @@ -150,7 +150,7 @@ class PlayerPawn : Actor native } // This is for SBARINFO. - clearscope int, int GetEffectTicsForItem(class item) + int, int GetEffectTicsForItem(class item) const { let pg = (class)(item); if (pg != null) @@ -167,10 +167,10 @@ class PlayerPawn : Actor native return -1, -1; } - clearscope native int GetMaxHealth(bool withupgrades = false); + native int GetMaxHealth(bool withupgrades = false) const; native bool ResetAirSupply (bool playgasp = false); native void CheckWeaponSwitch(class item); - clearscope native static String GetPrintableDisplayName(Class cls); + native clearscope static String GetPrintableDisplayName(Class cls); } From 4c5794b6d53269038732f5ac4cad472f6acef29f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 12:11:56 +0100 Subject: [PATCH 46/48] - made GetObituary non-constant. This gets only called from within the play code and making it const would block potential use cases that involve changing some internal variables like counters. --- wadsrc/static/zscript/actor.txt | 2 +- wadsrc/static/zscript/inventory/weapons.txt | 2 +- wadsrc/static/zscript/shared/player.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 233f75d40..029aebc0a 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -425,7 +425,7 @@ class Actor : Thinker native } } - virtual String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) const + virtual String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) { if (mod == 'Telefrag') { diff --git a/wadsrc/static/zscript/inventory/weapons.txt b/wadsrc/static/zscript/inventory/weapons.txt index 0eba93bf8..860d387e2 100644 --- a/wadsrc/static/zscript/inventory/weapons.txt +++ b/wadsrc/static/zscript/inventory/weapons.txt @@ -89,7 +89,7 @@ class Weapon : StateProvider native return s; } - override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) const + override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) { // Weapons may never return HitObituary by default. Override this if it is needed. return Obituary; diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index cbb361687..b13b84015 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -119,7 +119,7 @@ class PlayerPawn : Actor native } } - override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) const + override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) { if (victim.player != player && victim.IsTeammate(self)) { From 7dbc6939c4c7dddb74698f94b6a9f979befd45d0 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 12:43:07 +0100 Subject: [PATCH 47/48] - declared most native getters in Actor as const. - made the self pointer of const functions readonly. This seems to work fine. Both calling a non-const function and trying to assign a value to a member fail with an error message. --- src/scripting/thingdef.cpp | 3 +- src/scripting/zscript/zcc_compile.cpp | 9 ++++++ wadsrc/static/zscript/actor.txt | 40 +++++++++++++-------------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/scripting/thingdef.cpp b/src/scripting/thingdef.cpp index d9eca5fca..c0e444893 100644 --- a/src/scripting/thingdef.cpp +++ b/src/scripting/thingdef.cpp @@ -110,12 +110,13 @@ void SetImplicitArgs(TArray *args, TArray *argflags, TArrayPush(NewPointer(cls)); + if (args != nullptr) args->Push(NewPointer(cls, !!(funcflags & VARF_ReadOnly))); if (argflags != nullptr) argflags->Push(VARF_Implicit | VARF_ReadOnly); if (argnames != nullptr) argnames->Push(NAME_self); } if (funcflags & VARF_Action) { + assert(!(funcflags & VARF_ReadOnly)); // implied caller and callingstate pointers if (args != nullptr) { diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 2000075c9..f8e9980a2 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2156,6 +2156,15 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } if (f->Flags & ZCC_Action) { + if (varflags & VARF_ReadOnly) + { + Error(f, "Action functions cannot be declared const"); + varflags &= ~VARF_ReadOnly; + } + if (varflags & VARF_UI) + { + Error(f, "Action functions cannot be declared UI"); + } // Non-Actors cannot have action functions. if (!c->Type()->IsKindOf(RUNTIME_CLASS(PClassActor))) { diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 029aebc0a..ec5ce71d8 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -468,15 +468,15 @@ class Actor : Thinker native native bool UpdateWaterLevel (bool splash = true); native bool IsZeroDamage(); native void ClearInterpolation(); - native Vector3 PosRelative(sector sec); + native Vector3 PosRelative(sector sec) const; native void HandleSpawnFlags(); native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false); native void RestoreDamage(); - native int SpawnHealth(); + native int SpawnHealth() const; native void SetDamage(int dmg); - native double Distance2D(Actor other); - native double Distance3D(Actor other); + native double Distance2D(Actor other) const; + native double Distance3D(Actor other) const; native void SetOrigin(vector3 newpos, bool moving); native void SetXYZ(vector3 newpos); native Actor GetPointer(int aaptr); @@ -511,7 +511,7 @@ class Actor : Thinker native native void BloodSplatter (Vector3 pos, double hitangle, bool axe = false); native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false); native void PlaySpawnSound(Actor missile); - native bool CountsAsKill(); + native bool CountsAsKill() const; native bool Teleport(Vector3 pos, double angle, int flags); native void TraceBleed(int damage, Actor missile); @@ -559,21 +559,21 @@ class Actor : Thinker native native void LinkToWorld(LinkContext ctx = null); native void UnlinkFromWorld(out LinkContext ctx = null); native bool CanSeek(Actor target); - native double AngleTo(Actor target, bool absolute = false); + native double AngleTo(Actor target, bool absolute = false) const; native void AddZ(double zadd, bool moving = true); native void SetZ(double z); - native vector2 Vec2To(Actor other); - native vector3 Vec3To(Actor other); - native vector3 Vec3Offset(double x, double y, double z, bool absolute = false); - native vector3 Vec3Angle(double length, double angle, double z = 0, bool absolute = false); - native vector2 Vec2Angle(double length, double angle, bool absolute = false); - native vector2 Vec2Offset(double x, double y, bool absolute = false); - native vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false); - native void VelFromAngle(double speed = 0, double angle = 0); - native void Vel3DFromAngle(double speed, double angle, double pitch); + native vector2 Vec2To(Actor other) const; + native vector3 Vec3To(Actor other) const; + native vector3 Vec3Offset(double x, double y, double z, bool absolute = false) const; + native vector3 Vec3Angle(double length, double angle, double z = 0, bool absolute = false) const; + native vector2 Vec2Angle(double length, double angle, bool absolute = false) const; + native vector2 Vec2Offset(double x, double y, bool absolute = false) const; + native vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false) const; + native void VelFromAngle(double speed = 0, double angle = 0) const; + native void Vel3DFromAngle(double speed, double angle, double pitch) const; native void Thrust(double speed = 0, double angle = 0); - native bool isFriend(Actor other); - native bool isHostile(Actor other); + native bool isFriend(Actor other) const; + native bool isHostile(Actor other) const; native void AdjustFloorClip(); native DropItem GetDropItems(); native void CopyFriendliness (Actor other, bool changeTarget, bool resetHealth = true); @@ -588,8 +588,8 @@ class Actor : Thinker native native void Howl(); native void DrawSplash (int count, double angle, int kind); native void GiveSecret(bool printmsg = true, bool playsound = true); - native double GetCameraHeight(); - native double GetGravity(); + native double GetCameraHeight() const; + native double GetGravity() const; native bool CheckClass(class checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false); native void AddInventory(Inventory inv); @@ -613,7 +613,7 @@ class Actor : Thinker native native double GetDistance(bool checkz, int ptr = AAPTR_TARGET); native double GetAngle(int flags, int ptr = AAPTR_TARGET); native double GetZAt(double px = 0, double py = 0, double angle = 0, int flags = 0, int pick_pointer = AAPTR_DEFAULT); - native int GetSpawnHealth(); + native int GetSpawnHealth() const; native double GetCrouchFactor(int ptr = AAPTR_PLAYER1); native double GetCVar(string cvar); native int GetPlayerInput(int inputnum, int ptr = AAPTR_DEFAULT); From cfafbfe4f2284a047babd457ac914e79ff82ea14 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 4 Mar 2017 12:55:20 +0100 Subject: [PATCH 48/48] - removed FStringNoInit. This was development garbage. --- src/zstring.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/zstring.h b/src/zstring.h index b925a39b1..e486a0e8b 100644 --- a/src/zstring.h +++ b/src/zstring.h @@ -450,13 +450,4 @@ template<> struct THashTraits int Compare(const FString &left, const FString &right) { return left.Compare(right); } }; -class FStringNoInit -{ - char mem[sizeof(FString)]; - operator FString&() - { - return *reinterpret_cast(&mem); - } -}; - #endif