Merge pull request #38 from ValKmjolnir/develop

 optimize import module & add limited execution mode (disable unsafe system api)
This commit is contained in:
ValK 2024-02-21 19:59:42 +08:00 committed by GitHub
commit 2e321fc4d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 266 additions and 98 deletions

View File

@ -30,6 +30,7 @@ const u32 VM_SYMINFO = 1<<7;
const u32 VM_PROFILE = 1<<8;
const u32 VM_PROF_ALL = 1<<9;
const u32 VM_REF_FILE = 1<<10;
const u32 VM_LIMIT = 1<<11;
std::ostream& help(std::ostream& out) {
out
@ -59,6 +60,7 @@ std::ostream& help(std::ostream& out) {
<< " --prof | show profiling result, available in debug mode.\n"
<< " --prof-all | show profiling result of all files,"
<< "available under debug mode.\n"
<< " --limit | use limited execution mode."
<< "file:\n"
<< " <filename> | execute file.\n"
<< "argv:\n"
@ -75,13 +77,18 @@ std::ostream& logo(std::ostream& out) {
<< " / \\/ / _` / __|/ _` | |\n"
<< " / /\\ / (_| \\__ \\ (_| | |\n"
<< " \\_\\ \\/ \\__,_|___/\\__,_|_|\n"
<< "ver : " << __nasver << " (" << __DATE__ << " " << __TIME__ << ")\n"
<< "\n"
<< "ver : " << __nasver__
<< " " << nasal::get_platform() << " " << nasal::get_arch()
<< " (" << __DATE__ << " " << __TIME__ << ")\n"
<< "std : c++ " << __cplusplus << "\n"
<< "core : " << std::thread::hardware_concurrency() << " core(s)\n"
<< "repo : https://github.com/ValKmjolnir/Nasal-Interpreter\n"
<< "repo : https://gitee.com/valkmjolnir/Nasal-Interpreter\n"
<< "wiki : https://wiki.flightgear.org/Nasal_scripting_language\n"
<< "\n"
<< "presented by fgprc members - http://fgprc.org.cn\n"
<< "\n"
<< "input <nasal -h> to get help .\n\n";
return out;
}
@ -95,7 +102,8 @@ std::ostream& version(std::ostream& out) {
if (num<0.01) {
nasal::parse::easter_egg();
}
out << "nasal interpreter version " << __nasver;
out << "nasal interpreter version " << __nasver__;
out << " " << nasal::get_platform() << " " << nasal::get_arch();
out << " (" << __DATE__ << " " << __TIME__ << ")\n";
return out;
}
@ -127,12 +135,11 @@ void execute(
// parser gets lexer's token list to compile
parse.compile(lex).chkerr();
if (cmd&VM_RAW_AST) {
auto dumper = std::unique_ptr<nasal::ast_dumper>(new nasal::ast_dumper);
dumper->dump(parse.tree());
nasal::ast_dumper().dump(parse.tree());
}
// linker gets parser's ast and load import files to this ast
ld.link(parse, file, cmd&VM_DETAIL).chkerr();
ld.link(parse, cmd&VM_DETAIL).chkerr();
if (cmd&VM_REF_FILE) {
if (ld.get_file_list().size()) {
std::cout << "referenced file(s):\n";
@ -146,12 +153,11 @@ void execute(
auto opt = std::unique_ptr<nasal::optimizer>(new nasal::optimizer);
opt->do_optimization(parse.tree());
if (cmd&VM_AST) {
auto dumper = std::unique_ptr<nasal::ast_dumper>(new nasal::ast_dumper);
dumper->dump(parse.tree());
nasal::ast_dumper().dump(parse.tree());
}
// code generator gets parser's ast and import file list to generate code
gen.compile(parse, ld, false).chkerr();
gen.compile(parse, ld, false, cmd&VM_LIMIT).chkerr();
if (cmd&VM_CODE) {
gen.print(std::cout);
}
@ -167,6 +173,7 @@ void execute(
} else if (cmd&VM_TIME || cmd&VM_EXEC) {
auto runtime = std::unique_ptr<nasal::vm>(new nasal::vm);
runtime->set_detail_report_info(cmd&VM_DETAIL);
runtime->set_limit_mode_flag(cmd&VM_LIMIT);
runtime->run(gen, ld, argv);
}
@ -223,7 +230,8 @@ i32 main(i32 argc, const char* argv[]) {
{"--prof", VM_PROFILE},
{"--prof-all", VM_PROF_ALL},
{"-f", VM_REF_FILE},
{"--ref-file", VM_REF_FILE}
{"--ref-file", VM_REF_FILE},
{"--limit", VM_LIMIT|VM_EXEC}
};
u32 cmd = 0;
std::string filename = "";

View File

@ -1,7 +1,7 @@
#pragma once
#ifndef __nasver
#define __nasver "11.1"
#ifndef __nasver__
#define __nasver__ "11.1"
#endif
#include <cstdint>
@ -34,7 +34,8 @@ bool is_aarch64();
bool is_ia64();
bool is_powerpc();
bool is_superh();
const char* get_platform();
const char* get_arch();
// virtual machine stack depth, both global depth and value stack depth
const u32 STACK_DEPTH = 4096;

View File

@ -3,6 +3,13 @@
namespace nasal {
var builtin_unsafe(context* ctx, gc* ngc) {
return nas_err(
"unsafe_redirect",
"you are using unsafe system api under limited mode!"
);
}
var builtin_print(context* ctx, gc* ngc) {
for(auto& i : ctx->localr[1].vec().elems) {
std::cout << i;
@ -447,35 +454,11 @@ var builtin_sleep(context* ctx, gc* ngc) {
}
var builtin_platform(context* ctx, gc* ngc) {
if (is_windows()) {
return ngc->newstr("windows");
} else if (is_linux()) {
return ngc->newstr("linux");
} else if (is_macos()) {
return ngc->newstr("macOS");
}
return ngc->newstr("unknown");
return ngc->newstr(get_platform());
}
var builtin_arch(context* ctx, gc* ngc) {
if (is_x86()) {
return ngc->newstr("x86");
} else if (is_x86_64()) {
return ngc->newstr("x86-64");
} else if (is_amd64()) {
return ngc->newstr("amd64");
} else if (is_arm()) {
return ngc->newstr("arm");
} else if (is_aarch64()) {
return ngc->newstr("aarch64");
} else if (is_ia64()) {
return ngc->newstr("ia64");
} else if (is_powerpc()) {
return ngc->newstr("powerpc");
} else if (is_superh()) {
return ngc->newstr("superh");
}
return ngc->newstr("unknown");
return ngc->newstr(get_arch());
}
// md5 related functions
@ -683,7 +666,7 @@ var builtin_logtime(context* ctx, gc* ngc) {
tm* tm_t = localtime(&t);
char s[64];
snprintf(
s,64,"%d-%.2d-%.2d %.2d:%.2d:%.2d",
s, 64, "%d-%.2d-%.2d %.2d:%.2d:%.2d",
tm_t->tm_year+1900,
tm_t->tm_mon+1,
tm_t->tm_mday,

View File

@ -29,6 +29,7 @@
namespace nasal {
var builtin_unsafe(context*, gc*);
var builtin_print(context*, gc*);
var builtin_println(context*, gc*);
var builtin_exit(context*, gc*);

View File

@ -11,12 +11,18 @@ void codegen::init_file_map(const std::vector<std::string>& file_list) {
void codegen::load_native_function_table(nasal_builtin_table* table) {
for(usize i = 0; table[i].func; ++i) {
// check confliction
if (native_function_mapper.count(table[i].name)) {
err.err("code", "\"" + std::string(table[i].name) + "\" conflicts.");
continue;
}
native_function.push_back(table[i]);
if (flag_limited_mode && unsafe_system_api.count(table[i].name)) {
native_function.push_back({"__unsafe_redirect", builtin_unsafe});
} else {
native_function.push_back(table[i]);
}
auto index = native_function_mapper.size();
// insert into mapper
native_function_mapper[table[i].name] = index;
}
}
@ -1302,10 +1308,14 @@ void codegen::ret_gen(return_expr* node) {
emit(op_ret, 0, node->get_location());
}
const error& codegen::compile(parse& parse, linker& import, bool repl_flag) {
const error& codegen::compile(parse& parse,
linker& import,
bool repl_flag,
bool limit_mode) {
need_repl_output = repl_flag;
flag_limited_mode = limit_mode;
init_native_function();
init_file_map(import.get_file_list());
need_repl_output = repl_flag;
in_foreach_loop_level.push_back(0);

View File

@ -37,6 +37,24 @@ private:
// repl output flag, will generate op_repl to output stack top value if true
bool need_repl_output;
// limit mode flag
bool flag_limited_mode;
const std::unordered_set<std::string> unsafe_system_api = {
// builtin
"__system", "__input",
// io
"__fout", "__open", "__write", "__stat"
// bits
"__fld", "__sfld", "__setfld",
"__buf",
// fg
"__logprint",
// dylib
"__dlopen", "__dlclose", "__dlcallv", "__dlcall",
// unix
"__pipe", "__fork", "__waitpid", "__chdir",
"__environ", "__getcwd", "__getenv"
};
// file mapper for file -> index
std::unordered_map<std::string, usize> file_map;
@ -141,7 +159,7 @@ public:
public:
codegen() = default;
const error& compile(parse&, linker&, bool);
const error& compile(parse&, linker&, bool, bool);
void print(std::ostream&);
void symbol_dump(std::ostream&) const;
};

View File

@ -31,7 +31,7 @@ protected:
std::vector<std::string> res;
public:
flstream():file("") {}
flstream(): file("") {}
void load(const std::string&);
const std::string& operator[](usize n) const {return res[n];}
const auto& name() const {return file;}
@ -39,23 +39,23 @@ public:
usize size() const {return res.size();}
};
class error:public flstream {
class error: public flstream {
private:
u32 cnt; // counter for errors
std::string identation(usize len) {
return std::string(len,' ');
return std::string(len, ' ');
}
std::string leftpad(u32 num, usize len) {
auto tmp = std::to_string(num);
while(tmp.length()<len) {
tmp=" "+tmp;
tmp = " "+tmp;
}
return tmp;
}
public:
error():cnt(0) {}
error(): cnt(0) {}
void err(const std::string&, const std::string&);
void warn(const std::string&, const std::string&);
void err(const std::string&, const span&, const std::string&);

View File

@ -6,7 +6,7 @@
namespace nasal {
linker::linker(): show_path_flag(false), library_loaded(false), this_file("") {
linker::linker(): show_path_flag(false), this_file("") {
const auto seperator = is_windows()? ';':':';
const auto PATH = std::string(getenv("PATH"));
usize last = 0, position = PATH.find(seperator, 0);
@ -155,7 +155,7 @@ std::string linker::generate_self_import_path(const std::string& filename) {
return res + "[" + filename + "]";
}
void linker::link(code_block* new_tree_root, code_block* old_tree_root) {
void linker::merge_tree(code_block* new_tree_root, code_block* old_tree_root) {
// add children of add_root to the back of root
for(auto& i : old_tree_root->get_expressions()) {
new_tree_root->add_expression(i);
@ -179,32 +179,48 @@ code_block* linker::import_regular_file(
// check self import, avoid infinite loading loop
if (check_self_import(filename)) {
err.err("link",
"self-referenced module <" + filename + ">:\n" +
" reference path: " + generate_self_import_path(filename)
node->get_location(),
"self-referenced module <" + filename + ">, " +
"reference path: " + generate_self_import_path(filename)
);
return new code_block({0, 0, 0, 0, filename});
}
check_exist_or_record_file(filename);
module_load_stack.push_back(filename);
// avoid stack overflow
if (module_load_stack.size()>MAX_RECURSION_DEPTH) {
err.err("link",
node->get_location(),
"too deep module import stack (>" +
std::to_string(MAX_RECURSION_DEPTH) + ")."
);
return new code_block({0, 0, 0, 0, filename});
}
// start importing...
lexer nasal_lexer;
parse nasal_parser;
if (nasal_lexer.scan(filename).geterr()) {
err.err("link", "error occurred when analysing <" + filename + ">");
err.err("link",
node->get_location(),
"error occurred when analysing <" + filename + ">"
);
return new code_block({0, 0, 0, 0, filename});
}
if (nasal_parser.compile(nasal_lexer).geterr()) {
err.err("link", "error occurred when analysing <" + filename + ">");
err.err("link",
node->get_location(),
"error occurred when analysing <" + filename + ">"
);
return new code_block({0, 0, 0, 0, filename});
}
// swap result out
auto parse_result = nasal_parser.swap(nullptr);
// check if parse result has 'import'
auto result = load(parse_result, filename);
load(parse_result, filename);
module_load_stack.pop_back();
return result;
return parse_result;
}
code_block* linker::import_nasal_lib() {
@ -238,10 +254,12 @@ code_block* linker::import_nasal_lib() {
// swap result out
auto parse_result = nasal_parser.swap(nullptr);
// check if library has 'import' (in fact it should not)
return load(parse_result, path);
load(parse_result, path);
return parse_result;
}
std::string linker::generate_module_name(const std::string& file_path) {
// import("...") may trigger this error module name
auto error_name = "module@[" + file_path + "]";
if (!file_path.length()) {
return error_name;
@ -316,13 +334,19 @@ return_expr* linker::generate_module_return(code_block* block) {
}
definition_expr* linker::generate_module_definition(code_block* block) {
// generate ast node like this:
// var {module_name} = (func() {
// ... # module itself
// })();
auto def = new definition_expr(block->get_location());
def->set_identifier(new identifier(
block->get_location(),
generate_module_name(block->get_location().file)
));
// (func() {...})();
auto call = new call_expr(block->get_location());
// func() {...}
auto func = new function(block->get_location());
func->set_code_block(block);
func->get_code_block()->add_expression(generate_module_return(block));
@ -333,18 +357,7 @@ definition_expr* linker::generate_module_definition(code_block* block) {
return def;
}
code_block* linker::load(code_block* program_root, const std::string& filename) {
auto tree = new code_block({0, 0, 0, 0, filename});
// load library, this ast will be linked with root directly
// so no extra namespace is generated
if (!library_loaded) {
auto nasal_lib_code_block = import_nasal_lib();
// insert nasal lib code to the back of tree
link(tree, nasal_lib_code_block);
delete nasal_lib_code_block;
library_loaded = true;
}
void linker::load(code_block* program_root, const std::string& filename) {
// load imported modules
std::unordered_set<std::string> used_modules = {};
for(auto& import_node : program_root->get_expressions()) {
@ -353,46 +366,46 @@ code_block* linker::load(code_block* program_root, const std::string& filename)
}
// parse file and get ast
auto module_code_block = import_regular_file(import_node, used_modules);
auto replace_node = new null_expr(import_node->get_location());
// after importing the regular file as module, delete this node
delete import_node;
// and replace the node with null_expr node
import_node = replace_node;
// avoid repeatedly importing the same module
// avoid repeatedly importing the same module in one file
const auto& module_path = module_code_block->get_location().file;
if (used_modules.count(module_path)) {
delete module_code_block;
auto replace_node = new null_expr(import_node->get_location());
// after importing the regular file as module, delete this node
delete import_node;
// and replace the node with null_expr node
import_node = replace_node;
continue;
}
used_modules.insert(module_path);
delete import_node;
// then we generate a function warping the code block,
// and export the necessary global symbols in this code block
// by generate a return statement, with a hashmap return value
used_modules.insert(module_path);
tree->add_expression(generate_module_definition(module_code_block));
import_node = generate_module_definition(module_code_block);
}
// insert program root to the back of tree
link(tree, program_root);
return tree;
}
const error& linker::link(
parse& parse, const std::string& self, bool spath = false) {
const error& linker::link(parse& parse, bool spath = false) {
// switch for showing path when errors occur
show_path_flag = spath;
// initializing file map
this_file = self;
imported_files = {self};
module_load_stack = {self};
this_file = parse.tree()->get_location().file;
imported_files = {this_file};
module_load_stack = {this_file};
// scan root and import files
// then generate a new ast and return to import_ast
auto new_tree_root = load(parse.tree(), self);
auto old_tree_root = parse.swap(new_tree_root);
delete old_tree_root;
// dfs load file
auto library = import_nasal_lib();
// load used modules of this file
load(parse.tree(), this_file);
// then insert the whole tree into library tree root
merge_tree(library, parse.tree());
// swap tree root, and delete old root
delete parse.swap(library);
return err;
}

View File

@ -23,8 +23,8 @@ namespace nasal {
class linker {
private:
const u32 MAX_RECURSION_DEPTH = 256;
bool show_path_flag;
bool library_loaded;
std::string this_file;
error err;
std::vector<std::string> imported_files;
@ -36,7 +36,7 @@ private:
bool check_exist_or_record_file(const std::string&);
bool check_self_import(const std::string&);
std::string generate_self_import_path(const std::string&);
void link(code_block*, code_block*);
void merge_tree(code_block*, code_block*);
std::string get_path(expr*);
std::string find_real_file_path(const std::string&, const span&);
code_block* import_regular_file(expr*, std::unordered_set<std::string>&);
@ -44,11 +44,11 @@ private:
std::string generate_module_name(const std::string&);
return_expr* generate_module_return(code_block*);
definition_expr* generate_module_definition(code_block*);
code_block* load(code_block*, const std::string&);
void load(code_block*, const std::string&);
public:
linker();
const error& link(parse&, const std::string&, bool);
const error& link(parse&, bool);
const auto& get_file_list() const {return imported_files;}
};

View File

@ -97,6 +97,38 @@ bool is_superh() {
#endif
}
const char* get_platform() {
if (is_windows()) {
return "windows";
} else if (is_linux()) {
return "linux";
} else if (is_macos()) {
return "macOS";
}
return "unknown platform";
}
const char* get_arch() {
if (is_x86()) {
return "x86";
} else if (is_x86_64()) {
return "x86-64";
} else if (is_amd64()) {
return "amd64";
} else if (is_arm()) {
return "arm";
} else if (is_aarch64()) {
return "aarch64";
} else if (is_ia64()) {
return "ia64";
} else if (is_powerpc()) {
return "powerpc";
} else if (is_superh()) {
return "superh";
}
return "unknown arch";
}
f64 hex_to_f64(const char* str) {
f64 ret = 0;
for(; *str; ++str) {

View File

@ -45,6 +45,9 @@ protected:
bool first_exec_flag = true;
bool allow_repl_output = false;
/* limited mode, will not load unsafe system api if switch on */
bool flag_limited_mode = false;
/* vm initializing function */
void init(
const std::vector<std::string>&,
@ -192,6 +195,8 @@ public:
void set_repl_mode_flag(bool flag) {is_repl_mode = flag;}
/* set repl output flag */
void set_allow_repl_output_flag(bool flag) {allow_repl_output = flag;}
/* set limit mode flag */
void set_limit_mode_flag(bool flag) {flag_limited_mode = flag;}
};
inline bool vm::cond(var& val) {

View File

@ -92,12 +92,12 @@ bool repl::run() {
return false;
}
if (nasal_linker->link(*nasal_parser, "<nasal-repl>", true).geterr()) {
if (nasal_linker->link(*nasal_parser, true).geterr()) {
return false;
}
nasal_opt->do_optimization(nasal_parser->tree());
if (nasal_codegen->compile(*nasal_parser, *nasal_linker, true).geterr()) {
if (nasal_codegen->compile(*nasal_parser, *nasal_linker, true, false).geterr()) {
return false;
}
@ -120,7 +120,7 @@ void repl::execute() {
std::cout << "[nasal-repl] Initialization complete.\n\n";
// finish initialization, output version info
std::cout << "Nasal REPL interpreter version " << __nasver;
std::cout << "Nasal REPL interpreter version " << __nasver__;
std::cout << " (" << __DATE__ << " " << __TIME__ << ")\n";
help();

97
test/langtons-ant.nas Normal file
View File

@ -0,0 +1,97 @@
use std.unix;
srand();
var ant = {
new: func(color, pos_x, pos_y) {
return {
move: int(rand()*4),
color: color,
pos_x: pos_x,
pos_y: pos_y
};
}
};
# generate agents
var ants = [
ant.new(10, 30, 15),
ant.new(11, 31, 16),
ant.new(4, 29, 13),
ant.new(9, 30, 14),
ant.new(13, 30, 12),
ant.new(99, 25, 18)
];
# initialize game map
var map = [];
var map_color = [];
setsize(map, 60*30);
setsize(map_color, 60*30);
forindex(var i; map) {
map[i] = 0;
map_color[i] = 0;
}
foreach(var a; ants) {
map_color[a.pos_x + a.pos_y*60] = a.color;
}
var print_map = func {
var res = "\e[1;1H";
for(var y = 0; y<30; y += 1) {
for(var x = 0; x<60; x += 1) {
res ~= "\e[38;5;"~map_color[x + y*60]~";1m";
res ~= map[x + y*60] ~ " \e[0m";
}
res ~= "\n";
}
print(res);
}
var move = func {
var move_step = [
[0, -1],
[1, 0],
[0, 1],
[-1, 0]
];
var temp_map = [];
setsize(temp_map, 60*30);
forindex(var i; map) {
temp_map[i] = map[i];
}
foreach(var a; ants) {
var map_state = map[a.pos_x + a.pos_y*60];
temp_map[a.pos_x + a.pos_y*60] = map_state==0? 1:0;
if (map_state==1) {
a.move -= 1;
if (a.move < 0) {
a.move = 3;
}
} else {
a.move += 1;
if (a.move > 3) {
a.move = 0;
}
}
a.pos_x += move_step[a.move][0];
a.pos_y += move_step[a.move][1];
if (a.pos_x < 0) { a.pos_x = 59; }
if (a.pos_x > 59) { a.pos_x = 0; }
if (a.pos_y < 0) { a.pos_y = 29; }
if (a.pos_y > 29) { a.pos_y = 0; }
if (map_color[a.pos_x + a.pos_y*60]!=a.color) {
map_color[a.pos_x + a.pos_y*60] = a.color;
} else {
map_color[a.pos_x + a.pos_y*60] = 0;
}
}
map = temp_map;
}
while(1) {
print_map();
move();
unix.sleep(1/24);
}