Skip to content

Commit

Permalink
Add scope checks to private and protected properties
Browse files Browse the repository at this point in the history
  • Loading branch information
thekid committed May 18, 2023
1 parent 9830d04 commit ed5003f
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 4 deletions.
29 changes: 25 additions & 4 deletions src/main/php/lang/ast/emit/PropertyHooks.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace lang\ast\emit;

use lang\ast\Code;
use lang\ast\nodes\{
Assignment,
Block,
Expand Down Expand Up @@ -40,6 +41,26 @@ protected function rewriteHook($node, $name, $virtual, $literal) {
return $node;
}

protected function withScopeCheck($modifiers, $name, $node) {
if ($modifiers & MODIFIER_PRIVATE) {
$check= (
'$scope= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]["class"] ?? null;'.
'if (__CLASS__ !== $scope && \\lang\\VirtualProperty::class !== $scope)'.
'throw new \\Error("Cannot access private property ".__CLASS__."::\\$%1$s");'
);
} else if ($modifiers & MODIFIER_PROTECTED) {
$check= (
'$scope= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]["class"] ?? null;'.
'if (__CLASS__ !== $scope && !is_subclass_of($scope, __CLASS__) && \\lang\\VirtualProperty::class !== $scope)'.
'throw new \\Error("Cannot access protected property ".__CLASS__."::\\$%1$s");'
);
} else {
return $node;
}

return new Block([new Code(sprintf($check, $name)), $node]);
}

protected function emitProperty($result, $property) {
static $lookup= [
'public' => MODIFIER_PUBLIC,
Expand Down Expand Up @@ -88,10 +109,10 @@ protected function emitProperty($result, $property) {
)],
null // $hook->annotations
));
$get= new ReturnStatement(new InvokeExpression(
$get= $this->withScopeCheck($modifiers, $property->name, new ReturnStatement(new InvokeExpression(
new InstanceExpression(new Variable('this'), new Literal($method)),
[]
));
)));
} else if ('set' === $type) {
$this->emitOne($result, new Method(
$modifierList,
Expand All @@ -105,10 +126,10 @@ protected function emitProperty($result, $property) {
)],
null // $hook->annotations
));
$set= new InvokeExpression(
$set= $this->withScopeCheck($modifiers, $property->name, new InvokeExpression(
new InstanceExpression(new Variable('this'), new Literal($method)),
[new Variable('value')]
);
));
}
}

Expand Down
70 changes: 70 additions & 0 deletions src/test/php/lang/ast/unittest/emit/PropertyHooksTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,74 @@ public function run() {

Assert::equals(4, $r);
}

#[Test]
public function accessing_private_property() {
$r= $this->run('class <T> {
private string $test { get => "Test"; }
public function run() {
return $this->test;
}
}');

Assert::equals('Test', $r);
}

#[Test]
public function accessing_protected_property() {
$r= $this->run('class <T> {
protected string $test { get => "Test"; }
public function run() {
return $this->test;
}
}');

Assert::equals('Test', $r);
}

#[Test, Expect(class: Error::class, message: '/Cannot access private property .+test/')]
public function accessing_private_property_from_outside() {
$r= $this->run('class <T> {
private string $test { get => "Test"; }
public function run() {
return $this;
}
}');

$r->test;
}

#[Test, Expect(class: Error::class, message: '/Cannot access protected property .+test/')]
public function accessing_protected_property_from_outside() {
$r= $this->run('class <T> {
protected string $test { get => "Test"; }
public function run() {
return $this;
}
}');

$r->test;
}

#[Test]
public function accessing_private_property_reflectively() {
$t= $this->type('class <T> {
private string $test { get => "Test"; }
}');

Assert::equals('Test', $t->getField('test')->setAccessible(true)->get($t->newInstance()));
}

#[Test]
public function accessing_protected_property_reflectively() {
$t= $this->type('class <T> {
protected string $test { get => "Test"; }
}');

Assert::equals('Test', $t->getField('test')->setAccessible(true)->get($t->newInstance()));
}
}

0 comments on commit ed5003f

Please sign in to comment.