Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions polymod/hscript/_internal/Expr.hx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ enum Error
EInvalidIterator(v:String);
EInvalidOp(op:String);
EInvalidAccess(f:String);
EPrivateField(f:String);
EInvalidModule(m:String);
EBlacklistedModule(m:String);
EBlacklistedField(f:String);
Expand All @@ -180,6 +181,7 @@ enum Error
EClassInvalidSuper; // Accessing "super" in a parentless class
EScriptThrow(v:Dynamic); // Script called "throw"
EScriptCallThrow(v:Dynamic); // Script called a function which threw
EInvalidAccessorCombination(accessors:Array<String>);
// Fallback error type.
ECustom(msg:String);
}
Expand Down
186 changes: 176 additions & 10 deletions polymod/hscript/_internal/Interp.hx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import polymod.util.Util;
import haxe.PosInfos;
import haxe.Constraints.IMap;

using Lambda;
using StringTools;

private enum Stop
Expand Down Expand Up @@ -76,6 +77,8 @@ class Interp
var curExpr:Expr;
#end

var inPrivateAccess:Bool = false;

function getClassDecl():Null<ClassDecl>
{
if (_classDeclOverride != null)
Expand Down Expand Up @@ -123,23 +126,35 @@ class Interp
return Type.createInstance(defaultVariables.get(cl), args);
}

function tryBuildClass(clsRef:PolymodStaticClassReference, args:Array<Dynamic>):Null<Dynamic>
{
if (clsRef.cls != getClassDecl() && !clsRef.canInstantiate)
{
error(ECustom('Cannot access private constructor of "${clsRef.cls.name}"'));
return null;
}
return clsRef.instantiate(args);
}

// Try to retrieve a scripted class with this name in the same package.
if (getClassDecl().pkg != null && getClassDecl().pkg.length > 0)
{
var localClassId = getClassDecl().pkg.join('.') + "." + cl;
var clsRef = PolymodStaticClassReference.tryBuild(localClassId);
if (clsRef != null) return clsRef.instantiate(args);
if (clsRef != null) return tryBuildClass(clsRef, args);
}

// Try to retrieve a scripted class with this name in the base package.
var clsRef = PolymodStaticClassReference.tryBuild(cl);
if (clsRef != null) return clsRef.instantiate(args);
if (clsRef != null) return tryBuildClass(clsRef, args);

@:privateAccess
if (getClassDecl().imports != null && getClassDecl().imports.exists(cl))
{
var clsRef = PolymodStaticClassReference.tryBuild(getClassDecl().imports.get(cl).fullPath);
if (clsRef != null) return clsRef.instantiate(args);
if (clsRef != null) return tryBuildClass(clsRef, args);
}

@:privateAccess
if (getClassDecl()?.pkg != null)
{
Expand All @@ -148,7 +163,15 @@ class Interp
if (_scriptClassDescriptors.exists(packagedClass))
{
// OVERRIDE CHANGE: Create a PolymodScriptClass instead of a ScriptClass
var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(packagedClass), args);
var clsDescriptor:ClassDecl = findScriptClassDescriptor(packagedClass);
var ctorField:Null<FieldDecl> = clsDescriptor.fields.find((f) -> f.name == 'new');
if (clsDescriptor != getClassDecl() && ctorField?.access.contains(APrivate))
{
error(ECustom('Cannot access private constructor of ${clsRef.cls.name}'));
return null;
}

var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(clsDescriptor, args);
return proxy;
}
}
Expand All @@ -159,7 +182,14 @@ class Interp
if (_scriptClassDescriptors.exists(importedClass.fullPath))
{
// OVERRIDE CHANGE: Create a PolymodScriptClass instead of a ScriptClass
var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(importedClass.fullPath), args);
var clsDescriptor:ClassDecl = findScriptClassDescriptor(importedClass.fullPath);
var ctorField:Null<FieldDecl> = clsDescriptor.fields.find((f) -> f.name == 'new');
if (clsDescriptor != getClassDecl() && ctorField?.access.contains(APrivate))
{
error(ECustom('Cannot access private constructor of ${clsRef.cls.name}'));
return null;
}
var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(clsDescriptor, args);
return proxy;
}

