add support for experimental safe-mode which disables some functionality

This commit is contained in:
Pekka Laiho 2020-12-05 11:24:31 +07:00
parent 02e5176612
commit 35224bce8a
3 changed files with 80 additions and 65 deletions

View File

@ -6,14 +6,16 @@ class Evaller
protected Tokenizer $tokenizer; protected Tokenizer $tokenizer;
protected Reader $reader; protected Reader $reader;
protected Printer $printer; protected Printer $printer;
protected bool $safemode;
protected bool $debug = false; protected bool $debug = false;
public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer) public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer, bool $safemode)
{ {
$this->tokenizer = $tokenizer; $this->tokenizer = $tokenizer;
$this->reader = $reader; $this->reader = $reader;
$this->printer = $printer; $this->printer = $printer;
$this->safemode = $safemode;
} }
public function eval($ast, Env $env) public function eval($ast, Env $env)
@ -116,7 +118,7 @@ class Evaller
$ast = $astData[$astLength - 1]; $ast = $astData[$astLength - 1];
continue; // tco continue; // tco
} elseif ($symbolName == 'env') { } elseif (!$this->safemode && $symbolName == 'env') {
if ($astLength >= 2) { if ($astLength >= 2) {
if (!($astData[1] instanceof Symbol)) { if (!($astData[1] instanceof Symbol)) {
throw new MadLispException("first argument to env is not symbol"); throw new MadLispException("first argument to env is not symbol");
@ -126,7 +128,7 @@ class Evaller
} else { } else {
return $env; return $env;
} }
} elseif ($symbolName == 'eval') { } elseif (!$this->safemode && $symbolName == 'eval') {
if ($astLength == 1) { if ($astLength == 1) {
return null; return null;
} }
@ -207,10 +209,13 @@ class Evaller
$ast = $astData[2]; $ast = $astData[2];
$env = $newEnv; $env = $newEnv;
continue; // tco continue; // tco
} elseif ($symbolName == 'load') { } elseif (!$this->safemode && $symbolName == 'load') {
// Load is here because we want to load into // Load is here because we want to load into
// current $env which is hard otherwise. // current $env which is hard otherwise.
// This is disabled now for safe-mode, but some
// use (maybe restricted) might need to be allowed.
if ($astLength != 2) { if ($astLength != 2) {
throw new MadLispException("load requires exactly 1 argument"); throw new MadLispException("load requires exactly 1 argument");
} }

View File

@ -20,30 +20,36 @@ class Core implements ILib
protected Reader $reader; protected Reader $reader;
protected Printer $printer; protected Printer $printer;
protected Evaller $evaller; protected Evaller $evaller;
protected bool $safemode;
public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer, Evaller $evaller) public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer, Evaller $evaller, bool $safemode)
{ {
$this->tokenizer = $tokenizer; $this->tokenizer = $tokenizer;
$this->reader = $reader; $this->reader = $reader;
$this->printer = $printer; $this->printer = $printer;
$this->evaller = $evaller; $this->evaller = $evaller;
$this->safemode = $safemode;
} }
public function register(Env $env): void public function register(Env $env): void
{ {
// Register special constants // Register special constants
$env->set('__FILE__', null); if (!$this->safemode) {
$env->set('__DIR__', null); $env->set('__FILE__', null);
$env->set('__DIR__', null);
}
$env->set('debug', new CoreFunc('debug', 'Toggle debug mode.', 0, 0, if (!$this->safemode) {
function () { $env->set('debug', new CoreFunc('debug', 'Toggle debug mode.', 0, 0,
$val = !$this->evaller->getDebug(); function () {
$this->evaller->setDebug($val); $val = !$this->evaller->getDebug();
return $val; $this->evaller->setDebug($val);
} return $val;
)); }
));
}
$env->set('doc', new CoreFunc('doc', 'Get or set documentation for a function.', 1, 2, $env->set('doc', new CoreFunc('doc', 'Get or set documentation string for a function.', 1, 2,
function (Func $a, ?string $str = null) { function (Func $a, ?string $str = null) {
if (func_num_args() == 1) { if (func_num_args() == 1) {
return $a->getDoc(); return $a->getDoc();
@ -63,53 +69,63 @@ class Core implements ILib
} }
)); ));
$env->set('meta', new CoreFunc('meta', 'Read meta information of an entity.', 2, 2, if (!$this->safemode) {
function ($obj, $attribute) { $env->set('meta', new CoreFunc('meta', 'Read meta information of an entity.', 2, 2,
if ($obj instanceof Env) { function ($obj, $attribute) {
if ($attribute == 'name') { if ($obj instanceof Env) {
return $obj->getFullName(); if ($attribute == 'name') {
} elseif ($attribute == 'parent') { return $obj->getFullName();
return $obj->getParent(); } elseif ($attribute == 'parent') {
return $obj->getParent();
} else {
throw new MadLispException('unknown attribute for meta');
}
} elseif ($obj instanceof UserFunc) {
if ($attribute == 'args') {
return $obj->getBindings();
} elseif ($attribute == 'body') {
return $obj->getAst();
} elseif ($attribute == 'code') {
return new MList([new Symbol('fn'), $obj->getBindings(), $obj->getAst()]);
} else {
throw new MadLispException('unknown attribute for meta');
}
} else { } else {
throw new MadLispException('unknown attribute for meta'); throw new MadLispException('unknown entity for meta');
} }
} elseif ($obj instanceof UserFunc) {
if ($attribute == 'args') {
return $obj->getBindings();
} elseif ($attribute == 'body') {
return $obj->getAst();
} elseif ($attribute == 'code') {
return new MList([new Symbol('fn'), $obj->getBindings(), $obj->getAst()]);
} else {
throw new MadLispException('unknown attribute for meta');
}
} else {
throw new MadLispException('unknown entity for meta');
} }
} ));
)); }
$env->set('read', new CoreFunc('read', 'Read string as code.', 1, 1, if (!$this->safemode) {
fn (string $a) => $this->reader->read($this->tokenizer->tokenize($a)) $env->set('read', new CoreFunc('read', 'Read string as code.', 1, 1,
)); fn (string $a) => $this->reader->read($this->tokenizer->tokenize($a))
));
}
$env->set('print', new CoreFunc('print', 'Print argument. Give second argument as true to show strings in readable format.', 1, 2, if (!$this->safemode) {
function ($a, bool $readable = false) { $env->set('print', new CoreFunc('print', 'Print argument. Give second argument as true to show strings in readable format.', 1, 2,
$this->printer->print($a, $readable); function ($a, bool $readable = false) {
return null; $this->printer->print($a, $readable);
} return null;
)); }
));
}
// This is allowed in safe-mode, because the evaluation should be wrapped in a try-catch in embedded use.
$env->set('error', new CoreFunc('error', 'Throw an exception using argument (string) as message.', 1, 1, $env->set('error', new CoreFunc('error', 'Throw an exception using argument (string) as message.', 1, 1,
function (string $error) { function (string $error) {
// We should probably use another exception type to distinguish user-thrown errors from built-in errors.
throw new MadLispException($error); throw new MadLispException($error);
} }
)); ));
$env->set('exit', new CoreFunc('exit', 'Terminate the script with given exit code.', 0, 1, if (!$this->safemode) {
function (int $status = 0) { $env->set('exit', new CoreFunc('exit', 'Terminate the script with given exit code.', 0, 1,
exit($status); function (int $status = 0) {
} exit($status);
)); }
));
}
} }
} }

View File

@ -3,25 +3,22 @@ namespace MadLisp;
class LispFactory class LispFactory
{ {
public function make(array $coreLibs = [], array $userLibs = []): Lisp public function make(bool $safemode = false): Lisp
{ {
$tokenizer = new Tokenizer(); $tokenizer = new Tokenizer();
$reader = new Reader(); $reader = new Reader();
$printer = new Printer(); $printer = new Printer();
$eval = new Evaller($tokenizer, $reader, $printer); $eval = new Evaller($tokenizer, $reader, $printer, $safemode);
// Root environment // Root environment
$env = new Env('root'); $env = new Env('root');
// Register core functions // Register core functions
(new Lib\Core($tokenizer, $reader, $printer, $eval))->register($env); (new Lib\Core($tokenizer, $reader, $printer, $eval, $safemode))->register($env);
// Register core libraries // Register core libraries
(new Lib\Collections())->register($env); (new Lib\Collections())->register($env);
(new Lib\Compare())->register($env); (new Lib\Compare())->register($env);
(new Lib\Database())->register($env);
(new Lib\Http())->register($env);
(new Lib\IO())->register($env);
(new Lib\Json())->register($env); (new Lib\Json())->register($env);
(new Lib\Math())->register($env); (new Lib\Math())->register($env);
(new Lib\Regex())->register($env); (new Lib\Regex())->register($env);
@ -29,19 +26,16 @@ class LispFactory
(new Lib\Time())->register($env); (new Lib\Time())->register($env);
(new Lib\Types())->register($env); (new Lib\Types())->register($env);
// Register additional libs for root env // Register unsafe libraries if not in safemode
foreach ($coreLibs as $lib) { if (!$safemode) {
$lib->register($env); (new Lib\Database())->register($env);
(new Lib\Http())->register($env);
(new Lib\IO())->register($env);
} }
// User environment // User environment
$env = new Env('user', $env); $env = new Env('user', $env);
// Register additional libs for user env
foreach ($userLibs as $lib) {
$lib->register($env);
}
return new Lisp($tokenizer, $reader, $eval, $printer, $env); return new Lisp($tokenizer, $reader, $eval, $printer, $env);
} }
} }