summaryrefslogtreecommitdiff
path: root/deps/v8/tools/gcmole
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2011-07-08 16:40:11 -0700
committerRyan Dahl <ry@tinyclouds.org>2011-07-08 16:40:11 -0700
commite5564a3f29e0a818832a97c7c3b28d7c8b3b0460 (patch)
tree4b48a6577080d5e44da4d2cbebb7fe7951660de8 /deps/v8/tools/gcmole
parent0df2f74d364826053641395b01c2fcb1345057a9 (diff)
downloadandroid-node-v8-e5564a3f29e0a818832a97c7c3b28d7c8b3b0460.tar.gz
android-node-v8-e5564a3f29e0a818832a97c7c3b28d7c8b3b0460.tar.bz2
android-node-v8-e5564a3f29e0a818832a97c7c3b28d7c8b3b0460.zip
Upgrade V8 to 3.4.10
Diffstat (limited to 'deps/v8/tools/gcmole')
-rw-r--r--deps/v8/tools/gcmole/README62
-rw-r--r--deps/v8/tools/gcmole/gccause.lua60
-rw-r--r--deps/v8/tools/gcmole/gcmole.cc1261
-rw-r--r--deps/v8/tools/gcmole/gcmole.lua378
4 files changed, 1761 insertions, 0 deletions
diff --git a/deps/v8/tools/gcmole/README b/deps/v8/tools/gcmole/README
new file mode 100644
index 0000000000..02cf88ccc1
--- /dev/null
+++ b/deps/v8/tools/gcmole/README
@@ -0,0 +1,62 @@
+DESCRIPTION -------------------------------------------------------------------
+
+gcmole is a simple static analysis tool used to find possible evaluation order
+dependent GC-unsafe places in the V8 codebase.
+
+For example the following code is GC-unsafe:
+
+Handle<Object> Foo(); // Assume Foo can trigger a GC.
+void Bar(Object*, Object*);
+
+Handle<Object> baz;
+baz->Qux(*Foo()); // (a)
+Bar(*Foo(), *baz); // (b)
+
+Both in cases (a) and (b) compiler is free to evaluate call arguments (that
+includes receiver) in any order. That means it can dereference baz before
+calling to Foo and save a raw pointer to a heap object in the register or
+on the stack.
+
+PREREQUISITES -----------------------------------------------------------------
+
+1) Install Lua 5.1
+
+2) Get LLVM and Clang sources and build them.
+
+Follow the instructions on http://clang.llvm.org/get_started.html.
+
+Make sure to pass --enable-optimized to configure to get Release build
+instead of a Debug one.
+
+3) Build gcmole Clang plugin (libgcmole.so)
+
+In the tools/gcmole execute the following command:
+
+LLVM_SRC_ROOT=<path-to-llvm-source-root> make
+
+USING GCMOLE ------------------------------------------------------------------
+
+gcmole consists of driver script written in Lua and Clang plugin that does
+C++ AST processing. Plugin (libgcmole.so) is expected to be in the same
+folder as driver (gcmole.lua).
+
+To start analysis cd into the root of v8 checkout and execute the following
+command:
+
+CLANG_BIN=<path-to-clang-bin-folder> lua tools/gcmole/gcmole.lua [<arch>]
+
+where arch should be one of architectures supported by V8 (arm, ia32, x64).
+
+Analysis will be performed in 2 stages:
+
+- on the first stage driver will parse all files and build a global callgraph
+approximation to find all functions that might potentially cause GC, list
+of this functions will be written into gcsuspects file.
+
+- on the second stage driver will parse all files again and will locate all
+callsites that might be GC-unsafe based on the list of functions causing GC.
+Such places are marked with a "Possible problem with evaluation order."
+warning. Messages "Failed to resolve v8::internal::Object" are benign and
+can be ignored.
+
+If any errors were found driver exits with non-zero status.
diff --git a/deps/v8/tools/gcmole/gccause.lua b/deps/v8/tools/gcmole/gccause.lua
new file mode 100644
index 0000000000..a6fe542137
--- /dev/null
+++ b/deps/v8/tools/gcmole/gccause.lua
@@ -0,0 +1,60 @@
+-- Copyright 2011 the V8 project authors. All rights reserved.
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions are
+-- met:
+--
+-- * Redistributions of source code must retain the above copyright
+-- notice, this list of conditions and the following disclaimer.
+-- * Redistributions in binary form must reproduce the above
+-- copyright notice, this list of conditions and the following
+-- disclaimer in the documentation and/or other materials provided
+-- with the distribution.
+-- * Neither the name of Google Inc. nor the names of its
+-- contributors may be used to endorse or promote products derived
+-- from this software without specific prior written permission.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-- This is an auxiliary tool that reads gccauses file generated by
+-- gcmole.lua and prints tree of the calls that can potentially cause a GC
+-- inside a given function.
+--
+-- Usage: lua tools/gcmole/gccause.lua <function-name-pattern>
+--
+
+assert(loadfile "gccauses")()
+
+local P = ...
+
+local T = {}
+
+local function TrackCause(name, lvl)
+ io.write((" "):rep(lvl or 0), name, "\n")
+ if GC[name] then
+ local causes = GC[name]
+ for i = 1, #causes do
+ local f = causes[i]
+ if not T[f] then
+ T[f] = true
+ TrackCause(f, (lvl or 0) + 1)
+ end
+ end
+ end
+end
+
+for name, _ in pairs(GC) do
+ if name:match(P) then
+ T = {}
+ TrackCause(name)
+ end
+end
diff --git a/deps/v8/tools/gcmole/gcmole.cc b/deps/v8/tools/gcmole/gcmole.cc
new file mode 100644
index 0000000000..71ba24a33b
--- /dev/null
+++ b/deps/v8/tools/gcmole/gcmole.cc
@@ -0,0 +1,1261 @@
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This is clang plugin used by gcmole tool. See README for more details.
+
+#include "clang/AST/AST.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/Mangle.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/Frontend/FrontendPluginRegistry.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <bitset>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <set>
+#include <stack>
+
+namespace {
+
+typedef std::string MangledName;
+typedef std::set<MangledName> CalleesSet;
+
+static bool GetMangledName(clang::MangleContext* ctx,
+ const clang::NamedDecl* decl,
+ MangledName* result) {
+ if (!isa<clang::CXXConstructorDecl>(decl) &&
+ !isa<clang::CXXDestructorDecl>(decl)) {
+ llvm::SmallVector<char, 512> output;
+ llvm::raw_svector_ostream out(output);
+ ctx->mangleName(decl, out);
+ *result = out.str().str();
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool InV8Namespace(const clang::NamedDecl* decl) {
+ return decl->getQualifiedNameAsString().compare(0, 4, "v8::") == 0;
+}
+
+
+struct Resolver {
+ explicit Resolver(clang::ASTContext& ctx)
+ : ctx_(ctx), decl_ctx_(ctx.getTranslationUnitDecl()) {
+ }
+
+ Resolver(clang::ASTContext& ctx, clang::DeclContext* decl_ctx)
+ : ctx_(ctx), decl_ctx_(decl_ctx) {
+ }
+
+ clang::DeclarationName ResolveName(const char* n) {
+ clang::IdentifierInfo* ident = &ctx_.Idents.get(n);
+ return ctx_.DeclarationNames.getIdentifier(ident);
+ }
+
+ Resolver ResolveNamespace(const char* n) {
+ return Resolver(ctx_, Resolve<clang::NamespaceDecl>(n));
+ }
+
+ template<typename T>
+ T* Resolve(const char* n) {
+ if (decl_ctx_ == NULL) return NULL;
+
+ clang::DeclContext::lookup_result result =
+ decl_ctx_->lookup(ResolveName(n));
+
+ clang::DeclContext::lookup_iterator end = result.second;
+ for (clang::DeclContext::lookup_iterator i = result.first;
+ i != end;
+ i++) {
+ if (isa<T>(*i)) return cast<T>(*i);
+ }
+
+ return NULL;
+ }
+
+ private:
+ clang::ASTContext& ctx_;
+ clang::DeclContext* decl_ctx_;
+};
+
+
+class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
+ public:
+ explicit CalleesPrinter(clang::MangleContext* ctx) : ctx_(ctx) {
+ }
+
+ virtual bool VisitCallExpr(clang::CallExpr* expr) {
+ const clang::FunctionDecl* callee = expr->getDirectCallee();
+ if (callee != NULL) AnalyzeFunction(callee);
+ return true;
+ }
+
+ void AnalyzeFunction(const clang::FunctionDecl* f) {
+ MangledName name;
+ if (InV8Namespace(f) && GetMangledName(ctx_, f, &name)) {
+ AddCallee(name);
+
+ const clang::FunctionDecl* body = NULL;
+ if (f->hasBody(body) && !Analyzed(name)) {
+ EnterScope(name);
+ TraverseStmt(body->getBody());
+ LeaveScope();
+ }
+ }
+ }
+
+ typedef std::map<MangledName, CalleesSet* > Callgraph;
+
+ bool Analyzed(const MangledName& name) {
+ return callgraph_[name] != NULL;
+ }
+
+ void EnterScope(const MangledName& name) {
+ CalleesSet* callees = callgraph_[name];
+
+ if (callees == NULL) {
+ callgraph_[name] = callees = new CalleesSet();
+ }
+
+ scopes_.push(callees);
+ }
+
+ void LeaveScope() {
+ scopes_.pop();
+ }
+
+ void AddCallee(const MangledName& name) {
+ if (!scopes_.empty()) scopes_.top()->insert(name);
+ }
+
+ void PrintCallGraph() {
+ for (Callgraph::const_iterator i = callgraph_.begin(), e = callgraph_.end();
+ i != e;
+ ++i) {
+ std::cout << i->first << "\n";
+
+ CalleesSet* callees = i->second;
+ for (CalleesSet::const_iterator j = callees->begin(), e = callees->end();
+ j != e;
+ ++j) {
+ std::cout << "\t" << *j << "\n";
+ }
+ }
+ }
+
+ private:
+ clang::MangleContext* ctx_;
+
+ std::stack<CalleesSet* > scopes_;
+ Callgraph callgraph_;
+};
+
+
+class FunctionDeclarationFinder
+ : public clang::ASTConsumer,
+ public clang::RecursiveASTVisitor<FunctionDeclarationFinder> {
+ public:
+ explicit FunctionDeclarationFinder(clang::Diagnostic& d,
+ clang::SourceManager& sm,
+ const std::vector<std::string>& args)
+ : d_(d), sm_(sm) { }
+
+ virtual void HandleTranslationUnit(clang::ASTContext &ctx) {
+ mangle_context_ = clang::createItaniumMangleContext(ctx, d_);
+ callees_printer_ = new CalleesPrinter(mangle_context_);
+
+ TraverseDecl(ctx.getTranslationUnitDecl());
+
+ callees_printer_->PrintCallGraph();
+ }
+
+ virtual bool VisitFunctionDecl(clang::FunctionDecl* decl) {
+ callees_printer_->AnalyzeFunction(decl);
+ return true;
+ }
+
+ private:
+ clang::Diagnostic& d_;
+ clang::SourceManager& sm_;
+ clang::MangleContext* mangle_context_;
+
+ CalleesPrinter* callees_printer_;
+};
+
+
+static bool loaded = false;
+static CalleesSet gc_suspects;
+
+
+static void LoadGCSuspects() {
+ if (loaded) return;
+
+ std::ifstream fin("gcsuspects");
+ std::string s;
+
+ while (fin >> s) gc_suspects.insert(s);
+
+ loaded = true;
+}
+
+
+static bool KnownToCauseGC(clang::MangleContext* ctx,
+ const clang::FunctionDecl* decl) {
+ LoadGCSuspects();
+
+ if (!InV8Namespace(decl)) return false;
+
+ MangledName name;
+ if (GetMangledName(ctx, decl, &name)) {
+ return gc_suspects.find(name) != gc_suspects.end();
+ }
+
+ return false;
+}
+
+
+static const int kNoEffect = 0;
+static const int kCausesGC = 1;
+static const int kRawDef = 2;
+static const int kRawUse = 4;
+static const int kAllEffects = kCausesGC | kRawDef | kRawUse;
+
+class Environment;
+
+class ExprEffect {
+ public:
+ bool hasGC() { return (effect_ & kCausesGC) != 0; }
+ void setGC() { effect_ |= kCausesGC; }
+
+ bool hasRawDef() { return (effect_ & kRawDef) != 0; }
+ void setRawDef() { effect_ |= kRawDef; }
+
+ bool hasRawUse() { return (effect_ & kRawUse) != 0; }
+ void setRawUse() { effect_ |= kRawUse; }
+
+ static ExprEffect None() { return ExprEffect(kNoEffect, NULL); }
+ static ExprEffect NoneWithEnv(Environment* env) {
+ return ExprEffect(kNoEffect, env);
+ }
+ static ExprEffect RawUse() { return ExprEffect(kRawUse, NULL); }
+
+ static ExprEffect Merge(ExprEffect a, ExprEffect b);
+ static ExprEffect MergeSeq(ExprEffect a, ExprEffect b);
+ ExprEffect Define(const std::string& name);
+
+ Environment* env() {
+ return reinterpret_cast<Environment*>(effect_ & ~kAllEffects);
+ }
+
+ private:
+ ExprEffect(int effect, Environment* env)
+ : effect_((effect & kAllEffects) |
+ reinterpret_cast<intptr_t>(env)) { }
+
+ intptr_t effect_;
+};
+
+
+const std::string BAD_EXPR_MSG("Possible problem with evaluation order.");
+const std::string DEAD_VAR_MSG("Possibly dead variable.");
+
+
+class Environment {
+ public:
+ Environment() { }
+
+ static Environment Unreachable() {
+ Environment env;
+ env.live_.set();
+ return env;
+ }
+
+ static Environment Merge(const Environment& l,
+ const Environment& r) {
+ return Environment(l, r);
+ }
+
+ Environment ApplyEffect(ExprEffect effect) const {
+ Environment out = effect.hasGC() ? Environment() : Environment(*this);
+ if (effect.env() != NULL) out.live_ |= effect.env()->live_;
+ return out;
+ }
+
+ typedef std::map<std::string, int> SymbolTable;
+
+ bool IsAlive(const std::string& name) const {
+ SymbolTable::iterator code = symbol_table_.find(name);
+ if (code == symbol_table_.end()) return false;
+ return live_[code->second];
+ }
+
+ bool Equal(const Environment& env) {
+ return live_ == env.live_;
+ }
+
+ Environment Define(const std::string& name) const {
+ return Environment(*this, SymbolToCode(name));
+ }
+
+ void MDefine(const std::string& name) {
+ live_.set(SymbolToCode(name));
+ }
+
+ static int SymbolToCode(const std::string& name) {
+ SymbolTable::iterator code = symbol_table_.find(name);
+
+ if (code == symbol_table_.end()) {
+ int new_code = symbol_table_.size();
+ symbol_table_.insert(std::make_pair(name, new_code));
+ return new_code;
+ }
+
+ return code->second;
+ }
+
+ static void ClearSymbolTable() {
+ std::vector<Environment*>::iterator end = envs_.end();
+ for (std::vector<Environment*>::iterator i = envs_.begin();
+ i != end;
+ ++i) {
+ delete *i;
+ }
+ envs_.clear();
+ symbol_table_.clear();
+ }
+
+ void Print() const {
+ bool comma = false;
+ std::cout << "{";
+ SymbolTable::iterator end = symbol_table_.end();
+ for (SymbolTable::iterator i = symbol_table_.begin();
+ i != end;
+ ++i) {
+ if (live_[i->second]) {
+ if (comma) std::cout << ", ";
+ std::cout << i->first;
+ comma = true;
+ }
+ }
+ std::cout << "}";
+ }
+
+ static Environment* Allocate(const Environment& env) {
+ Environment* allocated_env = new Environment(env);
+ envs_.push_back(allocated_env);
+ return allocated_env;
+ }
+
+ private:
+ Environment(const Environment& l, const Environment& r)
+ : live_(l.live_ & r.live_) {
+ }
+
+ Environment(const Environment& l, int code)
+ : live_(l.live_) {
+ live_.set(code);
+ }
+
+ static SymbolTable symbol_table_;
+ static std::vector<Environment* > envs_;
+
+ static const int kMaxNumberOfLocals = 256;
+ std::bitset<kMaxNumberOfLocals> live_;
+
+ friend class ExprEffect;
+ friend class CallProps;
+};
+
+
+class CallProps {
+ public:
+ CallProps() : env_(NULL) { }
+
+ void SetEffect(int arg, ExprEffect in) {
+ if (in.hasGC()) gc_.set(arg);
+ if (in.hasRawDef()) raw_def_.set(arg);
+ if (in.hasRawUse()) raw_use_.set(arg);
+ if (in.env() != NULL) {
+ if (env_ == NULL) env_ = in.env();
+ env_->live_ |= in.env()->live_;
+ }
+ }
+
+ ExprEffect ComputeCumulativeEffect(bool result_is_raw) {
+ ExprEffect out = ExprEffect::NoneWithEnv(env_);
+ if (gc_.any()) out.setGC();
+ if (raw_use_.any()) out.setRawUse();
+ if (result_is_raw) out.setRawDef();
+ return out;
+ }
+
+ bool IsSafe() {
+ if (!gc_.any()) return true;
+ std::bitset<kMaxNumberOfArguments> raw = (raw_def_ | raw_use_);
+ if (!raw.any()) return true;
+ return gc_.count() == 1 && !((raw ^ gc_).any());
+ }
+
+ private:
+ static const int kMaxNumberOfArguments = 64;
+ std::bitset<kMaxNumberOfArguments> raw_def_;
+ std::bitset<kMaxNumberOfArguments> raw_use_;
+ std::bitset<kMaxNumberOfArguments> gc_;
+ Environment* env_;
+};
+
+
+Environment::SymbolTable Environment::symbol_table_;
+std::vector<Environment* > Environment::envs_;
+
+
+ExprEffect ExprEffect::Merge(ExprEffect a, ExprEffect b) {
+ Environment* a_env = a.env();
+ Environment* b_env = b.env();
+ Environment* out = NULL;
+ if (a_env != NULL && b_env != NULL) {
+ out = Environment::Allocate(*a_env);
+ out->live_ &= b_env->live_;
+ }
+ return ExprEffect(a.effect_ | b.effect_, out);
+}
+
+
+ExprEffect ExprEffect::MergeSeq(ExprEffect a, ExprEffect b) {
+ Environment* a_env = b.hasGC() ? NULL : a.env();
+ Environment* b_env = b.env();
+ Environment* out = (b_env == NULL) ? a_env : b_env;
+ if (a_env != NULL && b_env != NULL) {
+ out = Environment::Allocate(*b_env);
+ out->live_ |= a_env->live_;
+ }
+ return ExprEffect(a.effect_ | b.effect_, out);
+}
+
+
+ExprEffect ExprEffect::Define(const std::string& name) {
+ Environment* e = env();
+ if (e == NULL) {
+ e = Environment::Allocate(Environment());
+ }
+ e->MDefine(name);
+ return ExprEffect(effect_, e);
+}
+
+
+static std::string THIS ("this");
+
+
+class FunctionAnalyzer {
+ public:
+ FunctionAnalyzer(clang::MangleContext* ctx,
+ clang::DeclarationName handle_decl_name,
+ clang::CXXRecordDecl* object_decl,
+ clang::CXXRecordDecl* smi_decl,
+ clang::Diagnostic& d,
+ clang::SourceManager& sm,
+ bool dead_vars_analysis)
+ : ctx_(ctx),
+ handle_decl_name_(handle_decl_name),
+ object_decl_(object_decl),
+ smi_decl_(smi_decl),
+ d_(d),
+ sm_(sm),
+ block_(NULL),
+ dead_vars_analysis_(dead_vars_analysis) {
+ }
+
+
+ // --------------------------------------------------------------------------
+ // Expressions
+ // --------------------------------------------------------------------------
+
+ ExprEffect VisitExpr(clang::Expr* expr, const Environment& env) {
+#define VISIT(type) do { \
+ clang::type* concrete_expr = dyn_cast_or_null<clang::type>(expr); \
+ if (concrete_expr != NULL) { \
+ return Visit##type (concrete_expr, env); \
+ } \
+ } while(0);
+
+ VISIT(AbstractConditionalOperator);
+ VISIT(AddrLabelExpr);
+ VISIT(ArraySubscriptExpr);
+ VISIT(BinaryOperator);
+ VISIT(BinaryTypeTraitExpr);
+ VISIT(BlockDeclRefExpr);
+ VISIT(BlockExpr);
+ VISIT(CallExpr);
+ VISIT(CastExpr);
+ VISIT(CharacterLiteral);
+ VISIT(ChooseExpr);
+ VISIT(CompoundLiteralExpr);
+ VISIT(CXXBindTemporaryExpr);
+ VISIT(CXXBoolLiteralExpr);
+ VISIT(CXXConstructExpr);
+ VISIT(CXXDefaultArgExpr);
+ VISIT(CXXDeleteExpr);
+ VISIT(CXXDependentScopeMemberExpr);
+ VISIT(CXXNewExpr);
+ VISIT(CXXNoexceptExpr);
+ VISIT(CXXNullPtrLiteralExpr);
+ VISIT(CXXPseudoDestructorExpr);
+ VISIT(CXXScalarValueInitExpr);
+ VISIT(CXXThisExpr);
+ VISIT(CXXThrowExpr);
+ VISIT(CXXTypeidExpr);
+ VISIT(CXXUnresolvedConstructExpr);
+ VISIT(CXXUuidofExpr);
+ VISIT(DeclRefExpr);
+ VISIT(DependentScopeDeclRefExpr);
+ VISIT(DesignatedInitExpr);
+ VISIT(ExprWithCleanups);
+ VISIT(ExtVectorElementExpr);
+ VISIT(FloatingLiteral);
+ VISIT(GNUNullExpr);
+ VISIT(ImaginaryLiteral);
+ VISIT(ImplicitValueInitExpr);
+ VISIT(InitListExpr);
+ VISIT(IntegerLiteral);
+ VISIT(MemberExpr);
+ VISIT(OffsetOfExpr);
+ VISIT(OpaqueValueExpr);
+ VISIT(OverloadExpr);
+ VISIT(PackExpansionExpr);
+ VISIT(ParenExpr);
+ VISIT(ParenListExpr);
+ VISIT(PredefinedExpr);
+ VISIT(ShuffleVectorExpr);
+ VISIT(SizeOfPackExpr);
+ VISIT(StmtExpr);
+ VISIT(StringLiteral);
+ VISIT(SubstNonTypeTemplateParmPackExpr);
+ VISIT(UnaryExprOrTypeTraitExpr);
+ VISIT(UnaryOperator);
+ VISIT(UnaryTypeTraitExpr);
+ VISIT(VAArgExpr);
+#undef VISIT
+
+ return ExprEffect::None();
+ }
+
+#define DECL_VISIT_EXPR(type) \
+ ExprEffect Visit##type (clang::type* expr, const Environment& env)
+
+#define IGNORE_EXPR(type) \
+ ExprEffect Visit##type (clang::type* expr, const Environment& env) { \
+ return ExprEffect::None(); \
+ }
+
+ IGNORE_EXPR(AddrLabelExpr);
+ IGNORE_EXPR(BinaryTypeTraitExpr);
+ IGNORE_EXPR(BlockExpr);
+ IGNORE_EXPR(CharacterLiteral);
+ IGNORE_EXPR(ChooseExpr);
+ IGNORE_EXPR(CompoundLiteralExpr);
+ IGNORE_EXPR(CXXBoolLiteralExpr);
+ IGNORE_EXPR(CXXDependentScopeMemberExpr);
+ IGNORE_EXPR(CXXNullPtrLiteralExpr);
+ IGNORE_EXPR(CXXPseudoDestructorExpr);
+ IGNORE_EXPR(CXXScalarValueInitExpr);
+ IGNORE_EXPR(CXXNoexceptExpr);
+ IGNORE_EXPR(CXXTypeidExpr);
+ IGNORE_EXPR(CXXUnresolvedConstructExpr);
+ IGNORE_EXPR(CXXUuidofExpr);
+ IGNORE_EXPR(DependentScopeDeclRefExpr);
+ IGNORE_EXPR(DesignatedInitExpr);
+ IGNORE_EXPR(ExtVectorElementExpr);
+ IGNORE_EXPR(FloatingLiteral);
+ IGNORE_EXPR(ImaginaryLiteral);
+ IGNORE_EXPR(IntegerLiteral);
+ IGNORE_EXPR(OffsetOfExpr);
+ IGNORE_EXPR(ImplicitValueInitExpr);
+ IGNORE_EXPR(PackExpansionExpr);
+ IGNORE_EXPR(PredefinedExpr);
+ IGNORE_EXPR(ShuffleVectorExpr);
+ IGNORE_EXPR(SizeOfPackExpr);
+ IGNORE_EXPR(StmtExpr);
+ IGNORE_EXPR(StringLiteral);
+ IGNORE_EXPR(SubstNonTypeTemplateParmPackExpr);
+ IGNORE_EXPR(UnaryExprOrTypeTraitExpr);
+ IGNORE_EXPR(UnaryTypeTraitExpr);
+ IGNORE_EXPR(VAArgExpr);
+ IGNORE_EXPR(GNUNullExpr);
+ IGNORE_EXPR(OverloadExpr);
+
+ DECL_VISIT_EXPR(CXXThisExpr) {
+ return Use(expr, expr->getType(), THIS, env);
+ }
+
+ DECL_VISIT_EXPR(AbstractConditionalOperator) {
+ Environment after_cond = env.ApplyEffect(VisitExpr(expr->getCond(), env));
+ return ExprEffect::Merge(VisitExpr(expr->getTrueExpr(), after_cond),
+ VisitExpr(expr->getFalseExpr(), after_cond));
+ }
+
+ DECL_VISIT_EXPR(ArraySubscriptExpr) {
+ clang::Expr* exprs[2] = {expr->getBase(), expr->getIdx()};
+ return Par(expr, 2, exprs, env);
+ }
+
+ bool IsRawPointerVar(clang::Expr* expr, std::string* var_name) {
+ if (isa<clang::BlockDeclRefExpr>(expr)) {
+ *var_name = cast<clang::BlockDeclRefExpr>(expr)->getDecl()->
+ getNameAsString();
+ return true;
+ } else if (isa<clang::DeclRefExpr>(expr)) {
+ *var_name = cast<clang::DeclRefExpr>(expr)->getDecl()->getNameAsString();
+ return true;
+ }
+ return false;
+ }
+
+ DECL_VISIT_EXPR(BinaryOperator) {
+ clang::Expr* lhs = expr->getLHS();
+ clang::Expr* rhs = expr->getRHS();
+ clang::Expr* exprs[2] = {lhs, rhs};
+
+ switch (expr->getOpcode()) {
+ case clang::BO_Comma:
+ return Seq(expr, 2, exprs, env);
+
+ case clang::BO_LAnd:
+ case clang::BO_LOr:
+ return ExprEffect::Merge(VisitExpr(lhs, env), VisitExpr(rhs, env));
+
+ case clang::BO_Assign: {
+ std::string var_name;
+ if (IsRawPointerVar(lhs, &var_name)) {
+ return VisitExpr(rhs, env).Define(var_name);
+ }
+ return Par(expr, 2, exprs, env);
+ }
+
+ default:
+ return Par(expr, 2, exprs, env);
+ }
+ }
+
+ DECL_VISIT_EXPR(CXXBindTemporaryExpr) {
+ return VisitExpr(expr->getSubExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(CXXConstructExpr) {
+ return VisitArguments<>(expr, env);
+ }
+
+ DECL_VISIT_EXPR(CXXDefaultArgExpr) {
+ return VisitExpr(expr->getExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(CXXDeleteExpr) {
+ return VisitExpr(expr->getArgument(), env);
+ }
+
+ DECL_VISIT_EXPR(CXXNewExpr) {
+ return Par(expr,
+ expr->getNumConstructorArgs(),
+ expr->getConstructorArgs(),
+ env);
+ }
+
+ DECL_VISIT_EXPR(ExprWithCleanups) {
+ return VisitExpr(expr->getSubExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(CXXThrowExpr) {
+ return VisitExpr(expr->getSubExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(InitListExpr) {
+ return Seq(expr, expr->getNumInits(), expr->getInits(), env);
+ }
+
+ DECL_VISIT_EXPR(MemberExpr) {
+ return VisitExpr(expr->getBase(), env);
+ }
+
+ DECL_VISIT_EXPR(OpaqueValueExpr) {
+ return VisitExpr(expr->getSourceExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(ParenExpr) {
+ return VisitExpr(expr->getSubExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(ParenListExpr) {
+ return Par(expr, expr->getNumExprs(), expr->getExprs(), env);
+ }
+
+ DECL_VISIT_EXPR(UnaryOperator) {
+ // TODO We are treating all expressions that look like &raw_pointer_var
+ // as definitions of raw_pointer_var. This should be changed to
+ // recognize less generic pattern:
+ //
+ // if (maybe_object->ToObject(&obj)) return maybe_object;
+ //
+ if (expr->getOpcode() == clang::UO_AddrOf) {
+ std::string var_name;
+ if (IsRawPointerVar(expr->getSubExpr(), &var_name)) {
+ return ExprEffect::None().Define(var_name);
+ }
+ }
+ return VisitExpr(expr->getSubExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(CastExpr) {
+ return VisitExpr(expr->getSubExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(DeclRefExpr) {
+ return Use(expr, expr->getDecl(), env);
+ }
+
+ DECL_VISIT_EXPR(BlockDeclRefExpr) {
+ return Use(expr, expr->getDecl(), env);
+ }
+
+ ExprEffect Par(clang::Expr* parent,
+ int n,
+ clang::Expr** exprs,
+ const Environment& env) {
+ CallProps props;
+
+ for (int i = 0; i < n; ++i) {
+ props.SetEffect(i, VisitExpr(exprs[i], env));
+ }
+
+ if (!props.IsSafe()) ReportUnsafe(parent, BAD_EXPR_MSG);
+
+ return props.ComputeCumulativeEffect(IsRawPointerType(parent->getType()));
+ }
+
+ ExprEffect Seq(clang::Stmt* parent,
+ int n,
+ clang::Expr** exprs,
+ const Environment& env) {
+ ExprEffect out = ExprEffect::None();
+ Environment out_env = env;
+ for (int i = 0; i < n; ++i) {
+ out = ExprEffect::MergeSeq(out, VisitExpr(exprs[i], out_env));
+ out_env = out_env.ApplyEffect(out);
+ }
+ return out;
+ }
+
+ ExprEffect Use(const clang::Expr* parent,
+ const clang::QualType& var_type,
+ const std::string& var_name,
+ const Environment& env) {
+ if (IsRawPointerType(var_type)) {
+ if (!env.IsAlive(var_name) && dead_vars_analysis_) {
+ ReportUnsafe(parent, DEAD_VAR_MSG);
+ }
+ return ExprEffect::RawUse();
+ }
+ return ExprEffect::None();
+ }
+
+ ExprEffect Use(const clang::Expr* parent,
+ const clang::ValueDecl* var,
+ const Environment& env) {
+ return Use(parent, var->getType(), var->getNameAsString(), env);
+ }
+
+
+ template<typename ExprType>
+ ExprEffect VisitArguments(ExprType* call, const Environment& env) {
+ CallProps props;
+ VisitArguments<>(call, &props, env);
+ if (!props.IsSafe()) ReportUnsafe(call, BAD_EXPR_MSG);
+ return props.ComputeCumulativeEffect(IsRawPointerType(call->getType()));
+ }
+
+ template<typename ExprType>
+ void VisitArguments(ExprType* call,
+ CallProps* props,
+ const Environment& env) {
+ for (unsigned arg = 0; arg < call->getNumArgs(); arg++) {
+ props->SetEffect(arg + 1, VisitExpr(call->getArg(arg), env));
+ }
+ }
+
+
+ ExprEffect VisitCallExpr(clang::CallExpr* call,
+ const Environment& env) {
+ CallProps props;
+
+ clang::CXXMemberCallExpr* memcall =
+ dyn_cast_or_null<clang::CXXMemberCallExpr>(call);
+ if (memcall != NULL) {
+ clang::Expr* receiver = memcall->getImplicitObjectArgument();
+ props.SetEffect(0, VisitExpr(receiver, env));
+ }
+
+ VisitArguments<>(call, &props, env);
+
+ if (!props.IsSafe()) ReportUnsafe(call, BAD_EXPR_MSG);
+
+ ExprEffect out =
+ props.ComputeCumulativeEffect(IsRawPointerType(call->getType()));
+
+ clang::FunctionDecl* callee = call->getDirectCallee();
+ if ((callee != NULL) && KnownToCauseGC(ctx_, callee)) {
+ out.setGC();
+ }
+
+ return out;
+ }
+
+ // --------------------------------------------------------------------------
+ // Statements
+ // --------------------------------------------------------------------------
+
+ Environment VisitStmt(clang::Stmt* stmt, const Environment& env) {
+#define VISIT(type) do { \
+ clang::type* concrete_stmt = dyn_cast_or_null<clang::type>(stmt); \
+ if (concrete_stmt != NULL) { \
+ return Visit##type (concrete_stmt, env); \
+ } \
+ } while(0);
+
+ if (clang::Expr* expr = dyn_cast_or_null<clang::Expr>(stmt)) {
+ return env.ApplyEffect(VisitExpr(expr, env));
+ }
+
+ VISIT(AsmStmt);
+ VISIT(BreakStmt);
+ VISIT(CompoundStmt);
+ VISIT(ContinueStmt);
+ VISIT(CXXCatchStmt);
+ VISIT(CXXTryStmt);
+ VISIT(DeclStmt);
+ VISIT(DoStmt);
+ VISIT(ForStmt);
+ VISIT(GotoStmt);
+ VISIT(IfStmt);
+ VISIT(IndirectGotoStmt);
+ VISIT(LabelStmt);
+ VISIT(NullStmt);
+ VISIT(ReturnStmt);
+ VISIT(CaseStmt);
+ VISIT(DefaultStmt);
+ VISIT(SwitchStmt);
+ VISIT(WhileStmt);
+#undef VISIT
+
+ return env;
+ }
+
+#define DECL_VISIT_STMT(type) \
+ Environment Visit##type (clang::type* stmt, const Environment& env)
+
+#define IGNORE_STMT(type) \
+ Environment Visit##type (clang::type* stmt, const Environment& env) { \
+ return env; \
+ }
+
+ IGNORE_STMT(IndirectGotoStmt);
+ IGNORE_STMT(NullStmt);
+ IGNORE_STMT(AsmStmt);
+
+ // We are ignoring control flow for simplicity.
+ IGNORE_STMT(GotoStmt);
+ IGNORE_STMT(LabelStmt);
+
+ // We are ignoring try/catch because V8 does not use them.
+ IGNORE_STMT(CXXCatchStmt);
+ IGNORE_STMT(CXXTryStmt);
+
+ class Block {
+ public:
+ Block(const Environment& in,
+ FunctionAnalyzer* owner)
+ : in_(in),
+ out_(Environment::Unreachable()),
+ changed_(false),
+ owner_(owner) {
+ parent_ = owner_->EnterBlock(this);
+ }
+
+ ~Block() {
+ owner_->LeaveBlock(parent_);
+ }
+
+ void MergeIn(const Environment& env) {
+ Environment old_in = in_;
+ in_ = Environment::Merge(in_, env);
+ changed_ = !old_in.Equal(in_);
+ }
+
+ bool changed() {
+ if (changed_) {
+ changed_ = false;
+ return true;
+ }
+ return false;
+ }
+
+ const Environment& in() {
+ return in_;
+ }
+
+ const Environment& out() {
+ return out_;
+ }
+
+ void MergeOut(const Environment& env) {
+ out_ = Environment::Merge(out_, env);
+ }
+
+ void Seq(clang::Stmt* a, clang::Stmt* b, clang::Stmt* c) {
+ Environment a_out = owner_->VisitStmt(a, in());
+ Environment b_out = owner_->VisitStmt(b, a_out);
+ Environment c_out = owner_->VisitStmt(c, b_out);
+ MergeOut(c_out);
+ }
+
+ void Seq(clang::Stmt* a, clang::Stmt* b) {
+ Environment a_out = owner_->VisitStmt(a, in());
+ Environment b_out = owner_->VisitStmt(b, a_out);
+ MergeOut(b_out);
+ }
+
+ void Loop(clang::Stmt* a, clang::Stmt* b, clang::Stmt* c) {
+ Seq(a, b, c);
+ MergeIn(out());
+ }
+
+ void Loop(clang::Stmt* a, clang::Stmt* b) {
+ Seq(a, b);
+ MergeIn(out());
+ }
+
+
+ private:
+ Environment in_;
+ Environment out_;
+ bool changed_;
+ FunctionAnalyzer* owner_;
+ Block* parent_;
+ };
+
+
+ DECL_VISIT_STMT(BreakStmt) {
+ block_->MergeOut(env);
+ return Environment::Unreachable();
+ }
+
+ DECL_VISIT_STMT(ContinueStmt) {
+ block_->MergeIn(env);
+ return Environment::Unreachable();
+ }
+
+ DECL_VISIT_STMT(CompoundStmt) {
+ Environment out = env;
+ clang::CompoundStmt::body_iterator end = stmt->body_end();
+ for (clang::CompoundStmt::body_iterator s = stmt->body_begin();
+ s != end;
+ ++s) {
+ out = VisitStmt(*s, out);
+ }
+ return out;
+ }
+
+ DECL_VISIT_STMT(WhileStmt) {
+ Block block (env, this);
+ do {
+ block.Loop(stmt->getCond(), stmt->getBody());
+ } while (block.changed());
+ return block.out();
+ }
+
+ DECL_VISIT_STMT(DoStmt) {
+ Block block (env, this);
+ do {
+ block.Loop(stmt->getBody(), stmt->getCond());
+ } while (block.changed());
+ return block.out();
+ }
+
+ DECL_VISIT_STMT(ForStmt) {
+ Block block (VisitStmt(stmt->getInit(), env), this);
+ do {
+ block.Loop(stmt->getCond(),
+ stmt->getBody(),
+ stmt->getInc());
+ } while (block.changed());
+ return block.out();
+ }
+
+ DECL_VISIT_STMT(IfStmt) {
+ Environment cond_out = VisitStmt(stmt->getCond(), env);
+ Environment then_out = VisitStmt(stmt->getThen(), cond_out);
+ Environment else_out = VisitStmt(stmt->getElse(), cond_out);
+ return Environment::Merge(then_out, else_out);
+ }
+
+ DECL_VISIT_STMT(SwitchStmt) {
+ Block block (env, this);
+ block.Seq(stmt->getCond(), stmt->getBody());
+ return block.out();
+ }
+
+ DECL_VISIT_STMT(CaseStmt) {
+ Environment in = Environment::Merge(env, block_->in());
+ Environment after_lhs = VisitStmt(stmt->getLHS(), in);
+ return VisitStmt(stmt->getSubStmt(), after_lhs);
+ }
+
+ DECL_VISIT_STMT(DefaultStmt) {
+ Environment in = Environment::Merge(env, block_->in());
+ return VisitStmt(stmt->getSubStmt(), in);
+ }
+
+ DECL_VISIT_STMT(ReturnStmt) {
+ VisitExpr(stmt->getRetValue(), env);
+ return Environment::Unreachable();
+ }
+
+ const clang::TagType* ToTagType(const clang::Type* t) {
+ if (t == NULL) {
+ return NULL;
+ } else if (isa<clang::TagType>(t)) {
+ return cast<clang::TagType>(t);
+ } else if (isa<clang::SubstTemplateTypeParmType>(t)) {
+ return ToTagType(cast<clang::SubstTemplateTypeParmType>(t)->
+ getReplacementType().getTypePtr());
+ } else {
+ return NULL;
+ }
+ }
+
+ bool IsDerivedFrom(clang::CXXRecordDecl* record,
+ clang::CXXRecordDecl* base) {
+ return (record == base) || record->isDerivedFrom(base);
+ }
+
+ bool IsRawPointerType(clang::QualType qtype) {
+ const clang::PointerType* type =
+ dyn_cast_or_null<clang::PointerType>(qtype.getTypePtrOrNull());
+ if (type == NULL) return false;
+
+ const clang::TagType* pointee =
+ ToTagType(type->getPointeeType().getTypePtr());
+ if (pointee == NULL) return false;
+
+ clang::CXXRecordDecl* record =
+ dyn_cast_or_null<clang::CXXRecordDecl>(pointee->getDecl());
+ if (record == NULL) return false;
+
+ if (!InV8Namespace(record)) return false;
+
+ if (!record->hasDefinition()) return false;
+
+ record = record->getDefinition();
+
+ return IsDerivedFrom(record, object_decl_) &&
+ !IsDerivedFrom(record, smi_decl_);
+ }
+
+ Environment VisitDecl(clang::Decl* decl, const Environment& env) {
+ if (clang::VarDecl* var = dyn_cast<clang::VarDecl>(decl)) {
+ Environment out = var->hasInit() ? VisitStmt(var->getInit(), env) : env;
+
+ if (IsRawPointerType(var->getType())) {
+ out = out.Define(var->getNameAsString());
+ }
+
+ return out;
+ }
+ // TODO: handle other declarations?
+ return env;
+ }
+
+ DECL_VISIT_STMT(DeclStmt) {
+ Environment out = env;
+ clang::DeclStmt::decl_iterator end = stmt->decl_end();
+ for (clang::DeclStmt::decl_iterator decl = stmt->decl_begin();
+ decl != end;
+ ++decl) {
+ out = VisitDecl(*decl, out);
+ }
+ return out;
+ }
+
+
+ void DefineParameters(const clang::FunctionDecl* f,
+ Environment* env) {
+ env->MDefine(THIS);
+ clang::FunctionDecl::param_const_iterator end = f->param_end();
+ for (clang::FunctionDecl::param_const_iterator p = f->param_begin();
+ p != end;
+ ++p) {
+ env->MDefine((*p)->getNameAsString());
+ }
+ }
+
+
+ void AnalyzeFunction(const clang::FunctionDecl* f) {
+ const clang::FunctionDecl* body = NULL;
+ if (f->hasBody(body)) {
+ Environment env;
+ DefineParameters(body, &env);
+ VisitStmt(body->getBody(), env);
+ Environment::ClearSymbolTable();
+ }
+ }
+
+ Block* EnterBlock(Block* block) {
+ Block* parent = block_;
+ block_ = block;
+ return parent;
+ }
+
+ void LeaveBlock(Block* block) {
+ block_ = block;
+ }
+
+ private:
+ void ReportUnsafe(const clang::Expr* expr, const std::string& msg) {
+ d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_),
+ d_.getCustomDiagID(clang::Diagnostic::Warning, msg));
+ }
+
+
+ clang::MangleContext* ctx_;
+ clang::DeclarationName handle_decl_name_;
+ clang::CXXRecordDecl* object_decl_;
+ clang::CXXRecordDecl* smi_decl_;
+
+ clang::Diagnostic& d_;
+ clang::SourceManager& sm_;
+
+ Block* block_;
+ bool dead_vars_analysis_;
+};
+
+
+class ProblemsFinder : public clang::ASTConsumer,
+ public clang::RecursiveASTVisitor<ProblemsFinder> {
+ public:
+ ProblemsFinder(clang::Diagnostic& d,
+ clang::SourceManager& sm,
+ const std::vector<std::string>& args)
+ : d_(d), sm_(sm), dead_vars_analysis_(false) {
+ for (unsigned i = 0; i < args.size(); ++i) {
+ if (args[i] == "--dead-vars") {
+ dead_vars_analysis_ = true;
+ }
+ }
+ }
+
+ virtual void HandleTranslationUnit(clang::ASTContext &ctx) {
+ Resolver r(ctx);
+
+ clang::CXXRecordDecl* object_decl =
+ r.ResolveNamespace("v8").ResolveNamespace("internal").
+ Resolve<clang::CXXRecordDecl>("Object");
+
+ clang::CXXRecordDecl* smi_decl =
+ r.ResolveNamespace("v8").ResolveNamespace("internal").
+ Resolve<clang::CXXRecordDecl>("Smi");
+
+ if (object_decl != NULL) object_decl = object_decl->getDefinition();
+
+ if (smi_decl != NULL) smi_decl = smi_decl->getDefinition();
+
+ if (object_decl != NULL && smi_decl != NULL) {
+ function_analyzer_ =
+ new FunctionAnalyzer(clang::createItaniumMangleContext(ctx, d_),
+ r.ResolveName("Handle"),
+ object_decl,
+ smi_decl,
+ d_,
+ sm_,
+ dead_vars_analysis_);
+ TraverseDecl(ctx.getTranslationUnitDecl());
+ } else {
+ if (object_decl == NULL) {
+ llvm::errs() << "Failed to resolve v8::internal::Object\n";
+ }
+ if (smi_decl == NULL) {
+ llvm::errs() << "Failed to resolve v8::internal::Smi\n";
+ }
+ }
+ }
+
+ virtual bool VisitFunctionDecl(clang::FunctionDecl* decl) {
+ function_analyzer_->AnalyzeFunction(decl);
+ return true;
+ }
+
+ private:
+ clang::Diagnostic& d_;
+ clang::SourceManager& sm_;
+ bool dead_vars_analysis_;
+
+ FunctionAnalyzer* function_analyzer_;
+};
+
+
+template<typename ConsumerType>
+class Action : public clang::PluginASTAction {
+ protected:
+ clang::ASTConsumer *CreateASTConsumer(clang::CompilerInstance &CI,
+ llvm::StringRef InFile) {
+ return new ConsumerType(CI.getDiagnostics(), CI.getSourceManager(), args_);
+ }
+
+ bool ParseArgs(const clang::CompilerInstance &CI,
+ const std::vector<std::string>& args) {
+ args_ = args;
+ return true;
+ }
+
+ void PrintHelp(llvm::raw_ostream& ros) {
+ }
+ private:
+ std::vector<std::string> args_;
+};
+
+
+}
+
+static clang::FrontendPluginRegistry::Add<Action<ProblemsFinder> >
+FindProblems("find-problems", "Find GC-unsafe places.");
+
+static clang::FrontendPluginRegistry::Add<
+ Action<FunctionDeclarationFinder> >
+DumpCallees("dump-callees", "Dump callees for each function.");
diff --git a/deps/v8/tools/gcmole/gcmole.lua b/deps/v8/tools/gcmole/gcmole.lua
new file mode 100644
index 0000000000..4afc66d1cb
--- /dev/null
+++ b/deps/v8/tools/gcmole/gcmole.lua
@@ -0,0 +1,378 @@
+-- Copyright 2011 the V8 project authors. All rights reserved.
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions are
+-- met:
+--
+-- * Redistributions of source code must retain the above copyright
+-- notice, this list of conditions and the following disclaimer.
+-- * Redistributions in binary form must reproduce the above
+-- copyright notice, this list of conditions and the following
+-- disclaimer in the documentation and/or other materials provided
+-- with the distribution.
+-- * Neither the name of Google Inc. nor the names of its
+-- contributors may be used to endorse or promote products derived
+-- from this software without specific prior written permission.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-- This is main driver for gcmole tool. See README for more details.
+-- Usage: CLANG_BIN=clang-bin-dir lua tools/gcmole/gcmole.lua [arm|ia32|x64]
+
+local DIR = arg[0]:match("^(.+)/[^/]+$")
+
+local FLAGS = {
+ -- Do not build gcsuspects file and reuse previously generated one.
+ reuse_gcsuspects = false;
+
+ -- Print commands to console before executing them.
+ verbose = false;
+
+ -- Perform dead variable analysis (generates many false positives).
+ -- TODO add some sort of whiteliste to filter out false positives.
+ dead_vars = false;
+
+ -- When building gcsuspects whitelist certain functions as if they
+ -- can be causing GC. Currently used to reduce number of false
+ -- positives in dead variables analysis. See TODO for WHITELIST
+ -- below.
+ whitelist = true;
+}
+local ARGS = {}
+
+for i = 1, #arg do
+ local flag = arg[i]:match "^%-%-([%w_-]+)$"
+ if flag then
+ local no, real_flag = flag:match "^(no)([%w_-]+)$"
+ if real_flag then flag = real_flag end
+
+ flag = flag:gsub("%-", "_")
+ if FLAGS[flag] ~= nil then
+ FLAGS[flag] = (no ~= "no")
+ else
+ error("Unknown flag: " .. flag)
+ end
+ else
+ table.insert(ARGS, arg[i])
+ end
+end
+
+local ARCHS = ARGS[1] and { ARGS[1] } or { 'ia32', 'arm', 'x64' }
+
+local io = require "io"
+local os = require "os"
+
+function log(...)
+ io.stderr:write(string.format(...))
+ io.stderr:write "\n"
+end
+
+-------------------------------------------------------------------------------
+-- Clang invocation
+
+local CLANG_BIN = os.getenv "CLANG_BIN"
+
+if not CLANG_BIN or CLANG_BIN == "" then
+ error "CLANG_BIN not set"
+end
+
+local function MakeClangCommandLine(plugin, plugin_args, triple, arch_define)
+ if plugin_args then
+ for i = 1, #plugin_args do
+ plugin_args[i] = "-plugin-arg-" .. plugin .. " " .. plugin_args[i]
+ end
+ plugin_args = " " .. table.concat(plugin_args, " ")
+ end
+ return CLANG_BIN .. "/clang -cc1 -load " .. DIR .. "/libgcmole.so"
+ .. " -plugin " .. plugin
+ .. (plugin_args or "")
+ .. " -triple " .. triple
+ .. " -D" .. arch_define
+ .. " -DENABLE_VMSTATE_TRACKING"
+ .. " -DENABLE_LOGGING_AND_PROFILING"
+ .. " -DENABLE_DEBUGGER_SUPPORT"
+ .. " -Isrc"
+end
+
+function InvokeClangPluginForEachFile(filenames, cfg, func)
+ local cmd_line = MakeClangCommandLine(cfg.plugin,
+ cfg.plugin_args,
+ cfg.triple,
+ cfg.arch_define)
+
+ for _, filename in ipairs(filenames) do
+ log("-- %s", filename)
+ local action = cmd_line .. " src/" .. filename .. " 2>&1"
+ if FLAGS.verbose then print('popen ', action) end
+ local pipe = io.popen(action)
+ func(filename, pipe:lines())
+ pipe:close()
+ end
+end
+
+-------------------------------------------------------------------------------
+-- SConscript parsing
+
+local function ParseSConscript()
+ local f = assert(io.open("src/SConscript"), "failed to open SConscript")
+ local sconscript = f:read('*a')
+ f:close()
+
+ local SOURCES = sconscript:match "SOURCES = {(.-)}";
+
+ local sources = {}
+
+ for condition, list in
+ SOURCES:gmatch "'([^']-)': Split%(\"\"\"(.-)\"\"\"%)" do
+ local files = {}
+ for file in list:gmatch "[^%s]+" do table.insert(files, file) end
+ sources[condition] = files
+ end
+
+ for condition, list in SOURCES:gmatch "'([^']-)': %[(.-)%]" do
+ local files = {}
+ for file in list:gmatch "'([^']-)'" do table.insert(files, file) end
+ sources[condition] = files
+ end
+
+ return sources
+end
+
+local function EvaluateCondition(cond, props)
+ if cond == 'all' then return true end
+
+ local p, v = cond:match "(%w+):(%w+)"
+
+ assert(p and v, "failed to parse condition: " .. cond)
+ assert(props[p] ~= nil, "undefined configuration property: " .. p)
+
+ return props[p] == v
+end
+
+local function BuildFileList(sources, props)
+ local list = {}
+ for condition, files in pairs(sources) do
+ if EvaluateCondition(condition, props) then
+ for i = 1, #files do table.insert(list, files[i]) end
+ end
+ end
+ return list
+end
+
+local sources = ParseSConscript()
+
+local function FilesForArch(arch)
+ return BuildFileList(sources, { os = 'linux',
+ arch = arch,
+ mode = 'debug',
+ simulator = ''})
+end
+
+local mtConfig = {}
+
+mtConfig.__index = mtConfig
+
+local function config (t) return setmetatable(t, mtConfig) end
+
+function mtConfig:extend(t)
+ local e = {}
+ for k, v in pairs(self) do e[k] = v end
+ for k, v in pairs(t) do e[k] = v end
+ return config(e)
+end
+
+local ARCHITECTURES = {
+ ia32 = config { triple = "i586-unknown-linux",
+ arch_define = "V8_TARGET_ARCH_IA32" },
+ arm = config { triple = "i586-unknown-linux",
+ arch_define = "V8_TARGET_ARCH_ARM" },
+ x64 = config { triple = "x86_64-unknown-linux",
+ arch_define = "V8_TARGET_ARCH_X64" }
+}
+
+-------------------------------------------------------------------------------
+-- GCSuspects Generation
+
+local gc, gc_caused, funcs
+
+local WHITELIST = {
+ -- The following functions call CEntryStub which is always present.
+ "MacroAssembler.*CallExternalReference",
+ "MacroAssembler.*CallRuntime",
+ "CompileCallLoadPropertyWithInterceptor",
+ "CallIC.*GenerateMiss",
+
+ -- DirectCEntryStub is a special stub used on ARM.
+ -- It is pinned and always present.
+ "DirectCEntryStub.*GenerateCall",
+
+ -- TODO GCMole currently is sensitive enough to understand that certain
+ -- functions only cause GC and return Failure simulataneously.
+ -- Callsites of such functions are safe as long as they are properly
+ -- check return value and propagate the Failure to the caller.
+ -- It should be possible to extend GCMole to understand this.
+ "Heap.*AllocateFunctionPrototype"
+};
+
+local function AddCause(name, cause)
+ local t = gc_caused[name]
+ if not t then
+ t = {}
+ gc_caused[name] = t
+ end
+ table.insert(t, cause)
+end
+
+local function resolve(name)
+ local f = funcs[name]
+
+ if not f then
+ f = {}
+ funcs[name] = f
+
+ if name:match "Collect.*Garbage" then
+ gc[name] = true
+ AddCause(name, "<GC>")
+ end
+
+ if FLAGS.whitelist then
+ for i = 1, #WHITELIST do
+ if name:match(WHITELIST[i]) then
+ gc[name] = false
+ end
+ end
+ end
+ end
+
+ return f
+end
+
+local function parse (filename, lines)
+ local scope
+
+ for funcname in lines do
+ if funcname:sub(1, 1) ~= '\t' then
+ resolve(funcname)
+ scope = funcname
+ else
+ local name = funcname:sub(2)
+ resolve(name)[scope] = true
+ end
+ end
+end
+
+local function propagate ()
+ log "** Propagating GC information"
+
+ local function mark(from, callers)
+ for caller, _ in pairs(callers) do
+ if gc[caller] == nil then
+ gc[caller] = true
+ mark(caller, funcs[caller])
+ end
+ AddCause(caller, from)
+ end
+ end
+
+ for funcname, callers in pairs(funcs) do
+ if gc[funcname] then mark(funcname, callers) end
+ end
+end
+
+local function GenerateGCSuspects(arch, files, cfg)
+ -- Reset the global state.
+ gc, gc_caused, funcs = {}, {}, {}
+
+ log ("** Building GC Suspects for %s", arch)
+ InvokeClangPluginForEachFile (files,
+ cfg:extend { plugin = "dump-callees" },
+ parse)
+
+ propagate()
+
+ local out = assert(io.open("gcsuspects", "w"))
+ for name, value in pairs(gc) do if value then out:write (name, '\n') end end
+ out:close()
+
+ local out = assert(io.open("gccauses", "w"))
+ out:write "GC = {"
+ for name, causes in pairs(gc_caused) do
+ out:write("['", name, "'] = {")
+ for i = 1, #causes do out:write ("'", causes[i], "';") end
+ out:write("};\n")
+ end
+ out:write "}"
+ out:close()
+
+ log ("** GCSuspects generated for %s", arch)
+end
+
+--------------------------------------------------------------------------------
+-- Analysis
+
+local function CheckCorrectnessForArch(arch)
+ local files = FilesForArch(arch)
+ local cfg = ARCHITECTURES[arch]
+
+ if not FLAGS.reuse_gcsuspects then
+ GenerateGCSuspects(arch, files, cfg)
+ end
+
+ local processed_files = 0
+ local errors_found = false
+ local function SearchForErrors(filename, lines)
+ processed_files = processed_files + 1
+ for l in lines do
+ errors_found = errors_found or
+ l:match "^[^:]+:%d+:%d+:" or
+ l:match "error" or
+ l:match "warning"
+ print(l)
+ end
+ end
+
+ log("** Searching for evaluation order problems%s for %s",
+ FLAGS.dead_vars and " and dead variables" or "",
+ arch)
+ local plugin_args
+ if FLAGS.dead_vars then plugin_args = { "--dead-vars" } end
+ InvokeClangPluginForEachFile(files,
+ cfg:extend { plugin = "find-problems",
+ plugin_args = plugin_args },
+ SearchForErrors)
+ log("** Done processing %d files. %s",
+ processed_files,
+ errors_found and "Errors found" or "No errors found")
+
+ return errors_found
+end
+
+local function SafeCheckCorrectnessForArch(arch)
+ local status, errors = pcall(CheckCorrectnessForArch, arch)
+ if not status then
+ print(string.format("There was an error: %s", errors))
+ errors = true
+ end
+ return errors
+end
+
+local errors = false
+
+for _, arch in ipairs(ARCHS) do
+ if not ARCHITECTURES[arch] then
+ error ("Unknown arch: " .. arch)
+ end
+
+ errors = SafeCheckCorrectnessForArch(arch, report) or errors
+end
+
+os.exit(errors and 1 or 0)