Expand Down Expand Up @@ -262,6 +292,15 @@ class Interp
// Force call super function.
return o.scriptCallSuper(f, args);
}

#if POLYMOD_STRICT_SYNTAX
if (!checkPrivateAccess(o, f))
{
error(EPrivateField(f));
return null;
}
#end

else if (Std.isOfType(o, PolymodStaticAbstractReference))
{
var ref:PolymodStaticAbstractReference = cast(o, PolymodStaticAbstractReference);
Expand Down Expand Up @@ -776,9 +815,7 @@ class Interp
return assignValue(e1, expr(e2));
}

function assignValue(e1:Expr,
v:Dynamic,
_abstractInlineAssign:Bool = false):Null<Dynamic>
function assignValue(e1:Expr, v:Dynamic, _abstractInlineAssign:Bool = false):Null<Dynamic>
{
switch (Tools.expr(e1))
{
Expand Down Expand Up @@ -2006,8 +2043,23 @@ class Interp
restore(old);
return val;
}
case EMeta(_, _, e):
return expr(e);
case EMeta(name, args, e):
switch (name)
{
case ':privateAccess':
// Check to make sure we aren't already in a private access block.
// Otherwise the state'll be overwritten and the original block won't work anymore.
if (!inPrivateAccess)
{
inPrivateAccess = true;
var obj = expr(e); // We evaulate the expression knowing we're in a private access.
inPrivateAccess = false;
return obj;
}
return expr(e);
default:
return expr(e);
}
case ECheckType(e, _):
return expr(e);
}
Expand Down Expand Up @@ -2432,6 +2484,104 @@ class Interp
return null;
}

function checkPrivateAccess(o:Dynamic, f:String):Bool
{
// If we're in a private access block, automatically allow it.
if (inPrivateAccess)
return true;

// First, script classes.
if (Std.isOfType(o, PolymodStaticClassReference))
{
var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference);

// We are retrieving this field within the same script class context.
if (o.cls == getClassDecl())
return true;

var field:Null<FieldDecl> = PolymodScriptClass.scriptInterp.getScriptClassStaticFieldDecl(ref.getFullyQualifiedName(), f);
if (field != null && field.access.contains(APrivate))
{
return false;
}
}
else if (Std.isOfType(o, PolymodScriptClass))
{
var ref:PolymodAbstractScriptClass = cast(o, PolymodAbstractScriptClass);

if (ref.fullyQualifiedName == getClassFullyQualifiedName())
return true;

// If this script class shares the same superclasses with this class we're in (inheritance) then we can access it if the field is any of those.
var superClasses:Array<String> = PolymodScriptClass.getSuperClasses(getClassDecl()) ?? [];
var inheritatedSuperClasses:Array<String> = [ref.fullyQualifiedName].concat(PolymodScriptClass.getSuperClasses(ref._c)).filter((superCls:String) -> return superClasses.contains(superCls));

var superClass:Dynamic = ref.superClass;
while (superClass != null)
{
if (Std.isOfType(superClass, PolymodScriptClass))
{
var scriptCls:PolymodScriptClass = cast(superClass, PolymodScriptClass);
if (inheritatedSuperClasses.contains(scriptCls.fullyQualifiedName))
{
var fieldDecl = scriptCls.findField(f);
if (fieldDecl != null && fieldDecl.access.contains(APrivate))
{
return true;
}
}
superClass = superClass.superClass;
}
else
{
var superClsName:String = Util.getTypeNameOf(superClass);
if (inheritatedSuperClasses.contains(superClsName))
{
if (PolymodFinalMacro.getPrivateFieldsOf(superClsName).contains(f))
{
return true;
}
}
superClass = Type.getSuperClass(superClass);
}
}

// Regular check the script class itself has within the script class itself.
var fieldDecl:Null<FieldDecl> = ref.findField(f);
if (fieldDecl != null && fieldDecl.access.contains(APrivate))
{
return false;
}
}
else if (Std.isOfType(o, PolymodStaticAbstractReference))
{
// Abstracts can't have private instance fields.
return true;
}
else
{
// We're checking for fields from within a regular class.
var superClasses:Array<String> = PolymodScriptClass.getSuperClasses(getClassDecl()) ?? [];
var inheritatedSuperClasses:Array<String> = [Util.getTypeNameOf(o)].concat(Util.getSuperClasses(o) ?? []).filter((superCls:String) -> return superClasses.contains(superCls));

for (cls in inheritatedSuperClasses)
{
if (PolymodFinalMacro.getPrivateFields(cls).contains(f))
{
return true;
}
}

// Check from within the class itself.
if (PolymodFinalMacro.getPrivateFieldsOf(o).contains(f))
{
return false;
}
}

