/
BirdLeon
/
ROBLOX2016
Обзор
Документация
Войти
/
BirdLeon
/
ROBLOX2016
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
App/script/ScriptAnalyzer.cpp
4 059 строк
134 KB
PatoFlamejanteTV
full source code
19 дек 2024, 19:11
19 дек 2024, 19:11
05db15d
Код
Авторство
О чём код?
#include "stdafx.h" #include "script/ScriptAnalyzer.h" #include "Lua/lua.hpp" #include "util/ProtectedString.h" #include "lstate.h" #include "lopcodes.h" #include "lvm.h" #include "ldebug.h" #include "script/ScriptContext.h" #include "script/Script.h" #include "script/LuaInstanceBridge.h" #include "script/LuaArguments.h" #include "rbx/DenseHash.h" FASTFLAGVARIABLE(StudioVariableIntellesense, false) FASTFLAGVARIABLE(DebugScriptAnalyzer, false) FASTINTVARIABLE(ScriptAnalyzerIgnoreWarnings, 0) namespace RBX { using namespace ScriptAnalyzer; namespace ScriptParser { class Allocator { public: Allocator() : root(static_cast<Page*>(operator new(sizeof(Page)))) , offset(0) { root->next = NULL; } ~Allocator() { Page* page = root; while (page) { Page* next = page->next; operator delete(page); page = next; } } void* allocate(size_t size) { // pointer-align all allocations size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); if (offset + size <= sizeof(root->data)) { void* result = root->data + offset; offset += size; return result; } // allocate new page void* pageData = operator new(offsetof(Page, data) + std::max(sizeof(root->data), size)); Page* page = static_cast<Page*>(pageData); page->next = root; root = page; offset = size; return page->data; } private: struct Page { Page* next; char data[8192]; }; Page* root; unsigned int offset; }; } } void* operator new(size_t size, RBX::ScriptParser::Allocator& alloc) { return alloc.allocate(size); } void* operator new[](size_t size, RBX::ScriptParser::Allocator& alloc) { return alloc.allocate(size); } void operator delete(void*, RBX::ScriptParser::Allocator& alloc) { } void operator delete[](void*, RBX::ScriptParser::Allocator& alloc) { } namespace RBX { namespace ScriptParser { class Error: public std::exception { public: Error(const Location& location, const char* format, ...) RBX_PRINTF_ATTR(3, 4) : location(location) { va_list args; va_start(args, format); message = vformat(format, args); va_end(args); } virtual ~Error() throw() { } virtual const char* what() const throw() { return message.c_str(); } const Location& getLocation() const { return location; } private: std::string message; Location location; }; const char* kReserved[] = { "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" }; struct Lexeme { enum Type { Eof = 0, // 1..255 means actual character values Char_END = 256, Equal, LessEqual, GreaterEqual, NotEqual, Dot2, Dot3, String, Number, Name, Reserved_BEGIN, ReservedAnd = Reserved_BEGIN, ReservedBreak, ReservedDo, ReservedElse, ReservedElseif, ReservedEnd, ReservedFalse, ReservedFor, ReservedFunction, ReservedIf, ReservedIn, ReservedLocal, ReservedNil, ReservedNot, ReservedOr, ReservedRepeat, ReservedReturn, ReservedThen, ReservedTrue, ReservedUntil, ReservedWhile, Reserved_END }; Type type; Location location; union { const std::string* data; // String, Number const char* name; // Name }; Lexeme(const Location& location, Type type) : type(type) , location(location) { } Lexeme(const Location& location, char character) : type(static_cast<Type>(static_cast<unsigned char>(character))) , location(location) { } Lexeme(const Location& location, Type type, const std::string* data) : type(type) , location(location) , data(data) { RBXASSERT(type == String || type == Number); } Lexeme(const Location& location, Type type, const char* name) : type(type) , location(location) , name(name) { RBXASSERT(type == Name); } std::string toString() const { switch (type) { case Eof: return "<eof>"; case Equal: return "'=='"; case LessEqual: return "'<='"; case GreaterEqual: return "'>='"; case NotEqual: return "'~='"; case Dot2: return "'..'"; case Dot3: return "'...'"; case String: return format("\"%s\"", data->c_str()); case Number: return format("'%s'", data->c_str()); case Name: return format("'%s'", name); default: if (type < Char_END) return format("'%c'", type); else if (type >= Reserved_BEGIN && type < Reserved_END) return format("'%s'", kReserved[type - Reserved_BEGIN]); else return "<unknown>"; } } }; struct AstName { const char* value; AstName() : value(NULL) { } explicit AstName(const char* value) : value(value) { } bool operator==(const AstName& rhs) const { return value == rhs.value; } bool operator!=(const AstName& rhs) const { return value != rhs.value; } bool operator==(const char* rhs) const { return strcmp(value, rhs) == 0; } bool operator!=(const char* rhs) const { return strcmp(value, rhs) != 0; } }; size_t hash_value(const AstName& value) { return boost::hash_value(value.value); } class AstNameTable { public: AstNameTable(Allocator& allocator) : data("") , allocator(allocator) { } AstName addStatic(const char* name, Lexeme::Type type = Lexeme::Name) { AstNameTable::Entry entry = { AstName(name), type }; RBXASSERT(!data.contains(name)); data[name] = entry; return entry.value; } std::pair<AstName, Lexeme::Type> getOrAddWithType(const char* name) { const Entry* entry = data.find(name); if (entry) { return std::make_pair(entry->value, entry->type); } else { size_t nameLength = strlen(name); char* nameData = new (allocator) char[nameLength + 1]; memcpy(nameData, name, nameLength + 1); Entry newEntry = { AstName(nameData), Lexeme::Name }; data[nameData] = newEntry; return std::make_pair(newEntry.value, newEntry.type); } } AstName getOrAdd(const char* name) { return getOrAddWithType(name).first; } private: struct Entry { AstName value; Lexeme::Type type; }; DenseHashMap<const char*, Entry, Reflection::StringHashPredicate, Reflection::StringEqualPredicate> data; Allocator& allocator; }; class Lexer { public: Lexer(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator) : buffer(buffer) , bufferSize(bufferSize) , offset(0) , line(0) , lineOffset(0) , lexeme(Location(Position(0, 0), 0), Lexeme::Eof) , names(names) , allocator(allocator) { BOOST_STATIC_ASSERT(sizeof(kReserved) / sizeof(kReserved[0]) == Lexeme::Reserved_END - Lexeme::Reserved_BEGIN); for (int i = Lexeme::Reserved_BEGIN; i < Lexeme::Reserved_END; ++i) names.addStatic(kReserved[i - Lexeme::Reserved_BEGIN], static_cast<Lexeme::Type>(i)); // read first lexeme next(); } const Lexeme& next() { // consume whitespace or comments before the token while (isSpace(peekch()) || (peekch(0) == '-' && peekch(1) == '-')) { if (peekch(0) == '-') { consume(); consume(); skipCommentBody(); } else { while (isSpace(peekch())) consume(); } } lexeme = readNext(); return lexeme; } const Lexeme& current() const { return lexeme; } private: static bool isNewline(char ch) { return ch == '\n'; } static bool isSpace(char ch) { return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; } static bool isAlpha(char ch) { return static_cast<unsigned int>(ch - 'A') < 26 || static_cast<unsigned int>(ch - 'a') < 26; } static bool isDigit(char ch) { return static_cast<unsigned int>(ch - '0') < 10; } static char unescape(char ch) { switch (ch) { case 'a': return '\a'; case 'b': return '\b'; case 'f': return '\f'; case 'n': return '\n'; case 'r': return '\r'; case 't': return '\t'; case 'v': return '\v'; default: return ch; } } char peekch() const { return (offset < bufferSize) ? buffer[offset] : 0; } char peekch(unsigned int lookahead) const { return (offset + lookahead < bufferSize) ? buffer[offset + lookahead] : 0; } Position position() const { return Position(line, offset - lineOffset); } void consume() { if (isNewline(buffer[offset])) { line++; lineOffset = offset + 1; } offset++; } void skipCommentBody() { if (peekch() == '[') { int sep = skipLongSeparator(); if (sep >= 0) { Position start = position(); if (!readLongString(scratchData, sep)) throw Error(Location(start, position()), "Unfinished long comment"); return; } } // fall back to single-line comment while (peekch() != 0 && !isNewline(peekch())) consume(); } // Given a sequence [===[ or ]===], returns: // 1. number of equal signs (or 0 if none present) between the brackets // 2. -1 if this is not a long comment/string separator // 3. -N if this is a malformed separator // Does *not* consume the closing brace. int skipLongSeparator() { char start = peekch(); RBXASSERT(start == '[' || start == ']'); consume(); int count = 0; while (peekch() == '=') { consume(); count++; } return (start == peekch()) ? count : (-count) - 1; } bool readLongString(std::string& data, int sep) { data.clear(); // skip (second) [ RBXASSERT(peekch() == '['); consume(); // skip first newline if (isNewline(peekch())) consume(); unsigned int startOffset = offset; while (peekch()) { if (peekch() == ']') { if (skipLongSeparator() == sep) { RBXASSERT(peekch() == ']'); consume(); // skip (second) ] data.assign(buffer + startOffset, buffer + offset - sep - 2); return true; } } else { consume(); } } return false; } char readEscapedChar(const Position& start) { switch (peekch()) { case '\n': consume(); return '\n'; case '\r': consume(); if (peekch() == '\n') consume(); return '\n'; case 0: throw Error(Location(start, position()), "Unfinished string"); default: { if (isDigit(peekch())) { int code = 0; int i = 0; do { code = 10*code + (peekch() - '0'); consume(); } while (++i < 3 && isDigit(peekch())); if (code > UCHAR_MAX) throw Error(Location(start, position()), "Escape sequence too large"); return static_cast<char>(code); } else { char result = unescape(peekch()); consume(); return result; } } } } void readString(std::string& data) { Position start = position(); char delimiter = peekch(); RBXASSERT(delimiter == '\'' || delimiter == '"'); consume(); data.clear(); while (peekch() != delimiter) { switch (peekch()) { case 0: case '\r': case '\n': throw Error(Location(start, position()), "Unfinished string"); case '\\': consume(); data += readEscapedChar(start); break; default: data += peekch(); consume(); } } consume(); } void readNumber(std::string& data, unsigned int startOffset) { RBXASSERT(isDigit(peekch())); // This function does not do the number parsing - it only skips a number-like pattern. // It uses the same logic as Lua stock lexer; the resulting string is later converted // to a number with proper verification. do { consume(); } while (isDigit(peekch()) || peekch() == '.'); if (peekch() == 'e' || peekch() == 'E') { consume(); if (peekch() == '+' || peekch() == '-') consume(); } while (isAlpha(peekch()) || isDigit(peekch()) || peekch() == '_') consume(); data.assign(buffer + startOffset, buffer + offset); } Lexeme readNext() { Position start = position(); switch (peekch()) { case 0: return Lexeme(Location(start, 0), Lexeme::Eof); case '-': consume(); return Lexeme(Location(start, 1), '-'); case '[': { int sep = skipLongSeparator(); if (sep >= 0) { if (!readLongString(scratchData, sep)) throw Error(Location(start, position()), "Unfinished long string"); return Lexeme(Location(start, position()), Lexeme::String, &scratchData); } else if (sep == -1) return Lexeme(Location(start, 1), '['); else throw Error(Location(start, position()), "Invalid long string delimiter"); } case '=': { consume(); if (peekch() == '=') { consume(); return Lexeme(Location(start, 2), Lexeme::Equal); } else return Lexeme(Location(start, 1), '='); } case '<': { consume(); if (peekch() == '=') { consume(); return Lexeme(Location(start, 2), Lexeme::LessEqual); } else return Lexeme(Location(start, 1), '<'); } case '>': { consume(); if (peekch() == '=') { consume(); return Lexeme(Location(start, 2), Lexeme::GreaterEqual); } else return Lexeme(Location(start, 1), '>'); } case '~': { consume(); if (peekch() == '=') { consume(); return Lexeme(Location(start, 2), Lexeme::NotEqual); } else return Lexeme(Location(start, 1), '~'); } case '"': case '\'': readString(scratchData); return Lexeme(Location(start, position()), Lexeme::String, &scratchData); case '.': consume(); if (peekch() == '.') { consume(); if (peekch() == '.') { consume(); return Lexeme(Location(start, 3), Lexeme::Dot3); } else return Lexeme(Location(start, 2), Lexeme::Dot2); } else { if (isDigit(peekch())) { readNumber(scratchData, offset - 1); return Lexeme(Location(start, position()), Lexeme::Number, &scratchData); } else return Lexeme(Location(start, 1), '.'); } default: if (isDigit(peekch())) { readNumber(scratchData, offset); return Lexeme(Location(start, position()), Lexeme::Number, &scratchData); } else if (isAlpha(peekch()) || peekch() == '_') { unsigned int startOffset = offset; do consume(); while (isAlpha(peekch()) || isDigit(peekch()) || peekch() == '_'); scratchData.assign(buffer + startOffset, buffer + offset); std::pair<AstName, Lexeme::Type> name = names.getOrAddWithType(scratchData.c_str()); if (name.second == Lexeme::Name) return Lexeme(Location(start, position()), Lexeme::Name, name.first.value); else return Lexeme(Location(start, position()), name.second); } else { char ch = peekch(); consume(); return Lexeme(Location(start, 1), ch); } } } const char* buffer; size_t bufferSize; unsigned int offset; unsigned int line; unsigned int lineOffset; std::string scratchData; Lexeme lexeme; AstNameTable& names; Allocator& allocator; }; int gAstRttiIndex = 0; template <typename T> struct AstRtti { BOOST_STATIC_ASSERT(boost::is_class<T>::value); static const int value; }; template <typename T> const int AstRtti<T>::value = ++gAstRttiIndex; #define ASTRTTI(Class) virtual int getClassIndex() const { return AstRtti<Class>::value; } template <typename T> struct AstArray { T* data; size_t size; }; struct AstLocal { AstName name; Location location; AstLocal* shadow; unsigned int functionDepth; AstLocal(const AstName& name, const Location& location, AstLocal* shadow, unsigned int functionDepth) : name(name) , location(location) , shadow(shadow) , functionDepth(functionDepth) { } }; class AstVisitor { public: virtual ~AstVisitor() {} virtual bool visit(class AstExpr* node) { return true; } virtual bool visit(class AstExprGroup* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprConstantNil* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprConstantBool* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprConstantNumber* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprConstantString* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprLocal* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprGlobal* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprVarargs* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprCall* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprIndexName* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprIndexExpr* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprFunction* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprTable* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprUnary* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstExprBinary* node) { return visit((class AstExpr*)node); } virtual bool visit(class AstStat* node) { return true; } virtual bool visit(class AstStatBlock* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatIf* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatWhile* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatRepeat* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatBreak* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatReturn* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatExpr* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatLocal* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatFor* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatForIn* node) { return visit((class AstStat*)node); } virtual bool visit(class AstStatAssign* node) { return visit((class AstStat*)node); } }; class AstNode { public: explicit AstNode(const Location& location): location(location) {} virtual ~AstNode() {} virtual void visit(AstVisitor* visitor) = 0; virtual int getClassIndex() const = 0; template <typename T> bool is() const { return getClassIndex() == AstRtti<T>::value; } template <typename T> T* as() { return getClassIndex() == AstRtti<T>::value ? static_cast<T*>(this) : NULL; } Location location; }; class AstExpr: public AstNode { public: explicit AstExpr(const Location& location): AstNode(location) {} }; class AstStat: public AstNode { public: explicit AstStat(const Location& location): AstNode(location) {} }; class AstExprGroup: public AstExpr { public: ASTRTTI(AstExprGroup) explicit AstExprGroup(const Location& location, AstExpr* expr) : AstExpr(location) , expr(expr) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) expr->visit(visitor); } AstExpr* expr; }; class AstExprConstantNil: public AstExpr { public: ASTRTTI(AstExprConstantNil) explicit AstExprConstantNil(const Location& location) : AstExpr(location) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } }; class AstExprConstantBool: public AstExpr { public: ASTRTTI(AstExprConstantBool) AstExprConstantBool(const Location& location, bool value) : AstExpr(location) , value(value) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } bool value; }; class AstExprConstantNumber: public AstExpr { public: ASTRTTI(AstExprConstantNumber) AstExprConstantNumber(const Location& location, double value) : AstExpr(location) , value(value) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } double value; }; class AstExprConstantString: public AstExpr { public: ASTRTTI(AstExprConstantString) AstExprConstantString(const Location& location, const AstArray<char>& value) : AstExpr(location) , value(value) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } AstArray<char> value; }; class AstExprLocal: public AstExpr { public: ASTRTTI(AstExprLocal) AstExprLocal(const Location& location, AstLocal* local, bool upvalue) : AstExpr(location) , local(local) , upvalue(upvalue) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } AstLocal* local; bool upvalue; }; class AstExprGlobal: public AstExpr { public: ASTRTTI(AstExprGlobal) AstExprGlobal(const Location& location, const AstName& name) : AstExpr(location) , name(name) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } AstName name; }; class AstExprVarargs: public AstExpr { public: ASTRTTI(AstExprVarargs) AstExprVarargs(const Location& location) : AstExpr(location) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } }; class AstExprCall: public AstExpr { public: ASTRTTI(AstExprCall) AstExprCall(const Location& location, AstExpr* func, const AstArray<AstExpr*>& args, bool self) : AstExpr(location) , func(func) , args(args) , self(self) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { func->visit(visitor); for (size_t i = 0; i < args.size; ++i) args.data[i]->visit(visitor); } } AstExpr* func; AstArray<AstExpr*> args; bool self; }; class AstExprIndexName: public AstExpr { public: ASTRTTI(AstExprIndexName) AstExprIndexName(const Location& location, AstExpr* expr, const AstName& index, const Location& indexLocation) : AstExpr(location) , expr(expr) , index(index) , indexLocation(indexLocation) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) expr->visit(visitor); } AstExpr* expr; AstName index; Location indexLocation; }; class AstExprIndexExpr: public AstExpr { public: ASTRTTI(AstExprIndexExpr) AstExprIndexExpr(const Location& location, AstExpr* expr, AstExpr* index) : AstExpr(location) , expr(expr) , index(index) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { expr->visit(visitor); index->visit(visitor); } } AstExpr* expr; AstExpr* index; }; class AstExprFunction: public AstExpr { public: ASTRTTI(AstExprFunction) AstExprFunction(const Location& location, AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, AstStat* body) : AstExpr(location) , self(self) , args(args) , vararg(vararg) , body(body) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) body->visit(visitor); } AstLocal* self; AstArray<AstLocal*> args; bool vararg; AstStat* body; }; class AstExprTable: public AstExpr { public: ASTRTTI(AstExprTable) AstExprTable(const Location& location, const AstArray<AstExpr*>& pairs) : AstExpr(location) , pairs(pairs) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { for (size_t i = 0; i < pairs.size; ++i) if (pairs.data[i]) pairs.data[i]->visit(visitor); } } AstArray<AstExpr*> pairs; }; class AstExprUnary: public AstExpr { public: ASTRTTI(AstExprUnary) enum Op { Not, Minus, Len }; AstExprUnary(const Location& location, Op op, AstExpr* expr) : AstExpr(location) , op(op) , expr(expr) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) expr->visit(visitor); } Op op; AstExpr* expr; }; class AstExprBinary: public AstExpr { public: ASTRTTI(AstExprBinary) enum Op { Add, Sub, Mul, Div, Mod, Pow, Concat, CompareNe, CompareEq, CompareLt, CompareLe, CompareGt, CompareGe, And, Or }; AstExprBinary(const Location& location, Op op, AstExpr* left, AstExpr* right) : AstExpr(location) , op(op) , left(left) , right(right) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { left->visit(visitor); right->visit(visitor); } } Op op; AstExpr* left; AstExpr* right; }; class AstStatBlock: public AstStat { public: ASTRTTI(AstStatBlock) AstStatBlock(const Location& location, const AstArray<AstStat*>& body) : AstStat(location) , body(body) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { for (size_t i = 0; i < body.size; ++i) body.data[i]->visit(visitor); } } AstArray<AstStat*> body; }; class AstStatIf: public AstStat { public: ASTRTTI(AstStatIf) AstStatIf(const Location& location, AstExpr* condition, AstStat* thenbody, AstStat* elsebody) : AstStat(location) , condition(condition) , thenbody(thenbody) , elsebody(elsebody) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { condition->visit(visitor); thenbody->visit(visitor); if (elsebody) elsebody->visit(visitor); } } AstExpr* condition; AstStat* thenbody; AstStat* elsebody; }; class AstStatWhile: public AstStat { public: ASTRTTI(AstStatWhile) AstStatWhile(const Location& location, AstExpr* condition, AstStat* body) : AstStat(location) , condition(condition) , body(body) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { condition->visit(visitor); body->visit(visitor); } } AstExpr* condition; AstStat* body; }; class AstStatRepeat: public AstStat { public: ASTRTTI(AstStatRepeat) AstStatRepeat(const Location& location, AstExpr* condition, AstStat* body) : AstStat(location) , condition(condition) , body(body) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { condition->visit(visitor); body->visit(visitor); } } AstExpr* condition; AstStat* body; }; class AstStatBreak: public AstStat { public: ASTRTTI(AstStatBreak) AstStatBreak(const Location& location) : AstStat(location) { } virtual void visit(AstVisitor* visitor) { visitor->visit(this); } }; class AstStatReturn: public AstStat { public: ASTRTTI(AstStatReturn) AstStatReturn(const Location& location, const AstArray<AstExpr*>& list) : AstStat(location) , list(list) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { for (size_t i = 0; i < list.size; ++i) list.data[i]->visit(visitor); } } AstArray<AstExpr*> list; }; class AstStatExpr: public AstStat { public: ASTRTTI(AstStatExpr) AstStatExpr(const Location& location, AstExpr* expr) : AstStat(location) , expr(expr) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) expr->visit(visitor); } AstExpr* expr; }; class AstStatLocal: public AstStat { public: ASTRTTI(AstStatLocal) AstStatLocal(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values) : AstStat(location) , vars(vars) , values(values) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { for (size_t i = 0; i < values.size; ++i) values.data[i]->visit(visitor); } } AstArray<AstLocal*> vars; AstArray<AstExpr*> values; }; class AstStatFor: public AstStat { public: ASTRTTI(AstStatFor) AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStat* body) : AstStat(location) , var(var) , from(from) , to(to) , step(step) , body(body) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { from->visit(visitor); to->visit(visitor); if (step) step->visit(visitor); body->visit(visitor); } } AstLocal* var; AstExpr* from; AstExpr* to; AstExpr* step; AstStat* body; }; class AstStatForIn: public AstStat { public: ASTRTTI(AstStatForIn) AstStatForIn(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, AstStat* body) : AstStat(location) , vars(vars) , values(values) , body(body) { } AstArray<AstLocal*> vars; AstArray<AstExpr*> values; AstStat* body; virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { for (size_t i = 0; i < values.size; ++i) values.data[i]->visit(visitor); body->visit(visitor); } } }; class AstStatAssign: public AstStat { public: ASTRTTI(AstStatAssign) AstStatAssign(const Location& location, const AstArray<AstExpr*>& vars, const AstArray<AstExpr*>& values) : AstStat(location) , vars(vars) , values(values) { } virtual void visit(AstVisitor* visitor) { if (visitor->visit(this)) { for (size_t i = 0; i < vars.size; ++i) vars.data[i]->visit(visitor); for (size_t i = 0; i < values.size; ++i) values.data[i]->visit(visitor); } } AstArray<AstExpr*> vars; AstArray<AstExpr*> values; }; template <typename T> class TempVector { public: explicit TempVector(std::vector<T>& storage) : storage(storage) , offset(storage.size()) { } ~TempVector() { RBXASSERT(storage.size() >= offset); storage.erase(storage.begin() + offset, storage.end()); } const T& operator[](size_t index) const { return storage[offset + index]; } const T& front() const { return storage[offset]; } const T& back() const { return storage.back(); } bool empty() const { return storage.size() == offset; } size_t size() const { return storage.size() - offset; } void push_back(const T& item) { storage.push_back(item); } private: std::vector<T>& storage; unsigned int offset; }; class Parser { public: static AstStat* parse(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator) { Parser p(buffer, bufferSize, names, allocator); return p.parseChunk(); } private: struct Name; Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator) : lexer(buffer, bufferSize, names, allocator) , allocator(allocator) , localMap(AstName()) { Function top; top.vararg = true; functionStack.push_back(top); nameSelf = names.addStatic("self"); } bool blockFollow(const Lexeme& l) { return l.type == Lexeme::Eof || l.type == Lexeme::ReservedElse || l.type == Lexeme::ReservedElseif || l.type == Lexeme::ReservedEnd || l.type == Lexeme::ReservedUntil; } AstStat* parseChunk() { AstStat* result = parseBlock(); expect(Lexeme::Eof); return result; } // chunk ::= {stat [`;']} [laststat [`;']] // block ::= chunk AstStat* parseBlock() { unsigned int localsBegin = saveLocals(); AstStat* result = parseBlockNoScope(); restoreLocals(localsBegin); return result; } AstStat* parseBlockNoScope() { TempVector<AstStat*> body(scratchStat); while (!blockFollow(lexer.current())) { std::pair<AstStat*, bool> stat = parseStat(); if (lexer.current().type == ';') lexer.next(); body.push_back(stat.first); if (stat.second) break; } Location location = body.empty() ? lexer.current().location : Location(body.front()->location, body.back()->location); return new (allocator) AstStatBlock(location, copy(body)); } // stat ::= // varlist `=' explist | // functioncall | // do block end | // while exp do block end | // repeat block until exp | // if exp then block {elseif exp then block} [else block] end | // for Name `=' exp `,' exp [`,' exp] do block end | // for namelist in explist do block end | // function funcname funcbody | // local function Name funcbody | // local namelist [`=' explist] // laststat ::= return [explist] | break std::pair<AstStat*, bool> parseStat() { switch (lexer.current().type) { case Lexeme::ReservedIf: return std::make_pair(parseIf(), false); case Lexeme::ReservedWhile: return std::make_pair(parseWhile(), false); case Lexeme::ReservedDo: return std::make_pair(parseDo(), false); case Lexeme::ReservedFor: return std::make_pair(parseFor(), false); case Lexeme::ReservedRepeat: return std::make_pair(parseRepeat(), false); case Lexeme::ReservedFunction: return std::make_pair(parseFunctionStat(), false); case Lexeme::ReservedLocal: return std::make_pair(parseLocal(), false); case Lexeme::ReservedReturn: return std::make_pair(parseReturn(), true); case Lexeme::ReservedBreak: return std::make_pair(parseBreak(), true); default: return std::make_pair(parseAssignmentOrCall(), false); } } // if exp then block {elseif exp then block} [else block] end AstStat* parseIf() { Location start = lexer.current().location; lexer.next(); // if / elseif AstExpr* cond = parseExpr(); Lexeme matchThenElse = lexer.current(); expect(Lexeme::ReservedThen); lexer.next(); AstStat* thenbody = parseBlock(); AstStat* elsebody = NULL; Location end = start; if (lexer.current().type == Lexeme::ReservedElseif) { elsebody = parseIf(); end = elsebody->location; } else { if (lexer.current().type == Lexeme::ReservedElse) { matchThenElse = lexer.current(); lexer.next(); elsebody = parseBlock(); if (FFlag::StudioVariableIntellesense) elsebody->location.begin = matchThenElse.location.end; } end = lexer.current().location; expectMatch(Lexeme::ReservedEnd, matchThenElse); lexer.next(); } return new (allocator) AstStatIf(Location(start, end), cond, thenbody, elsebody); } // while exp do block end AstStat* parseWhile() { Location start = lexer.current().location; lexer.next(); // while AstExpr* cond = parseExpr(); Lexeme matchDo = lexer.current(); expect(Lexeme::ReservedDo); lexer.next(); functionStack.back().loopDepth++; AstStat* body = parseBlock(); functionStack.back().loopDepth--; Location end = lexer.current().location; expectMatch(Lexeme::ReservedEnd, matchDo); lexer.next(); return new (allocator) AstStatWhile(Location(start, end), cond, body); } // repeat block until exp AstStat* parseRepeat() { Location start = lexer.current().location; Lexeme matchRepeat = lexer.current(); lexer.next(); // repeat unsigned int localsBegin = saveLocals(); functionStack.back().loopDepth++; AstStat* body = parseBlockNoScope(); functionStack.back().loopDepth--; expectMatch(Lexeme::ReservedUntil, matchRepeat); lexer.next(); AstExpr* cond = parseExpr(); restoreLocals(localsBegin); return new (allocator) AstStatRepeat(Location(start, cond->location), cond, body); } // do block end AstStat* parseDo() { Lexeme matchDo = lexer.current(); lexer.next(); // do AstStat* body = parseBlock(); expectMatch(Lexeme::ReservedEnd, matchDo); lexer.next(); return body; } // break AstStat* parseBreak() { if (functionStack.back().loopDepth > 0) { Location start = lexer.current().location; lexer.next(); // break return new (allocator) AstStatBreak(start); } else throw Error(lexer.current().location, "No loop to break"); } // for Name `=' exp `,' exp [`,' exp] do block end | // for namelist in explist do block end | AstStat* parseFor() { Location start = lexer.current().location; lexer.next(); // for Name varname = parseName(); if (lexer.current().type == '=') { lexer.next(); AstExpr* from = parseExpr(); expect(','); lexer.next(); AstExpr* to = parseExpr(); AstExpr* step = NULL; if (lexer.current().type == ',') { lexer.next(); step = parseExpr(); } Lexeme matchDo = lexer.current(); expect(Lexeme::ReservedDo); lexer.next(); unsigned int localsBegin = saveLocals(); AstLocal* var = pushLocal(varname); functionStack.back().loopDepth++; AstStat* body = parseBlock(); functionStack.back().loopDepth--; restoreLocals(localsBegin); Location end = lexer.current().location; expectMatch(Lexeme::ReservedEnd, matchDo); lexer.next(); return new (allocator) AstStatFor(Location(start, end), var, from, to, step, body); } else { TempVector<Name> names(scratchName); names.push_back(varname); if (lexer.current().type == ',') { lexer.next(); parseNameList(names); } expect(Lexeme::ReservedIn); lexer.next(); TempVector<AstExpr*> values(scratchExpr); parseExprList(values); Lexeme matchDo = lexer.current(); expect(Lexeme::ReservedDo); lexer.next(); unsigned int localsBegin = saveLocals(); TempVector<AstLocal*> vars(scratchLocal); for (size_t i = 0; i < names.size(); ++i) vars.push_back(pushLocal(names[i])); functionStack.back().loopDepth++; AstStat* body = parseBlock(); functionStack.back().loopDepth--; restoreLocals(localsBegin); Location end = lexer.current().location; expectMatch(Lexeme::ReservedEnd, matchDo); lexer.next(); return new (allocator) AstStatForIn(Location(start, end), copy(vars), copy(values), body); } } // function funcname funcbody | // funcname ::= Name {`.' Name} [`:' Name] AstStat* parseFunctionStat() { Location start = lexer.current().location; Lexeme matchFunction = lexer.current(); lexer.next(); // parse funcname into a chain of indexing operators AstExpr* expr = parseNameExpr(); while (lexer.current().type == '.') { lexer.next(); Name name = parseName(); expr = new (allocator) AstExprIndexName(Location(start, name.location), expr, name.name, name.location); } // finish with : bool hasself = false; if (lexer.current().type == ':') { lexer.next(); Name name = parseName(); expr = new (allocator) AstExprIndexName(Location(start, name.location), expr, name.name, name.location); hasself = true; } AstExpr* body = parseFunctionBody(hasself, matchFunction); return new (allocator) AstStatAssign(Location(start, body->location), copy(&expr, 1), copy(&body, 1)); } // local function Name funcbody | // local namelist [`=' explist] AstStat* parseLocal() { Location start = lexer.current().location; lexer.next(); // local if (lexer.current().type == Lexeme::ReservedFunction) { Lexeme matchFunction = lexer.current(); lexer.next(); Name name = parseName(); AstLocal* var = pushLocal(name); AstExpr* body = parseFunctionBody(false, matchFunction); return new (allocator) AstStatLocal(Location(start, body->location), copy(&var, 1), copy(&body, 1)); } else { TempVector<Name> names(scratchName); parseNameList(names); TempVector<AstLocal*> vars(scratchLocal); TempVector<AstExpr*> values(scratchExpr); if (lexer.current().type == '=') { lexer.next(); parseExprList(values); } for (size_t i = 0; i < names.size(); ++i) vars.push_back(pushLocal(names[i])); Location end = values.empty() ? names.back().location : values.back()->location; return new (allocator) AstStatLocal(Location(start, end), copy(vars), copy(values)); } } // return [explist] AstStat* parseReturn() { Location start = lexer.current().location; lexer.next(); TempVector<AstExpr*> list(scratchExpr); if (!blockFollow(lexer.current()) && lexer.current().type != ';') parseExprList(list); Location end = list.empty() ? start : list.back()->location; return new (allocator) AstStatReturn(Location(start, end), copy(list)); } // varlist `=' explist | // functioncall | AstStat* parseAssignmentOrCall() { AstExpr* expr = parsePrimaryExpr(); if (expr->is<AstExprCall>()) return new (allocator) AstStatExpr(expr->location, expr); else return parseAssignment(expr); } bool isExprVar(AstExpr* expr) { return expr->is<AstExprLocal>() || expr->is<AstExprGlobal>() || expr->is<AstExprIndexExpr>() || expr->is<AstExprIndexName>(); } AstStat* parseAssignment(AstExpr* initial) { // The initial expr has to be a var if (!isExprVar(initial)) throw Error(initial->location, "Syntax error: expression must be a variable or a field"); TempVector<AstExpr*> vars(scratchExpr); vars.push_back(initial); while (lexer.current().type == ',') { lexer.next(); AstExpr* expr = parsePrimaryExpr(); if (!isExprVar(expr)) throw Error(expr->location, "Syntax error: expression must be a variable or a field"); vars.push_back(expr); } expect('='); lexer.next(); TempVector<AstExpr*> values(scratchExprAux); parseExprList(values); return new (allocator) AstStatAssign(Location(initial->location, values.back()->location), copy(vars), copy(values)); } // funcbody ::= `(' [parlist] `)' block end // parlist ::= namelist [`,' `...'] | `...' AstExprFunction* parseFunctionBody(bool hasself, const Lexeme& matchFunction) { Location start = lexer.current().location; Lexeme matchParen = lexer.current(); expect('('); lexer.next(); TempVector<Name> args(scratchName); bool vararg = false; if (lexer.current().type != ')') vararg = parseNameList(args, /* allowDot3= */ true); expectMatch(')', matchParen); lexer.next(); unsigned int localsBegin = saveLocals(); AstLocal* self = NULL; if (hasself) { self = pushLocal(Name(nameSelf, start)); } TempVector<AstLocal*> vars(scratchLocal); for (size_t i = 0; i < args.size(); ++i) vars.push_back(pushLocal(args[i])); Function fun; fun.vararg = vararg; functionStack.push_back(fun); AstStat* body = parseBlock(); functionStack.pop_back(); restoreLocals(localsBegin); Location end = lexer.current().location; expectMatch(Lexeme::ReservedEnd, matchFunction); lexer.next(); return new (allocator) AstExprFunction(Location(start, end), self, copy(vars), vararg, body); } // explist ::= {exp `,'} exp void parseExprList(TempVector<AstExpr*>& result) { result.push_back(parseExpr()); while (lexer.current().type == ',') { lexer.next(); result.push_back(parseExpr()); } } // namelist ::= Name {`,' Name} bool parseNameList(TempVector<Name>& result, bool allowDot3 = false) { if (lexer.current().type == Lexeme::Dot3 && allowDot3) { lexer.next(); return true; } result.push_back(parseName()); while (lexer.current().type == ',') { lexer.next(); if (lexer.current().type == Lexeme::Dot3 && allowDot3) { lexer.next(); return true; } else { result.push_back(parseName()); } } return false; } static boost::optional<AstExprUnary::Op> parseUnaryOp(const Lexeme& l) { if (l.type == Lexeme::ReservedNot) return AstExprUnary::Not; else if (l.type == '-') return AstExprUnary::Minus; else if (l.type == '#') return AstExprUnary::Len; else return boost::optional<AstExprUnary::Op>(); } static boost::optional<AstExprBinary::Op> parseBinaryOp(const Lexeme& l) { if (l.type == '+') return AstExprBinary::Add; else if (l.type == '-') return AstExprBinary::Sub; else if (l.type == '*') return AstExprBinary::Mul; else if (l.type == '/') return AstExprBinary::Div; else if (l.type == '%') return AstExprBinary::Mod; else if (l.type == '^') return AstExprBinary::Pow; else if (l.type == Lexeme::Dot2) return AstExprBinary::Concat; else if (l.type == Lexeme::NotEqual) return AstExprBinary::CompareNe; else if (l.type == Lexeme::Equal) return AstExprBinary::CompareEq; else if (l.type == '<') return AstExprBinary::CompareLt; else if (l.type == Lexeme::LessEqual) return AstExprBinary::CompareLe; else if (l.type == '>') return AstExprBinary::CompareGt; else if (l.type == Lexeme::GreaterEqual) return AstExprBinary::CompareGe; else if (l.type == Lexeme::ReservedAnd) return AstExprBinary::And; else if (l.type == Lexeme::ReservedOr) return AstExprBinary::Or; else return boost::optional<AstExprBinary::Op>(); } struct BinaryOpPriority { unsigned char left, right; }; // subexpr -> (simpleexp | unop subexpr) { binop subexpr } // where `binop' is any binary operator with a priority higher than `limit' std::pair<AstExpr*, boost::optional<AstExprBinary::Op> > parseSubExpr(unsigned int limit) { static const BinaryOpPriority binaryPriority[] = { {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, // `+' `-' `/' `%' {10, 9}, {5, 4}, // power and concat (right associative) {3, 3}, {3, 3}, // equality and inequality {3, 3}, {3, 3}, {3, 3}, {3, 3}, // order {2, 2}, {1, 1} // logical (and/or) }; const unsigned int unaryPriority = 8; Location start = lexer.current().location; AstExpr* expr; if (boost::optional<AstExprUnary::Op> uop = parseUnaryOp(lexer.current())) { lexer.next(); AstExpr* subexpr = parseSubExpr(unaryPriority).first; expr = new (allocator) AstExprUnary(Location(start, subexpr->location), uop.get(), subexpr); } else { expr = parseSimpleExpr(); } // expand while operators have priorities higher than `limit' boost::optional<AstExprBinary::Op> op = parseBinaryOp(lexer.current()); while (op && binaryPriority[op.get()].left > limit) { lexer.next(); // read sub-expression with higher priority std::pair<AstExpr*, boost::optional<AstExprBinary::Op> > next = parseSubExpr(binaryPriority[op.get()].right); expr = new (allocator) AstExprBinary(Location(start, next.first->location), op.get(), expr, next.first); op = next.second; } return std::make_pair(expr, op); // return first untreated operator } AstExpr* parseExpr() { return parseSubExpr(0).first; } // NAME AstExpr* parseNameExpr() { Name name = parseName(); AstLocal* const * value = localMap.find(name.name); if (value && *value) { AstLocal* local = *value; return new (allocator) AstExprLocal(name.location, local, local->functionDepth != functionStack.size()); } return new (allocator) AstExprGlobal(name.location, name.name); } // prefixexp -> NAME | '(' expr ')' AstExpr* parsePrefixExpr() { if (lexer.current().type == '(') { Location start = lexer.current().location; Lexeme matchParen = lexer.current(); lexer.next(); AstExpr* expr = parseExpr(); Location end = lexer.current().location; expectMatch(')', matchParen); lexer.next(); return new (allocator) AstExprGroup(Location(start, end), expr); } else { return parseNameExpr(); } } // primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs } AstExpr* parsePrimaryExpr() { Location start = lexer.current().location; AstExpr* expr = parsePrefixExpr(); while (true) { if (lexer.current().type == '.') { lexer.next(); Name index = parseName(); expr = new (allocator) AstExprIndexName(Location(start, index.location), expr, index.name, index.location); } else if (lexer.current().type == '[') { Lexeme matchBracket = lexer.current(); lexer.next(); AstExpr* index = parseExpr(); Location end = lexer.current().location; expectMatch(']', matchBracket); lexer.next(); expr = new (allocator) AstExprIndexExpr(Location(start, end), expr, index); } else if (lexer.current().type == ':') { lexer.next(); Name index = parseName(); AstExpr* func = new (allocator) AstExprIndexName(Location(start, index.location), expr, index.name, index.location); expr = parseFunctionArgs(func, true); } else if (lexer.current().type == '{' || lexer.current().type == '(' || lexer.current().type == Lexeme::String) { expr = parseFunctionArgs(expr, false); } else { break; } } return expr; } // simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp AstExpr* parseSimpleExpr() { Location start = lexer.current().location; if (lexer.current().type == Lexeme::ReservedNil) { lexer.next(); return new (allocator) AstExprConstantNil(start); } else if (lexer.current().type == Lexeme::ReservedTrue) { lexer.next(); return new (allocator) AstExprConstantBool(start, true); } else if (lexer.current().type == Lexeme::ReservedFalse) { lexer.next(); return new (allocator) AstExprConstantBool(start, false); } else if (lexer.current().type == Lexeme::ReservedFunction) { Lexeme matchFunction = lexer.current(); lexer.next(); return parseFunctionBody(false, matchFunction); } else if (lexer.current().type == Lexeme::Number) { const char* datap = lexer.current().data->c_str(); char* dataend = NULL; double value = strtod(datap, &dataend); // maybe a hexadecimal constant? if (*dataend == 'x' || *dataend == 'X') value = strtoul(datap, &dataend, 16); if (*dataend == 0) { lexer.next(); return new (allocator) AstExprConstantNumber(start, value); } else throw Error(lexer.current().location, "Malformed number"); } else if (lexer.current().type == Lexeme::String) { AstArray<char> value = copy(*lexer.current().data); lexer.next(); return new (allocator) AstExprConstantString(start, value); } else if (lexer.current().type == Lexeme::Dot3) { if (functionStack.back().vararg) { lexer.next(); return new (allocator) AstExprVarargs(start); } else throw Error(lexer.current().location, "Cannot use '...' outside a vararg function"); } else if (lexer.current().type == '{') { return parseTableConstructor(); } else { return parsePrimaryExpr(); } } // args ::= `(' [explist] `)' | tableconstructor | String AstExprCall* parseFunctionArgs(AstExpr* func, bool self) { if (lexer.current().type == '(') { if (func->location.end.line != lexer.current().location.begin.line) throw Error(lexer.current().location, "Ambiguous syntax: this looks like an argument list for a function call, but could also be a start of new statement"); Lexeme matchParen = lexer.current(); lexer.next(); TempVector<AstExpr*> args(scratchExpr); if (lexer.current().type != ')') parseExprList(args); Location end = lexer.current().location; expectMatch(')', matchParen); lexer.next(); return new (allocator) AstExprCall(Location(func->location, end), func, copy(args), self); } else if (lexer.current().type == '{') { AstExpr* expr = parseTableConstructor(); return new (allocator) AstExprCall(Location(func->location, expr->location), func, copy(&expr, 1), self); } else if (lexer.current().type == Lexeme::String) { AstExpr* expr = new (allocator) AstExprConstantString(lexer.current().location, copy(*lexer.current().data)); lexer.next(); return new (allocator) AstExprCall(Location(func->location, expr->location), func, copy(&expr, 1), self); } else { throw Error(lexer.current().location, "Expected '(', '{' or <string>, got %s", lexer.current().toString().c_str()); } } // tableconstructor ::= `{' [fieldlist] `}' // fieldlist ::= field {fieldsep field} [fieldsep] // field ::= `[' exp `]' `=' exp | Name `=' exp | exp // fieldsep ::= `,' | `;' AstExpr* parseTableConstructor() { TempVector<AstExpr*> pairs(scratchExpr); Location start = lexer.current().location; Lexeme matchBrace = lexer.current(); expect('{'); lexer.next(); while (lexer.current().type != '}') { if (lexer.current().type == '[') { Lexeme matchLocationBracket = lexer.current(); lexer.next(); AstExpr* key = parseExpr(); expectMatch(']', matchLocationBracket); lexer.next(); expect('='); lexer.next(); AstExpr* value = parseExpr(); pairs.push_back(key); pairs.push_back(value); } else { AstExpr* expr = parseExpr(); if (lexer.current().type == '=') { lexer.next(); AstName name; if (AstExprLocal* e = expr->as<AstExprLocal>()) name = e->local->name; else if (AstExprGlobal* e = expr->as<AstExprGlobal>()) name = e->name; else throw Error(expr->location, "Expected a name, got a complex expression"); AstArray<char> nameString; nameString.data = const_cast<char*>(name.value); nameString.size = strlen(name.value); AstExpr* key = new (allocator) AstExprConstantString(expr->location, nameString); AstExpr* value = parseExpr(); pairs.push_back(key); pairs.push_back(value); } else { pairs.push_back(NULL); pairs.push_back(expr); } } if (lexer.current().type == ',' || lexer.current().type == ';') lexer.next(); else expectMatch('}', matchBrace); } Location end = lexer.current().location; lexer.next(); return new (allocator) AstExprTable(Location(start, end), copy(pairs)); } // Name Name parseName() { if (lexer.current().type != Lexeme::Name) throw Error(lexer.current().location, "Expected identifier, got %s", lexer.current().toString().c_str()); Name result(AstName(lexer.current().name), lexer.current().location); lexer.next(); return result; } AstLocal* pushLocal(const Name& name) { AstLocal*& local = localMap[name.name]; local = new (allocator) AstLocal(name.name, name.location, local, functionStack.size()); localStack.push_back(local); return local; } unsigned int saveLocals() { return localStack.size(); } void restoreLocals(unsigned int offset) { for (size_t i = localStack.size(); i > offset; --i) { AstLocal* l = localStack[i - 1]; localMap[l->name] = l->shadow; } localStack.resize(offset); } void expect(char value) { expect(static_cast<Lexeme::Type>(static_cast<unsigned char>(value))); } void expect(Lexeme::Type type) { if (lexer.current().type != type) throw Error(lexer.current().location, "Expected %s, got %s", Lexeme(Location(Position(0, 0), 0), type).toString().c_str(), lexer.current().toString().c_str()); } void expectMatch(char value, const Lexeme& begin) { expectMatch(static_cast<Lexeme::Type>(static_cast<unsigned char>(value)), begin); } void expectMatch(Lexeme::Type type, const Lexeme& begin) { if (lexer.current().type != type) { std::string typeString = Lexeme(Location(Position(0, 0), 0), type).toString(); if (lexer.current().location.begin.line == begin.location.begin.line) throw Error(lexer.current().location, "Expected %s (to close %s at column %d), got %s", typeString.c_str(), begin.toString().c_str(), begin.location.begin.column + 1, lexer.current().toString().c_str()); else throw Error(lexer.current().location, "Expected %s (to close %s at line %d), got %s", typeString.c_str(), begin.toString().c_str(), begin.location.begin.line + 1, lexer.current().toString().c_str()); } } template <typename T> AstArray<T> copy(const T* data, size_t size) { AstArray<T> result; result.data = size ? new (allocator) T[size] : NULL; result.size = size; std::copy(data, data + size, result.data); return result; } template <typename T> AstArray<T> copy(const TempVector<T>& data) { return copy(data.empty() ? NULL : &data[0], data.size()); } AstArray<char> copy(const std::string& data) { AstArray<char> result = copy(data.c_str(), data.size() + 1); result.size = data.size(); return result; } struct Function { bool vararg; unsigned int loopDepth; Function() : vararg(false) , loopDepth(0) { } }; struct Local { AstLocal* local; unsigned int offset; Local() : local(NULL) , offset(0) { } }; struct Name { AstName name; Location location; Name(const AstName& name, const Location& location) : name(name) , location(location) { } }; Lexer lexer; Allocator& allocator; AstName nameSelf; std::vector<Function> functionStack; DenseHashMap<AstName, AstLocal*> localMap; std::vector<AstLocal*> localStack; std::vector<AstStat*> scratchStat; std::vector<AstExpr*> scratchExpr; std::vector<AstExpr*> scratchExprAux; std::vector<Name> scratchName; std::vector<AstLocal*> scratchLocal; }; RBX_PRINTF_ATTR(4, 5) void emitWarning(ScriptAnalyzer::Result& result, ScriptAnalyzer::WarningCode code, const Location& location, const char* format, ...) { if (FInt::ScriptAnalyzerIgnoreWarnings & (1 << code)) return; va_list args; va_start(args, format); std::string message = vformat(format, args); va_end(args); Warning warning(code, ScriptAnalyzer::Location(ScriptAnalyzer::Position(location.begin.line, location.begin.column), ScriptAnalyzer::Position(location.end.line, location.end.column)), message); result.warnings.push_back(warning); } struct AnalyzerContext { ScriptAnalyzer::Result* result; AstStat* root; AstName placeholder; DenseHashMap<AstName, Reflection::Variant> builtinGlobals; DenseHashMap<AstName, const char*> deprecatedGlobals; AnalyzerContext(ScriptAnalyzer::Result* result, AstStat* root) : result(result) , root(root) , builtinGlobals(AstName()) , deprecatedGlobals(AstName()) { } void fillNames(AstNameTable& names) { placeholder = names.getOrAdd("_"); } void fillBuiltinGlobals(AstNameTable& names, lua_State* L) { lua_pushvalue(L, LUA_GLOBALSINDEX); lua_pushnil(L); while (lua_next(L, -2)) { if (const char* name = lua_tostring(L, -2)) { int top = lua_gettop(L); Reflection::Variant value; try { Lua::LuaArguments::get(L, -1, value, true); } catch (RBX::base_exception&) { // swallow the exception - mainly happens if you have tables with non-string keys in the environment } lua_settop(L, top); builtinGlobals[names.getOrAdd(name)] = value; } lua_pop(L, 1); } lua_pop(L, 1); } void fillDeprecatedGlobals(AstNameTable& names) { // Global names with inconsistent case deprecatedGlobals[names.getOrAdd("Game")] = "game"; deprecatedGlobals[names.getOrAdd("Workspace")] = "workspace"; // Global functions with inconsistent case deprecatedGlobals[names.getOrAdd("Delay")] = "delay"; deprecatedGlobals[names.getOrAdd("ElapsedTime")] = "elapsedTime"; deprecatedGlobals[names.getOrAdd("Spawn")] = "spawn"; deprecatedGlobals[names.getOrAdd("Wait")] = "wait"; // Global functions that should not exist deprecatedGlobals[names.getOrAdd("DebuggerManager")] = NULL; deprecatedGlobals[names.getOrAdd("PluginManager")] = NULL; deprecatedGlobals[names.getOrAdd("printidentity")] = NULL; deprecatedGlobals[names.getOrAdd("Stats")] = NULL; deprecatedGlobals[names.getOrAdd("stats")] = NULL; deprecatedGlobals[names.getOrAdd("Version")] = NULL; deprecatedGlobals[names.getOrAdd("version")] = NULL; deprecatedGlobals[names.getOrAdd("settings")] = NULL; // Lua globals that don't work (security) but are still defined deprecatedGlobals[names.getOrAdd("load")] = NULL; deprecatedGlobals[names.getOrAdd("dofile")] = NULL; deprecatedGlobals[names.getOrAdd("loadfile")] = NULL; } }; struct AnalyzerPass { const char* name; void (*process)(AnalyzerContext& context); }; class AnalyzerPassIntellesenseLocal: AstVisitor { public: static void process (AnalyzerContext& context) { AnalyzerPassIntellesenseLocal pass; pass.context = &context; pass.firstResult = true; context.root->visit(&pass); pass.report(); } private: AnalyzerContext* context; ScriptAnalyzer::IntellesenseResult result; ScriptAnalyzer::IntellesenseResult* currentBlock; ScriptAnalyzer::Location lastLocation; int positionInBlock; bool firstResult; void report() { if (!firstResult) context->result->intellesenseAnalysis = result.children; } virtual bool visit(AstStatLocal* node) { ScriptAnalyzer::IntellesenseResult localResult; localResult.name = node->vars.data[0]->name.value; localResult.location = node->location; localResult.isLocal = true; localResult.isFunction = false; currentBlock->children[positionInBlock] = localResult; return true; } virtual bool visit(AstStat* node) { lastLocation = node->location; return true; } virtual bool visit(AstExprGlobal* node) { if (context->builtinGlobals.find(node->name) || !currentBlock->children[positionInBlock].name.empty()) return false; ScriptAnalyzer::IntellesenseResult globalResult; globalResult.name = node->name.value; globalResult.location = node->location; globalResult.isLocal = false; globalResult.isFunction = false; currentBlock->children[positionInBlock] = globalResult; return true; } virtual bool visit(AstExprCall* node) { return false; } virtual bool visit(AstExprFunction* node) { if (AstStatBlock* blockNode = dynamic_cast<AstStatBlock*>(node->body)) { ScriptAnalyzer::IntellesenseResult* localResult = ¤tBlock->children[positionInBlock]; localResult->isFunction = true; localResult->location = node->location; int numberOfLocalVars = (int)node->args.size; localResult->children.resize(blockNode->body.size + numberOfLocalVars); for (int i = 0; i < numberOfLocalVars; ++i) { localResult->children[i].name = node->args.data[i]->name.value; localResult->children[i].location = node->args.data[i]->location; localResult->children[i].isLocal = true; } for (size_t i = 0; i < blockNode->body.size; ++i) { positionInBlock = i + numberOfLocalVars; currentBlock = localResult; blockNode->body.data[i]->visit(this); } } return false; } virtual bool visit(AstStatIf* node) { if (visit((class AstStat*)node)) { node->condition->visit(this); ScriptAnalyzer::IntellesenseResult* localResult = ¤tBlock->children[positionInBlock]; localResult->location = lastLocation; int sizeOfBlock = node->elsebody ? 2 : 1; currentBlock = localResult; currentBlock->children.resize(sizeOfBlock); positionInBlock = 0; if (node->elsebody) lastLocation.end = node->elsebody->location.begin; node->thenbody->visit(this); if (node->elsebody) { lastLocation.begin = node->elsebody->location.begin; lastLocation.end = localResult->location.end; currentBlock = localResult; positionInBlock = 1; node->elsebody->visit(this); } } return false; } virtual bool visit(AstStatFor* node) { if (AstStatBlock* blockNode = dynamic_cast<AstStatBlock*>(node->body)) { ScriptAnalyzer::IntellesenseResult* localResult = ¤tBlock->children[positionInBlock]; localResult->location = node->location; localResult->children.resize(blockNode->body.size + 1); localResult->children[0].name = node->var->name.value; localResult->children[0].location = node->var->location; localResult->children[0].isLocal = true; for (size_t i = 0; i < blockNode->body.size; ++i) { positionInBlock = i + 1; currentBlock = localResult; blockNode->body.data[i]->visit(this); } } return false; } virtual bool visit(AstStatForIn* node) { if (AstStatBlock* blockNode = dynamic_cast<AstStatBlock*>(node->body)) { ScriptAnalyzer::IntellesenseResult* localResult = ¤tBlock->children[positionInBlock]; localResult->location = node->location; int numberOfLocalVars = (int)node->vars.size; localResult->children.resize(blockNode->body.size + numberOfLocalVars); for (int i = 0; i < numberOfLocalVars; ++i) { localResult->children[i].name = node->vars.data[i]->name.value; localResult->children[i].location = node->vars.data[i]->location; localResult->children[i].isLocal = true; } for (size_t i = 0; i < blockNode->body.size; ++i) { positionInBlock = i + numberOfLocalVars; currentBlock = localResult; blockNode->body.data[i]->visit(this); } } return false; } virtual bool visit(AstStatBlock* node) { ScriptAnalyzer::IntellesenseResult* localResult; if (firstResult) { result.location = node->location; localResult = &result; firstResult = false; } else { ScriptAnalyzer::IntellesenseResult blockResult; blockResult.location = lastLocation; currentBlock->children[positionInBlock] = blockResult; localResult = ¤tBlock->children[positionInBlock]; } localResult->children.resize(node->body.size); for (size_t i = 0; i < node->body.size; ++i) { positionInBlock = i; currentBlock = localResult; node->body.data[i]->visit(this); } return false; } }; class AnalyzerPassWarnGlobalLocal: AstVisitor { public: static void process(AnalyzerContext& context) { AnalyzerPassWarnGlobalLocal pass; pass.context = &context; for (DenseHashMap<AstName, Reflection::Variant>::const_iterator it = context.builtinGlobals.begin(); it != context.builtinGlobals.end(); ++it) pass.globals[*it].builtin = true; for (DenseHashMap<AstName, const char*>::const_iterator it = context.deprecatedGlobals.begin(); it != context.deprecatedGlobals.end(); ++it) pass.globals[*it].deprecated = &it.getItem().value; context.root->visit(&pass); pass.report(); } private: struct Global { AstExprGlobal* firstRef; AstExprFunction* onlyFunctionRef; bool assigned; bool builtin; const char* const * deprecated; Global() : firstRef(NULL) , onlyFunctionRef(NULL) , assigned(false) , builtin(false) , deprecated(NULL) { } }; AnalyzerContext* context; DenseHashMap<AstName, Global> globals; std::vector<AstExprGlobal*> globalRefs; std::vector<AstExprFunction*> functionStack; AnalyzerPassWarnGlobalLocal() : globals(AstName()) { } void report() { for (size_t i = 0; i < globalRefs.size(); ++i) { AstExprGlobal* gv = globalRefs[i]; Global* g = globals.find(gv->name); if (!g || (!g->assigned && !g->builtin)) emitWarning(*context->result, ScriptAnalyzer::Warning_UnknownGlobal, gv->location, "Unknown global '%s'", gv->name.value); else if (g->deprecated) { if (*g->deprecated) emitWarning(*context->result, ScriptAnalyzer::Warning_DeprecatedGlobal, gv->location, "Global '%s' is deprecated, use '%s' instead", gv->name.value, *g->deprecated); else emitWarning(*context->result, ScriptAnalyzer::Warning_DeprecatedGlobal, gv->location, "Global '%s' is deprecated", gv->name.value); } } for (DenseHashMap<AstName, Global>::const_iterator it = globals.begin(); it != globals.end(); ++it) { const Global& g = it.getItem().value; if (g.onlyFunctionRef && g.assigned && g.firstRef->name != context->placeholder) emitWarning(*context->result, ScriptAnalyzer::Warning_GlobalUsedAsLocal, g.firstRef->location, "Global '%s' is only used in the enclosing function; consider changing it to local", g.firstRef->name.value); } } virtual bool visit(AstExprFunction* node) { functionStack.push_back(node); node->body->visit(this); functionStack.pop_back(); return false; } virtual bool visit(AstExprGlobal* node) { trackGlobalRef(node); if (node->name == context->placeholder) emitWarning(*context->result, ScriptAnalyzer::Warning_Placeholder, node->location, "Placeholder value '_' is read here; consider using a named variable"); return true; } virtual bool visit(AstExprLocal* node) { if (node->local->name == context->placeholder) emitWarning(*context->result, ScriptAnalyzer::Warning_Placeholder, node->location, "Placeholder value '_' is read here; consider using a named variable"); return true; } virtual bool visit(AstStatAssign* node) { for (size_t i = 0; i < node->vars.size; ++i) { AstExpr* var = node->vars.data[i]; if (AstExprGlobal* gv = var->as<AstExprGlobal>()) { Global& g = globals[gv->name]; if (g.builtin) emitWarning(*context->result, ScriptAnalyzer::Warning_BuiltinGlobalWrite, gv->location, "Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value); else g.assigned = true; trackGlobalRef(gv); } else if (var->is<AstExprLocal>()) ; else var->visit(this); } for (size_t i = 0; i < node->values.size; ++i) node->values.data[i]->visit(this); return false; } void trackGlobalRef(AstExprGlobal* node) { AstExprFunction* function = functionStack.empty() ? NULL : functionStack.back(); Global& g = globals[node->name]; globalRefs.push_back(node); if (!g.firstRef) { g.firstRef = node; g.onlyFunctionRef = function; } else { if (g.onlyFunctionRef != function) g.onlyFunctionRef = NULL; } } }; class AnalyzerPassWarnSameLineStatement: AstVisitor { public: static void process(AnalyzerContext& context) { AnalyzerPassWarnSameLineStatement pass; pass.result = context.result; pass.lastLine = -1; context.root->visit(&pass); } private: ScriptAnalyzer::Result* result; int lastLine; virtual bool visit(AstStatBlock* node) { for (size_t i = 1; i < node->body.size; ++i) { const Location& last = node->body.data[i - 1]->location; const Location& location = node->body.data[i]->location; if (location.begin.line == last.end.line && location.begin.line != lastLine) { emitWarning(*result, ScriptAnalyzer::Warning_SameLineStatement, location, "A new statement is on the same line"); // Warn once per line lastLine = location.begin.line; } } return true; } }; class AnalyzerPassWarnMultiLineStatement: AstVisitor { public: static void process(AnalyzerContext& context) { AnalyzerPassWarnMultiLineStatement pass; pass.result = context.result; context.root->visit(&pass); } private: ScriptAnalyzer::Result* result; struct Statement { Location start; unsigned int lastLine; bool flagged; }; std::vector<Statement> stack; virtual bool visit(AstExpr* node) { Statement& top = stack.back(); if (!top.flagged) { Location location = node->location; if (location.begin.line > top.lastLine) { top.lastLine = location.begin.line; if (location.begin.column <= top.start.begin.column) { emitWarning(*result, ScriptAnalyzer::Warning_MultiLineStatement, location, "Statement spans multiple lines; use indentation to silence"); top.flagged = true; } } } return true; } virtual bool visit(AstExprTable* node) { return false; } virtual bool visit(AstStatRepeat* node) { node->body->visit(this); return false; } virtual bool visit(AstStatBlock* node) { for (size_t i = 0; i < node->body.size; ++i) { AstStat* stmt = node->body.data[i]; Statement s = { stmt->location, stmt->location.begin.line, false }; stack.push_back(s); stmt->visit(this); stack.pop_back(); } return false; } }; class AnalyzerPassWarnDotCall: AstVisitor { public: static void process(AnalyzerContext& context) { AnalyzerPassWarnDotCall pass; pass.result = context.result; context.root->visit(&pass); } private: ScriptAnalyzer::Result* result; virtual bool visit(AstExprCall* node) { if (!node->self) if (AstExprIndexName* index = node->func->as<AstExprIndexName>()) if (!index->expr->is<AstExprLocal>() && !index->expr->is<AstExprGlobal>()) { emitWarning(*result, ScriptAnalyzer::Warning_DotCall, node->func->location, "Expected ':' not '.' calling member function %s", index->index.value); } return true; } }; class AnalyzerPassWarnUnknownType: AstVisitor { public: static void process(AnalyzerContext& context) { AnalyzerPassWarnUnknownType pass; pass.result = context.result; context.root->visit(&pass); } private: ScriptAnalyzer::Result* result; struct ClassDescriptorNamePredicate { bool operator()(const Name& lhs, const Reflection::ClassDescriptor* rhs) const { return lhs < rhs->name; } bool operator()(const Reflection::ClassDescriptor* lhs, const Name& rhs) const { return lhs->name < rhs; } }; void validateType(AstExprConstantString* arg, bool creatable) { const Name& name = Name::lookup(std::string(arg->value.data, arg->value.size)); if (name.empty() || !std::binary_search(Reflection::ClassDescriptor::all_begin(), Reflection::ClassDescriptor::all_end(), name, ClassDescriptorNamePredicate())) emitWarning(*result, ScriptAnalyzer::Warning_UnknownType, arg->location, "Unknown type name '%s'", arg->value.data); else if (creatable && !Creatable<Instance>::getCreator(name)) emitWarning(*result, ScriptAnalyzer::Warning_UnknownType, arg->location, "Type '%s' is not creatable", arg->value.data); } virtual bool visit(AstExprCall* node) { if (AstExprIndexName* index = node->func->as<AstExprIndexName>()) { AstExprConstantString* arg0 = node->args.size > 0 ? node->args.data[0]->as<AstExprConstantString>() : NULL; if (arg0) { if (node->self && index->index == "IsA" && node->args.size == 1) { validateType(arg0, false); } else if (node->self && (index->index == "GetService" || index->index == "FindService") && node->args.size == 1) { AstExprGlobal* g = index->expr->as<AstExprGlobal>(); if (g && (g->name == "game" || g->name == "Game")) { validateType(arg0, false); } } else if (!node->self && index->index == "new" && node->args.size <= 2) { AstExprGlobal* g = index->expr->as<AstExprGlobal>(); if (g && g->name == "Instance") { validateType(arg0, true); } } } } return true; } }; class AnalyzerPassWarnLocalShadow: AstVisitor { public: static void process(AnalyzerContext& context) { AnalyzerPassWarnLocalShadow pass; pass.result = context.result; context.root->visit(&pass); pass.report(); } private: ScriptAnalyzer::Result* result; std::set<AstLocal*> used; void report() { for (std::set<AstLocal*>::iterator it = used.begin(); it != used.end(); ++it) { AstLocal* local = *it; AstLocal* shadow = getUsedShadow(local); if (shadow) emitWarning(*result, ScriptAnalyzer::Warning_LocalShadow, local->location, "Variable '%s' shadows the previous declaration at line %d", local->name.value, shadow->location.begin.line + 1); } } AstLocal* getUsedShadow(AstLocal* local) { while (local->shadow) { if (used.count(local->shadow)) return local->shadow; local = local->shadow; } return NULL; } virtual bool visit(AstExprLocal* node) { used.insert(node->local); return true; } }; class AnalyzerPassDataflow: AstVisitor { public: static void process(AnalyzerContext& context) { AnalyzerPassDataflow pass; pass.context = &context; for (DenseHashMap<AstName, Reflection::Variant>::const_iterator it = context.builtinGlobals.begin(); it != context.builtinGlobals.end(); ++it) { const Reflection::Variant& v = it.getItem().value; shared_ptr<Instance> value; if (v.isType<shared_ptr<Instance> >()) value = v.cast<shared_ptr<Instance> >(); if (value) pass.globals[it->value] = Value(value); else pass.globals[it->value] = Value(Value::Bottom); } context.root->visit(&pass); for (size_t i = 0; i < pass.tables.size(); ++i) { Table& t = pass.tables[i]; if (!t.bottom) { for (std::set<AstExprIndexName*>::iterator it = t.reads.begin(); it != t.reads.end(); ++it) { if (t.keys.count((*it)->index.value) == 0) { emitWarning(*context.result, ScriptAnalyzer::Warning_UnknownMember, (*it)->indexLocation, "Can not find member '%s' in table", (*it)->index.value); } } } } } private: AnalyzerContext* context; struct Value { enum Type { Null, Object, Class, Table, Bottom }; Type type; shared_ptr<Instance> instance; const Reflection::ClassDescriptor* klass; unsigned int table; Value() : type(Null) , klass(NULL) { } explicit Value(Type type) : type(type) , klass(NULL) { } explicit Value(const shared_ptr<Instance>& instance) : type(Object) , instance(instance) , klass(&instance->getDescriptor()) { } explicit Value(const Reflection::ClassDescriptor* klass) : type(Class) , klass(klass) { } explicit Value(unsigned int table) : type(Table) , table(table) { } }; struct Table { std::set<std::string> keys; std::set<AstExprIndexName*> reads; bool bottom; Table() : bottom(false) { } }; std::map<std::string, Value> globals; std::map<AstLocal*, Value> locals; std::map<AstExpr*, Value> evals; std::vector<Table> tables; struct ClassDescriptorNamePredicate { bool operator()(const Name& lhs, const Reflection::ClassDescriptor* rhs) const { return lhs < rhs->name; } bool operator()(const Reflection::ClassDescriptor* lhs, const Name& rhs) const { return lhs->name < rhs; } }; const Reflection::ClassDescriptor* getClassDescriptor(AstExprConstantString* arg) { const Name& name = Name::lookup(std::string(arg->value.data, arg->value.size)); if (name.empty()) return NULL; Reflection::ClassDescriptor::ClassDescriptors::const_iterator it = std::lower_bound(Reflection::ClassDescriptor::all_begin(), Reflection::ClassDescriptor::all_end(), name, ClassDescriptorNamePredicate()); if (it == Reflection::ClassDescriptor::all_end() || (*it)->name != name) return NULL; return *it; } Value eval(AstExpr* node) { std::pair<std::map<AstExpr*, Value>::iterator, bool> p = evals.insert(std::make_pair(node, Value())); if (p.second) p.first->second = evalExpr(node); return p.first->second; } Value evalExpr(AstExpr* node) { if (AstExprGroup* e = node->as<AstExprGroup>()) return eval(e->expr); if (node->is<AstExprConstantNil>()) return Value(); if (AstExprLocal* e = node->as<AstExprLocal>()) return evalElement(locals, e->local); if (AstExprGlobal* e = node->as<AstExprGlobal>()) return evalElement(globals, std::string(e->name.value)); if (AstExprIndexName* e = node->as<AstExprIndexName>()) { Value v = eval(e->expr); if (v.type == Value::Object) return evalLookup(v.instance.get(), e->index.value, e->indexLocation); else if (v.type == Value::Class) return evalLookup(v.klass, e->index.value, e->indexLocation); else if (v.type == Value::Table) { tables[v.table].reads.insert(e); return Value(Value::Bottom); } else return Value(Value::Bottom); } if (AstExprIndexExpr* e = node->as<AstExprIndexExpr>()) { Value v = eval(e->expr); if (v.type == Value::Table) tables[v.table].bottom = true; return Value(Value::Bottom); } if (AstExprCall* e = node->as<AstExprCall>()) { if (AstExprIndexName* index = e->func->as<AstExprIndexName>()) { AstExprConstantString* arg = e->args.size >= 1 ? e->args.data[0]->as<AstExprConstantString>() : NULL; if (arg && !e->self && index->index == "new") { AstExprGlobal* g = index->expr->as<AstExprGlobal>(); if (g && g->name == "Instance") { const Reflection::ClassDescriptor* klass = getClassDescriptor(arg); if (klass) return Value(klass); else return Value(Value::Bottom); } } } return Value(Value::Bottom); } if (AstExprTable* e = node->as<AstExprTable>()) { unsigned int table = tables.size(); tables.push_back(Table()); for (size_t i = 0; i < e->pairs.size; i += 2) { AstExpr* key = e->pairs.data[i]; AstExprConstantString* kv = key ? key->as<AstExprConstantString>() : NULL; if (kv) tables[table].keys.insert(std::string(kv->value.data, kv->value.size)); } return Value(table); } return Value(Value::Bottom); } template <typename Key> Value evalElement(const std::map<Key, Value>& map, const Key& key) { typename std::map<Key, Value>::const_iterator it = map.find(key); return it == map.end() ? Value(Value::Bottom) : it->second; } Value evalLookup(const Reflection::ClassDescriptor* klass, const char* name, const Location& location) { if (klass->findPropertyDescriptor(name)) return Value(Value::Bottom); if (klass->findYieldFunctionDescriptor(name)) return Value(Value::Bottom); if (klass->findFunctionDescriptor(name)) return Value(Value::Bottom); if (klass->findEventDescriptor(name)) return Value(Value::Bottom); if (klass->findCallbackDescriptor(name)) return Value(Value::Bottom); emitWarning(*context->result, ScriptAnalyzer::Warning_UnknownMember, location, "Can not find member '%s' in type '%s'", name, klass->name.c_str()); return Value(Value::Bottom); } Value evalLookup(Instance* object, const char* name, const Location& location) { if (Reflection::PropertyDescriptor* prop = object->findPropertyDescriptor(name)) { try { Reflection::Variant value; prop->getVariant(object, value); shared_ptr<Instance> instance; if (value.isType<shared_ptr<Instance> >()) instance = value.cast<shared_ptr<Instance> >(); else if (value.isType<shared_ptr<Reflection::DescribedBase> >()) instance = shared_dynamic_cast<Instance>(value.cast<shared_ptr<Reflection::DescribedBase> >()); return instance ? Value(instance) : Value(Value::Bottom); } catch (std::exception& e) { emitWarning(*context->result, ScriptAnalyzer::Warning_UnknownMember, location, "Can not get member '%s': %s", name, e.what()); return Value(Value::Bottom); } } if (object->findYieldFunctionDescriptor(name)) return Value(Value::Bottom); if (object->findFunctionDescriptor(name)) return Value(Value::Bottom); if (object->findSignalDescriptor(name)) return Value(Value::Bottom); if (object->findCallbackDescriptor(name)) return Value(Value::Bottom); if (Instance* child = object->findFirstChildByName(name)) return Value(shared_from(child)); emitWarning(*context->result, ScriptAnalyzer::Warning_UnknownMember, location, "Can not find member '%s'", name); return Value(Value::Bottom); } virtual bool visit(AstExpr* node) { eval(node); return true; } virtual bool visit(AstStatLocal* node) { if (node->vars.size == 1 && node->values.size == 1) { locals[node->vars.data[0]] = eval(node->values.data[0]); node->values.data[0]->visit(this); return false; } return true; } virtual bool visit(AstStatAssign* node) { if (node->vars.size == 1 && node->values.size == 1) { AstExpr* e = node->vars.data[0]; if (AstExprLocal* l = e->as<AstExprLocal>()) { if (locals.count(l->local) == 0) locals[l->local] = eval(node->values.data[0]); else locals[l->local] = Value(Value::Bottom); } } for (size_t i = 0; i < node->vars.size; ++i) { AstExpr* e = node->vars.data[i]; if (AstExprIndexName* index = e->as<AstExprIndexName>()) { Value v = eval(index->expr); if (v.type == Value::Table) { tables[v.table].keys.insert(index->index.value); if (node->vars.size == 1 && node->values.size == 1) { AstExprFunction* f = node->values.data[0]->as<AstExprFunction>(); if (f && f->self) { locals[f->self] = v; } } } } } return true; } }; struct WarningComparator { int compare(const ScriptAnalyzer::Position& lhs, const ScriptAnalyzer::Position& rhs) const { if (lhs.line != rhs.line) return lhs.line < rhs.line ? -1 : 1; if (lhs.column != rhs.column) return lhs.column < rhs.column ? -1 : 1; return 0; } int compare(const ScriptAnalyzer::Location& lhs, const ScriptAnalyzer::Location& rhs) const { if (int c = compare(lhs.begin, rhs.begin)) return c; if (int c = compare(lhs.end, rhs.end)) return c; return 0; } bool operator()(const ScriptAnalyzer::Warning& lhs, const ScriptAnalyzer::Warning& rhs) const { if (int c = compare(lhs.location, rhs.location)) return c < 0; return lhs.code < rhs.code; } }; } static Lua::ThreadRef createScriptThread(ScriptContext* sc, shared_ptr<Instance> script) { Security::Identities identity = RBX::Security::GameScript_; lua_State* globalState = sc->getGlobalState(identity); RBXASSERT_BALLANCED_LUA_STACK(globalState); Lua::ThreadRef thread = lua_newthread(globalState); // Pop the thread from the stack when we leave Lua::ScopedPopper popper(globalState, 1); { RBXASSERT_BALLANCED_LUA_STACK(thread); RBXASSERT(lua_gettop(thread)==0); // declare "script" global Lua::ObjectBridge::push(thread, script); lua_setglobal( thread, "script" ); // balance the thread's stack lua_settop(thread, 0); } return thread; } static void logTiming(const shared_ptr<Instance>& script, const Timer<Time::Precise>& timer, const char* name) { if (FFlag::DebugScriptAnalyzer) { double time = timer.delta().msec(); if (time > 0.05) StandardOut::singleton()->printf(MESSAGE_OUTPUT, "%s: %s took %.1f msec", script->getFullName().c_str(), name, time); } } ScriptAnalyzer::Result ScriptAnalyzer::analyze(DataModel* dm, shared_ptr<Instance> script, const std::string& code) { using namespace ScriptParser; try { if (!dm) return ScriptAnalyzer::Result(); RBXASSERT(dm->currentThreadHasWriteLock()); ScriptContext* sc = ServiceProvider::create<ScriptContext>(dm); Lua::ThreadRef L = createScriptThread(sc, script); Result result; try { Timer<Time::Precise> parseTimer; ScriptParser::Allocator a; AstNameTable names(a); AstStat* root = Parser::parse(code.c_str(), code.size(), names, a); logTiming(script, parseTimer, format("Parsing %d Kb", static_cast<int>(code.size() / 1024)).c_str()); AnalyzerContext context(&result, root); context.fillNames(names); context.fillBuiltinGlobals(names, L); context.fillDeprecatedGlobals(names); std::vector<AnalyzerPass> passes; #define PASS(name) do { AnalyzerPass p = { #name, name::process }; passes.push_back(p); } while (0) PASS(AnalyzerPassWarnGlobalLocal); PASS(AnalyzerPassWarnMultiLineStatement); PASS(AnalyzerPassWarnUnknownType); if (FFlag::StudioVariableIntellesense) PASS(AnalyzerPassIntellesenseLocal); // PASS(AnalyzerPassWarnLocalShadow); // PASS(AnalyzerPassWarnDotCall); // PASS(AnalyzerPassDataflow); #undef PASS for (size_t i = 0; i < (FFlag::StudioVariableIntellesense ? passes.size() : sizeof(passes) / sizeof(passes[0])); ++i) { Timer<Time::Precise> passTimer; passes[i].process(context); logTiming(script, passTimer, passes[i].name); } } catch (const ScriptParser::Error& e) { Error error; error.location = e.getLocation(); error.text = e.what(); result.error = error; } std::sort(result.warnings.begin(), result.warnings.end(), WarningComparator()); return result; } catch (RBX::base_exception& e) { StandardOut::singleton()->printf(MESSAGE_ERROR, "ScriptAnalyzer: unexpected error %s while parsing %s", e.what(), script ? script->getFullName().c_str() : "unknown script"); return Result(); } } }