luau/fuzz/protoprint.cpp

950 lines
25 KiB
C++
Raw Permalink Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "luau.pb.h"
static const std::string kNames[] = {
"_G", "_VERSION", "__add", "__call", "__concat", "__div", "__eq", "__idiv", "__index",
"__iter", "__le", "__len", "__lt", "__mod", "__mode", "__mul", "__namecall", "__newindex",
"__pow", "__sub", "__type", "__unm", "abs", "acos", "arshift", "asin", "assert",
"atan", "atan2", "band", "bit32", "bnot", "boolean", "bor", "btest", "buffer",
"bxor", "byte", "ceil", "char", "charpattern", "clamp", "clear", "clock", "clone",
"close", "codepoint", "codes", "collectgarbage", "concat", "copy", "coroutine", "cos", "cosh",
"countlz", "countrz", "create", "date", "debug", "deg", "difftime", "error", "exp",
"extract", "fill", "find", "floor", "fmod", "foreach", "foreachi", "format", "freeze",
"frexp", "fromstring", "function", "gcinfo", "getfenv", "getmetatable", "getn", "gmatch", "gsub",
"huge", "info", "insert", "ipairs", "isfrozen", "isyieldable", "ldexp", "len", "loadstring",
"log", "log10", "lower", "lrotate", "lshift", "match", "math", "max", "maxn",
"min", "modf", "move", "newproxy", "next", "nil", "noise", "number", "offset",
"os", "pack", "packsize", "pairs", "pcall", "pi", "pow", "print", "rad",
"random", "randomseed", "rawequal", "rawget", "rawlen", "rawset", "readf32", "readf64", "readi16",
"readi32", "readi8", "readstring", "readu16", "readu32", "readu8", "remove", "rep", "replace",
"require", "resume", "reverse", "round", "rrotate", "rshift", "running", "select", "setfenv",
"setmetatable", "sign", "sin", "sinh", "sort", "split", "sqrt", "status", "string",
"sub", "table", "tan", "tanh", "thread", "time", "tonumber", "tostring", "tostring",
"traceback", "type", "typeof", "unpack", "upper", "userdata", "utf8", "vector", "wrap",
"writef32", "writef64", "writei16", "writei32", "writei8", "writestring", "writeu16", "writeu32", "writeu8",
"xpcall", "yield",
};
static const std::string kTypes[] = {
"any",
Sync to upstream/release/601 (#1084) ## What's changed - `bit32.byteswap` added ([RFC](https://github.com/luau-lang/rfcs/blob/4f543ec23b6a1b53396e0803dd253c83041bae62/docs/function-bit32-byteswap.md)) - Buffer library implementation ([RFC](https://github.com/luau-lang/rfcs/blob/4f543ec23b6a1b53396e0803dd253c83041bae62/docs/type-byte-buffer.md)) - Fixed a missing `stdint.h` include - Fixed parser limiter for recursive type annotations being kind of weird (fixes #645) ### Native Codegen - Fixed a pair of issues when lowering `bit32.extract` - Fixed a narrow edge case that could result in an infinite loop without an interruption - Fixed a negative array out-of-bounds access issue - Temporarily reverted linear block predecessor value propagation ### New type solver - We now type check assignments to annotated variables - Fixed some test cases under local type inference - Moved `isPending` checks for type families to improve performance - Optimized our process for testing if a free type is sufficiently solved - Removed "none ptr" from lea instruction disassembly logging ### Build system & tooling - CMake configuration now validates dependencies to maintain separation between components - Improvements to the fuzzer coverage - Deduplicator for fuzzed callstacks --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
2023-10-28 05:18:41 +08:00
"boolean",
"buffer",
"nil",
"number",
"string",
"thread",
Sync to upstream/release/601 (#1084) ## What's changed - `bit32.byteswap` added ([RFC](https://github.com/luau-lang/rfcs/blob/4f543ec23b6a1b53396e0803dd253c83041bae62/docs/function-bit32-byteswap.md)) - Buffer library implementation ([RFC](https://github.com/luau-lang/rfcs/blob/4f543ec23b6a1b53396e0803dd253c83041bae62/docs/type-byte-buffer.md)) - Fixed a missing `stdint.h` include - Fixed parser limiter for recursive type annotations being kind of weird (fixes #645) ### Native Codegen - Fixed a pair of issues when lowering `bit32.extract` - Fixed a narrow edge case that could result in an infinite loop without an interruption - Fixed a negative array out-of-bounds access issue - Temporarily reverted linear block predecessor value propagation ### New type solver - We now type check assignments to annotated variables - Fixed some test cases under local type inference - Moved `isPending` checks for type families to improve performance - Optimized our process for testing if a free type is sufficiently solved - Removed "none ptr" from lea instruction disassembly logging ### Build system & tooling - CMake configuration now validates dependencies to maintain separation between components - Improvements to the fuzzer coverage - Deduplicator for fuzzed callstacks --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
2023-10-28 05:18:41 +08:00
"vector",
};
static const std::string kClasses[] = {
"Vector3",
"Instance",
"Part",
};
struct ProtoToLuau
{
struct Function
{
int loops = 0;
bool vararg = false;
};
std::string source;
std::vector<Function> functions;
bool types = false;
ProtoToLuau()
{
Function top = {};
top.vararg = true;
functions.push_back(top);
}
void ident(const luau::Name& name)
{
if (name.has_builtin())
{
size_t index = size_t(name.builtin()) % std::size(kNames);
source += kNames[index];
}
else if (name.has_custom())
{
source += 'n';
source += std::to_string(name.custom() & 0xff);
}
else
{
source += '_';
}
}
void ident(const luau::Typename& name)
{
source += 't';
source += std::to_string(name.index() & 0xff);
}
2022-02-25 07:53:37 +08:00
template<typename T>
void genericidents(const T& node)
{
if (node.generics_size() || node.genericpacks_size())
{
source += '<';
bool first = true;
for (size_t i = 0; i < node.generics_size(); ++i)
{
if (!first)
source += ',';
first = false;
ident(node.generics(i));
}
for (size_t i = 0; i < node.genericpacks_size(); ++i)
{
if (!first)
source += ',';
first = false;
ident(node.genericpacks(i));
source += "...";
}
source += '>';
}
}
void print(const luau::Expr& expr)
{
if (expr.has_group())
print(expr.group());
else if (expr.has_nil())
print(expr.nil());
else if (expr.has_bool_())
print(expr.bool_());
else if (expr.has_number())
print(expr.number());
else if (expr.has_string())
print(expr.string());
else if (expr.has_local())
print(expr.local());
else if (expr.has_global())
print(expr.global());
else if (expr.has_varargs())
print(expr.varargs());
else if (expr.has_call())
print(expr.call());
else if (expr.has_index_name())
print(expr.index_name());
else if (expr.has_index_expr())
print(expr.index_expr());
else if (expr.has_function())
print(expr.function());
else if (expr.has_table())
print(expr.table());
else if (expr.has_unary())
print(expr.unary());
else if (expr.has_binary())
print(expr.binary());
2022-02-25 07:53:37 +08:00
else if (expr.has_ifelse())
print(expr.ifelse());
else if (expr.has_interpstring())
print(expr.interpstring());
else
source += "_";
}
void print(const luau::ExprPrefix& expr)
{
if (expr.has_group())
print(expr.group());
else if (expr.has_local())
print(expr.local());
else if (expr.has_global())
print(expr.global());
else if (expr.has_call())
print(expr.call());
else if (expr.has_index_name())
print(expr.index_name());
else if (expr.has_index_expr())
print(expr.index_expr());
else
source += "_";
}
void print(const luau::ExprGroup& expr)
{
source += '(';
print(expr.expr());
source += ')';
}
void print(const luau::ExprConstantNil& expr)
{
source += "nil";
}
void print(const luau::ExprConstantBool& expr)
{
source += expr.val() ? "true" : "false";
}
void print(const luau::ExprConstantNumber& expr)
{
source += std::to_string(expr.val());
}
void print(const luau::ExprConstantString& expr)
{
source += '"';
for (char ch : expr.val())
if (isalpha(ch))
source += ch;
source += '"';
}
void print(const luau::Local& var)
{
source += 'l';
source += std::to_string(var.name() & 0xff);
}
void print(const luau::ExprLocal& expr)
{
print(expr.var());
}
void print(const luau::ExprGlobal& expr)
{
ident(expr.name());
}
void print(const luau::ExprVarargs& expr)
{
if (functions.back().vararg)
source += "...";
else
source += "_";
}
void print(const luau::ExprCall& expr)
{
if (expr.func().has_index_name())
print(expr.func().index_name(), expr.self());
else
print(expr.func());
source += '(';
for (int i = 0; i < expr.args_size(); ++i)
{
if (i != 0)
source += ',';
print(expr.args(i));
}
source += ')';
}
void print(const luau::ExprIndexName& expr, bool self = false)
{
print(expr.expr());
source += self ? ':' : '.';
ident(expr.index());
}
void print(const luau::ExprIndexExpr& expr)
{
print(expr.expr());
source += '[';
print(expr.index());
source += ']';
}
void function(const luau::ExprFunction& expr)
{
2022-02-25 07:53:37 +08:00
genericidents(expr);
source += "(";
for (int i = 0; i < expr.args_size(); ++i)
{
if (i != 0)
source += ',';
print(expr.args(i));
if (types && i < expr.types_size())
{
source += ':';
print(expr.types(i));
}
}
if (expr.vararg())
{
if (expr.args_size())
source += ',';
source += "...";
}
source += ')';
if (types && expr.rettypes_size())
{
source += ':';
if (expr.rettypes_size() > 1)
source += '(';
for (size_t i = 0; i < expr.rettypes_size(); ++i)
{
if (i != 0)
source += ',';
print(expr.rettypes(i));
}
if (expr.rettypes_size() > 1)
source += ')';
}
source += '\n';
Function func = {};
func.vararg = expr.vararg();
functions.push_back(func);
print(expr.body());
functions.pop_back();
source += "end";
}
void print(const luau::ExprFunction& expr)
{
source += "function";
function(expr);
}
void print(const luau::ExprTable& expr)
{
source += '{';
for (int i = 0; i < expr.items_size(); ++i)
{
if (expr.items(i).has_key_name())
{
ident(expr.items(i).key_name());
source += '=';
}
else if (expr.items(i).has_key_expr())
{
source += "[";
print(expr.items(i).key_expr());
source += "]=";
}
print(expr.items(i).value());
source += ',';
}
source += '}';
}
void print(const luau::ExprUnary& expr)
{
if (expr.op() == luau::ExprUnary::Not)
source += "not ";
else if (expr.op() == luau::ExprUnary::Minus)
source += "- ";
else if (expr.op() == luau::ExprUnary::Len)
source += "# ";
print(expr.expr());
}
void print(const luau::ExprBinary& expr)
{
print(expr.left());
if (expr.op() == luau::ExprBinary::Add)
source += " + ";
else if (expr.op() == luau::ExprBinary::Sub)
source += " - ";
else if (expr.op() == luau::ExprBinary::Mul)
source += " * ";
else if (expr.op() == luau::ExprBinary::Div)
source += " / ";
else if (expr.op() == luau::ExprBinary::FloorDiv)
source += " // ";
else if (expr.op() == luau::ExprBinary::Mod)
source += " % ";
else if (expr.op() == luau::ExprBinary::Pow)
source += " ^ ";
else if (expr.op() == luau::ExprBinary::Concat)
source += " .. ";
else if (expr.op() == luau::ExprBinary::CompareNe)
source += " ~= ";
else if (expr.op() == luau::ExprBinary::CompareEq)
source += " == ";
else if (expr.op() == luau::ExprBinary::CompareLt)
source += " < ";
else if (expr.op() == luau::ExprBinary::CompareLe)
source += " <= ";
else if (expr.op() == luau::ExprBinary::CompareGt)
source += " > ";
else if (expr.op() == luau::ExprBinary::CompareGe)
source += " >= ";
else if (expr.op() == luau::ExprBinary::And)
source += " and ";
else if (expr.op() == luau::ExprBinary::Or)
source += " or ";
print(expr.right());
}
void print(const luau::ExprIfElse& expr)
{
2022-02-25 07:53:37 +08:00
source += "if ";
print(expr.cond());
source += " then ";
print(expr.then());
2022-02-25 07:53:37 +08:00
if (expr.has_else_())
{
source += " else ";
print(expr.else_());
}
else if (expr.has_elseif())
{
source += " else";
print(expr.elseif());
}
}
void print(const luau::ExprInterpString& expr)
{
source += "`";
for (int i = 0; i < expr.parts_size(); ++i)
{
if (expr.parts(i).has_string())
{
// String literal is added surrounded with "", but that's ok
print(expr.parts(i));
}
else
{
source += "{";
print(expr.parts(i));
source += "}";
}
}
source += "`";
}
void print(const luau::LValue& expr)
{
if (expr.has_local())
print(expr.local());
else if (expr.has_global())
print(expr.global());
else if (expr.has_index_name())
print(expr.index_name());
else if (expr.has_index_expr())
print(expr.index_expr());
else
source += "_";
}
void print(const luau::Stat& stat)
{
if (stat.has_block())
print(stat.block());
else if (stat.has_if_())
print(stat.if_());
else if (stat.has_while_())
print(stat.while_());
else if (stat.has_repeat())
print(stat.repeat());
else if (stat.has_break_())
print(stat.break_());
else if (stat.has_continue_())
print(stat.continue_());
else if (stat.has_return_())
print(stat.return_());
else if (stat.has_call())
print(stat.call());
else if (stat.has_local())
print(stat.local());
else if (stat.has_for_())
print(stat.for_());
else if (stat.has_for_in())
print(stat.for_in());
else if (stat.has_assign())
print(stat.assign());
else if (stat.has_compound_assign())
print(stat.compound_assign());
else if (stat.has_function())
print(stat.function());
else if (stat.has_local_function())
print(stat.local_function());
else if (stat.has_type_alias())
print(stat.type_alias());
2022-02-25 07:53:37 +08:00
else if (stat.has_require_into_local())
print(stat.require_into_local());
else
source += "do end\n";
}
void print(const luau::StatBlock& stat)
{
for (int i = 0; i < stat.body_size(); ++i)
{
if (stat.body(i).has_block())
{
source += "do\n";
print(stat.body(i));
source += "end\n";
}
else
{
print(stat.body(i));
// parser will reject code with break/continue/return being non-trailing statements in a block
if (stat.body(i).has_break_() || stat.body(i).has_continue_() || stat.body(i).has_return_())
break;
}
}
}
void print(const luau::StatIf& stat)
{
source += "if ";
print(stat.cond());
source += " then\n";
print(stat.then());
if (stat.has_else_())
{
source += "else\n";
print(stat.else_());
source += "end\n";
}
else if (stat.has_elseif())
{
source += "else";
print(stat.elseif());
}
else
{
source += "end\n";
}
}
void print(const luau::StatWhile& stat)
{
source += "while ";
print(stat.cond());
source += " do\n";
functions.back().loops++;
print(stat.body());
functions.back().loops--;
source += "end\n";
}
void print(const luau::StatRepeat& stat)
{
source += "repeat\n";
functions.back().loops++;
print(stat.body());
functions.back().loops--;
source += "until ";
print(stat.cond());
source += "\n";
}
void print(const luau::StatBreak& stat)
{
if (functions.back().loops)
source += "break\n";
else
source += "do end\n";
}
void print(const luau::StatContinue& stat)
{
if (functions.back().loops)
source += "continue\n";
else
source += "do end\n";
}
void print(const luau::StatReturn& stat)
{
source += "return ";
for (int i = 0; i < stat.list_size(); ++i)
{
if (i != 0)
source += ',';
print(stat.list(i));
}
source += "\n";
}
void print(const luau::StatCall& stat)
{
print(stat.expr());
source += '\n';
}
void print(const luau::StatLocal& stat)
{
source += "local ";
if (stat.vars_size() == 0)
source += '_';
for (int i = 0; i < stat.vars_size(); ++i)
{
if (i != 0)
source += ',';
print(stat.vars(i));
if (types && i < stat.types_size())
{
source += ':';
print(stat.types(i));
}
}
if (stat.values_size() != 0)
source += " = ";
for (int i = 0; i < stat.values_size(); ++i)
{
if (i != 0)
source += ',';
print(stat.values(i));
}
source += '\n';
}
void print(const luau::StatFor& stat)
{
source += "for ";
print(stat.var());
source += '=';
print(stat.from());
source += ',';
print(stat.to());
if (stat.has_step())
{
source += ',';
print(stat.step());
}
source += " do\n";
functions.back().loops++;
print(stat.body());
functions.back().loops--;
source += "end\n";
}
void print(const luau::StatForIn& stat)
{
source += "for ";
if (stat.vars_size() == 0)
source += '_';
for (int i = 0; i < stat.vars_size(); ++i)
{
if (i != 0)
source += ',';
print(stat.vars(i));
}
source += " in ";
if (stat.values_size() == 0)
source += "...";
for (int i = 0; i < stat.values_size(); ++i)
{
if (i != 0)
source += ',';
print(stat.values(i));
}
source += " do\n";
functions.back().loops++;
print(stat.body());
functions.back().loops--;
source += "end\n";
}
void print(const luau::StatAssign& stat)
{
if (stat.vars_size() == 0)
source += '_';
for (int i = 0; i < stat.vars_size(); ++i)
{
if (i != 0)
source += ',';
print(stat.vars(i));
}
source += " = ";
if (stat.values_size() == 0)
source += "nil";
for (int i = 0; i < stat.values_size(); ++i)
{
if (i != 0)
source += ',';
print(stat.values(i));
}
source += '\n';
}
void print(const luau::StatCompoundAssign& stat)
{
print(stat.var());
if (stat.op() == luau::StatCompoundAssign::Add)
source += " += ";
else if (stat.op() == luau::StatCompoundAssign::Sub)
source += " -= ";
else if (stat.op() == luau::StatCompoundAssign::Mul)
source += " *= ";
else if (stat.op() == luau::StatCompoundAssign::Div)
source += " /= ";
else if (stat.op() == luau::StatCompoundAssign::Mod)
source += " %= ";
else if (stat.op() == luau::StatCompoundAssign::Pow)
source += " ^= ";
else if (stat.op() == luau::StatCompoundAssign::Concat)
source += " ..= ";
print(stat.value());
source += '\n';
}
void print(const luau::StatFunction& stat)
{
source += "function ";
if (stat.var().has_index_name())
print(stat.var().index_name(), stat.self());
else if (stat.var().has_index_expr())
source += '_'; // function foo[bar]() is invalid syntax
else
print(stat.var());
function(stat.func());
source += '\n';
}
void print(const luau::StatLocalFunction& stat)
{
source += "local function ";
print(stat.var());
function(stat.func());
source += '\n';
}
void print(const luau::StatTypeAlias& stat)
{
2022-02-25 07:53:37 +08:00
if (stat.export_())
source += "export ";
source += "type ";
ident(stat.name());
2022-02-25 07:53:37 +08:00
genericidents(stat);
source += " = ";
print(stat.type());
source += '\n';
}
2022-02-25 07:53:37 +08:00
void print(const luau::StatRequireIntoLocalHelper& stat)
{
source += "local ";
print(stat.var());
source += " = require(module" + std::to_string(stat.modulenum() % 2) + ")\n";
}
void print(const luau::Type& type)
{
if (type.has_primitive())
print(type.primitive());
else if (type.has_literal())
print(type.literal());
else if (type.has_table())
print(type.table());
else if (type.has_function())
print(type.function());
else if (type.has_typeof())
print(type.typeof());
else if (type.has_union_())
print(type.union_());
else if (type.has_intersection())
print(type.intersection());
else if (type.has_class_())
print(type.class_());
else if (type.has_ref())
print(type.ref());
2022-02-25 07:53:37 +08:00
else if (type.has_boolean())
print(type.boolean());
else if (type.has_string())
print(type.string());
else
source += "any";
}
void print(const luau::TypePrimitive& type)
{
size_t index = size_t(type.kind()) % std::size(kTypes);
source += kTypes[index];
}
void print(const luau::TypeLiteral& type)
{
ident(type.name());
2022-02-25 07:53:37 +08:00
if (type.generics_size() || type.genericpacks_size())
{
source += '<';
2022-02-25 07:53:37 +08:00
bool first = true;
for (size_t i = 0; i < type.generics_size(); ++i)
{
2022-02-25 07:53:37 +08:00
if (!first)
source += ',';
2022-02-25 07:53:37 +08:00
first = false;
print(type.generics(i));
}
2022-02-25 07:53:37 +08:00
for (size_t i = 0; i < type.genericpacks_size(); ++i)
{
if (!first)
source += ',';
first = false;
ident(type.genericpacks(i));
source += "...";
}
source += '>';
}
}
void print(const luau::TypeTable& type)
{
source += '{';
for (size_t i = 0; i < type.items_size(); ++i)
{
ident(type.items(i).key());
source += ':';
print(type.items(i).type());
source += ',';
}
if (type.has_indexer())
{
source += '[';
print(type.indexer().key());
source += "]:";
print(type.indexer().value());
}
source += '}';
}
void print(const luau::TypeFunction& type)
{
2022-02-25 07:53:37 +08:00
genericidents(type);
source += '(';
for (size_t i = 0; i < type.args_size(); ++i)
{
if (i != 0)
source += ',';
print(type.args(i));
}
source += ")->";
if (type.rets_size() != 1)
source += '(';
for (size_t i = 0; i < type.rets_size(); ++i)
{
if (i != 0)
source += ',';
print(type.rets(i));
}
if (type.rets_size() != 1)
source += ')';
}
void print(const luau::TypeTypeof& type)
{
source += "typeof(";
print(type.expr());
source += ')';
}
void print(const luau::TypeUnion& type)
{
source += '(';
print(type.left());
source += ")|(";
print(type.right());
source += ')';
}
void print(const luau::TypeIntersection& type)
{
source += '(';
print(type.left());
source += ")&(";
print(type.right());
source += ')';
}
void print(const luau::TypeClass& type)
{
size_t index = size_t(type.kind()) % std::size(kClasses);
source += kClasses[index];
}
void print(const luau::TypeRef& type)
{
print(type.prefix());
source += '.';
ident(type.index());
}
2022-02-25 07:53:37 +08:00
void print(const luau::TypeBoolean& type)
{
source += type.val() ? "true" : "false";
}
void print(const luau::TypeString& type)
{
source += '"';
for (char ch : type.val())
if (isgraph(ch))
source += ch;
source += '"';
}
};
2022-02-25 07:53:37 +08:00
std::vector<std::string> protoprint(const luau::ModuleSet& stat, bool types)
{
2022-02-25 07:53:37 +08:00
std::vector<std::string> result;
if (stat.has_module())
{
ProtoToLuau printer;
printer.types = types;
printer.print(stat.module());
result.push_back(printer.source);
}
ProtoToLuau printer;
printer.types = types;
2022-02-25 07:53:37 +08:00
printer.print(stat.program());
result.push_back(printer.source);
return result;
}