return true;
}

function get(o:Dynamic, f:String):Null<Dynamic>
{
if (o == null) error(ENullObjectReference(f));
Expand All @@ -2447,6 +2597,14 @@ class Interp
var oCls:String = Util.getTypeNameOf(o);
#if hl oCls = oCls.replace('$', ''); #end

#if POLYMOD_STRICT_SYNTAX
if (!checkPrivateAccess(o, f))
{
error(EPrivateField(f));
return null;
}
#end

// Check if the field is a blacklisted static field.
if (PolymodScriptClass.blacklistedStaticFields.exists(o) && PolymodScriptClass.blacklistedStaticFields.get(o).contains(f))
{
Expand Down Expand Up @@ -2563,6 +2721,14 @@ class Interp
var oCls:String = Util.getTypeNameOf(o);
#if hl oCls = oCls.replace('$', ''); #end

#if POLYMOD_STRICT_SYNTAX
if (!checkPrivateAccess(o, f))
{
error(EPrivateField(f));
return null;
}
#end

// Check if the field is a blacklisted static field.
if (PolymodScriptClass.blacklistedStaticFields.exists(o) && PolymodScriptClass.blacklistedStaticFields.get(o).contains(f))
{
Expand Down
33 changes: 31 additions & 2 deletions polymod/hscript/_internal/Parser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class Parser
var idents:Array<Bool>;
var uid:Int = 0;

var packageSet:Bool = false;

#if hscriptPos
var origin:String;
var tokenMin:Int;
Expand Down Expand Up @@ -1463,6 +1465,13 @@ class Parser
switch (ident)
{
case "package":
#if POLYMOD_STRICT_SYNTAX
if (!packageSet)
packageSet = true;
else
error(ECustom("Unknown identifier: package"), currentPos, currentPos); // Throw an error if there was a package already set.
#end

var path = parsePath();
ensure(TSemicolon);
return DPackage(path);
Expand Down Expand Up @@ -1664,16 +1673,29 @@ class Parser
case "override":
access.push(AOverride);
case "public":
access.push(APublic);
// Throw an error if the user tries declaring a variable as public when it's already been declared private.
if (access.contains(APrivate))
error(ECustom("Conflicting access modifier public"), currentPos, currentPos);
else if (!access.contains(APublic))
access.push(APublic);
case "private":
access.push(APrivate);
// Throw an error if the user tries declaring a variable as private when it's already been declared public.
if (access.contains(APublic))
error(ECustom("Conflicting access modifier private"), currentPos, currentPos);
else if (!access.contains(APrivate))
access.push(APrivate);
case "inline":
access.push(AInline);
case "static":
access.push(AStatic);
case "macro":
access.push(AMacro);
case "function":
if (access.contains(AOverride) && access.contains(AStatic))
{
error(EInvalidAccessorCombination(['override', 'static']), currentPos, currentPos);
}

var name = getIdent();
var inf = parseFunctionDecl();
maybe(TSemicolon);
Expand Down Expand Up @@ -1714,6 +1736,13 @@ class Parser
else
ensure(TSemicolon);

#if POLYMOD_STRICT_SYNTAX
if (access.contains(AInline) && !access.contains(AStatic))
{
error(ECustom('Invalid modifier: inline on non-static variable'), currentPos, currentPos);
}
#end

return {
name: name,
meta: meta,
Expand Down
Loading